Sprache auswählen

Je größer ein Programm wird, desto wahrscheinlicher wird es, dass man mal ein Menü braucht, in dem der Benutzer Einstellungen anpassen kann. In Kivy ist diese Funktionalität bereits integriert. Wie immer gibt es auch hier mehrere Möglichkeiten, die ich nacheinander aufzeige.

Grundlegendes

Was man immer benötigt ist eine JSON Datei, in der der Aufbau und der Inhalt des Menüs definiert wird. Grundsätzlich kann man vier Typen von Daten verwenden.

  • Dateipfade
  • Boolsche Werte
  • Optionen
  • Numerische Werte
  • Strings

Diese Werte versieht man im JSON File noch mit einem Titel, einer eventuell benötigten Beschreibung, einem Marker dafür, in welchem Reiter (Panel) des Menüs der Datensatz angezeigt werden soll und einen Key, mit dem man die Daten später auslesen kann.

Hier der Code für die JSON Datei settings.json

[
    {
        "type": "path",
        "title": "Pfad zur Datei",
        "desc": "Datei auswählen",
        "section": "Panel 1",
        "key": "item 1"
    },
    {
        "type": "bool",
        "title": "Benachrichtigungen",
        "desc": "Benachrichtigunen ein / aus",
        "section": "Panel 1",
        "key": "item 2"
    },
    {
        "type": "options",
        "title": "Textfarbe",
        "desc": "Auswahl der Textfarbe",
        "section": "Panel 1",
        "key": "item 3",
        "options": ["Blau", "Gelb", "Schwarz"]
    },
    {
        "type": "numeric",
        "title": "Alarmgrenze",
        "desc": "Obergrenze für XYZ",
        "section": "Panel 1",
        "key": "item 4"
    }
]

Variante 1

Die erste Variante, die ich eigentlich auch als die einfachste ansehe benötigt eine zweite Datei, in der man die Anfangswerte für die Menüpunkte definiert. Die Dateinendung ist in diesem Fall .ini.

mysettings.ini

[Panel 1]
item 1 = /
item 2 = 1
item 3 = Blau
item 4 = 30

Wichtig ist hier, dass die Menüpunkte der JSON Datei hier 1:1 aufgeführt werden. Lücken oder zusätzliche Items sind nicht zulässig. Andernfalls gibt es beim Parsen Fehler.

Jetzt geht es an das eigentliche Programm. Im .kv File definieren wir einfach einen Button, der das Menü öffnet. Das Menü wird mit den Standardwerten geöffnet. Die Optionen, die man hat, probieren wir in den später folgenden Beispielen aus.

settings.kv

BoxLayout:
    orientation: 'vertical'
    Button:
        text: 'Einstellungen'
        on_release: app.open_settings()

settingsApp.py

# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.settings import SettingsWithSidebar

class SettingsApp(App):
    def build(self):
        self.settings_cls = SettingsWithSidebar
        #Read the config from file
        self.config.read('mysettings.ini')

    def build_settings(self, settings):
        '''The json file will be loaded and constructs the layout of the menu'''
        settings.add_json_panel('Panel 1', self.config, 'settings.json')

SettingsApp().run()

Im Programm passieren zwei Dinge. Die definierten Methoden build() und build_settings() werden während der Initialisierung des Programms aufgerufen. Dabei wird mysettings.ini gelesen und damit die Standardwerte festgelegt. Mit der Methode setting.add_json_panel() liest man die einzelnen Werte ein, die von einem spezialisierten JSON Parser interpretiert und in unser Menü umgesetzt werden.

Mit dem Button ruft man dann die Funktion auf, die die Einstellungen öffnen. Man sieht auch, dass wir hier lediglich ein weiteres Panel hinzugefügt haben. Das Panel "Kivy" ist standardmäßig vorhanden. 

Wer jetzt ein wenig an den Werten schraubt und das Menü wieder schließt mag sich fragen, was mit den Werten beim Neustart des Programms passiert. Sind die weg oder werden die irgendwie gespeichert???

Die Antwort ist ja. Die Werte werden in der mysettings.ini gespeichert und beim nächsten Start des Programms sind die zuletzt eingestellten Werte verfügbar.

Variante 2

Hier spart man sich die Erstellung der .ini Datei, muss aber die Standardwerte im .py File definieren. Dazu müssen wir lediglich eine Methode build_config() hinzuufügen und hier zunächst unser Panel einfügen und anschließend die Standardwerte unterhalb des Panels festlegen. Fertig. Das restliche Verhalten entspricht 1:1 dem, der Variante 1.

# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.settings import SettingsWithSpinner


class SettingsApp(App):
    def build(self):
        self.settings_cls = SettingsWithSpinner

    def build_config(self, config):
        config.adddefaultsection('Panel 1')
        config.setdefaults('Panel 1',
                           {'item 1': '/',
                            'item 2': 'False',
                            'item 3': 'None',
                            'item 4':'25'})

    def build_settings(self, settings):
        '''The json file will be loaded and constructs the layout of the menu'''
        settings.add_json_panel('Panel 1', self.config, 'settings.json')

SettingsApp().run()

Was man noch beachten kann ist die Tatsache, dass diese Variante ebenfalls die Einstellungen abspeichert. Dazu erzeugt das Programm selbstständig die Datei settings.ini. Beim erneuten Ausführen werden die Standardwerte aus dieser geladen.

Sonstiges

Ein paar Punkte müssen wir uns jetzt noch ansehen.

  • Wie komme ich an die Werte im Menü
  • Kann ich bei einer Änderung automatisch etwas im Programm updaten
  • Kann ich das Erscheinungsbild des Menüs beeinflussen?

Beides zeige ich am folgenden Beispiel. Das .kv Layout bietet die standardmäßig vorhandene Auswahl an Layouts für das Menü und im weiteren Verlauf rufen wir Daten aus dem Menü ab.

BoxLayout:
    orientation: 'vertical'
    Label:
        text: '1 - Choose your settings layout'
    BoxLayout:
        orientation: 'horizontal'
        ToggleButton:
            id: TabbedPanel
            text: 'Tabbed Panel'
            on_press:
                app.set_type('TabbedPanel')
                app.untoggle('TabbedPanel')        
        ToggleButton:
            id: Spinner
            text: 'Spinner'
            on_press:
                app.set_type('Spinner')
                app.untoggle('Spinner')
        ToggleButton:
            id: Sidebar
            text: 'Sidebar'
            on_press:
                app.set_type('Sidebar')
                app.untoggle('Sidebar')
        ToggleButton:
            id: NoMenu
            text: 'No Menu\n(Leave with ESC)'
            on_press:
                app.set_type('NoMenu')
                app.untoggle('NoMenu')
    Button:
        text: '2 - Open the settings'
        on_release: app.open_settings()
    Button:
        text: '3 - Get the settings'
        id: read_menu
        on_release: app.print_current_settings()
    Label:
        text: 'Press button'
        id: settings_content
        

 

# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.settings import SettingsWithTabbedPanel
from kivy.uix.settings import SettingsWithSpinner
from kivy.uix.settings import SettingsWithSidebar
from kivy.uix.settings import SettingsWithNoMenu
from kivy.uix.togglebutton import ToggleButton


class SettingsApp(App):

    choices = {'TabbedPanel': SettingsWithTabbedPanel,
               'Spinner': SettingsWithSpinner,
               'Sidebar' : SettingsWithSidebar,
               'NoMenu': SettingsWithNoMenu}

    def build(self):
        self.settings_cls = SettingsWithSidebar
        self.config.read('mysettings.ini')

    def build_settings(self, settings):
        '''The json file will be loaded and constructs the layout of the menu'''
        settings.add_json_panel('Panel 1', self.config, 'settings.json')

    def set_type(self, settings_type):
        #old layout needs to be removed or nothing will be updated
        self.destroy_settings()
        #set chosen layout
        self.settings_cls = self.choices[settings_type]

    def untoggle(self, active_button):
        buttons = ('TabbedPanel', 'Spinner', 'Sidebar', 'NoMenu')

        for button in buttons:
            if button != active_button:
                self.get_running_app().root.ids[button].state = 'normal'

    def print_current_settings(self):
        #get all items of the settings
        items = self.config.items('Panel 1')

        #loop over the items and print them to the label
        text = ''
        for item in items:
            text = text + self.config.get('Panel 1', item[0]) + '\n'
            
        self.root.ids['settings_content'].text = text

    def on_config_change(self, config, section, key, value):
        print('config has changed.\nsection: {}, key: {}, new value: {}'. format(section, key, value))
        if key == 'item 3':
            if value == 'Blau':
                self.root.ids['settings_content'].color = (0,0,1,1)
                print('Blue selected')
            if value == 'Gelb':
                self.root.ids['settings_content'].color = (0,1,0,1)
                print('Yellow selected')
            if value == 'Schwarz':
                self.root.ids['settings_content'].color = (0,0,0,1)
                print('Black selected')

SettingsApp().run()

Damit kann man mit den Effekten Spielen und sich den Umgang mit den Funktionen in Ruhe vor Augen führen.

Die Funktion config.get(Panel, item) nutze ich dazu, aus dem Programm heraus Werte aus dem Menü auszulesen.

Wenn man beim Ändern von Menüwerten Aktionen auslösen will, kann man das mit der Funktion on_config_change(self, config, key, item) tun. Hier färbe ich die Schrift entsprechend der Menüauswahl.

Wer einen Blick in das Terminal wirft sieht, dass die geänderten Einstellungen auch textuell ausgegeben werden.

Natürlich ist auch nur das hier die Spitze des Eisbergs. Die Doku bietet aber viele Anregungen dazu, was sonst noch so geht.

Quellcode auf GitHub

Der aktuellste Code ist immer auf GitHub verügbar