Kivy – modernes GUI-Toolkit für Python Projekte

Foto des Autors
Daniel Naczinski

Mit dem GUI-Framework Kivy lassen sich moderne Python-basierte Desktop-Anwendungen und mobile interaktive Multi-Touch-Applikationen bauen. In diesem Wissensbeitrag zeigen wir, warum man in Python-Projekten auf Kivy setzen sollte. Den Umgang mit dem Framework demonstrieren wir anhand eines Beispiels.

Was ist Kivy?

Kivy ist ein GUI-Framework (Graphical User Interface-Framework) und ermöglicht uns eine moderne und innovative Gestaltung von Benutzeroberflächen. Es handelt sich um ein Open-Source-Toolkit, das aus einem Mix von Python und Cython geschrieben ist. Mit Kivy lassen sich sowohl ansprechende Desktop-Applikationen, als auch mobile interaktive Multi-Touch-Anwendungen bauen.

Der große Vorteil von Kivy besteht darin, dass der GUI-Code auf verschiedenen Systemen wiederverwendbar ist, sodass plattformübergreifende UIs für z.B. Windows, macOS, Linux, iOS und Android erstellt werden können. Außerdem wird auch der Raspberry Pi unterstützt, wobei eine flüssige und ressourcenarme Bedienung der grafischen Elemente möglich ist.

Unsere folgenden Screenshots zeigen beispielhafte Kivy-GUIs:

Kivy-GUI-Beispiel 1: Einstellungsseite eines Pflanzenbewässerungssystems
Kivy-GUI-Beispiel 2: Temperaturüberwachung eines Pflanzenbewässerungssystems

Python und Kivy

Kivy besteht zu großen Teilen aus der Programmiersprache Python und ist daher sehr gut als GUI-Toolkit für Python-Projekte geeignet. Python stellt grundsätzlich eine große Standardbibliothek bereit, die den Entwicklungsprozess von Projekten erleichtert. Darüber hinaus gibt es dank der großen Python Community eine Vielzahl an Frameworks und Bibliotheken, sodass auch umfangreiche Projekte entstehen können.

Python gilt als eine interpretierte Sprache. Das bedeutet im Wesentlichen, dass der Quellcode während der Laufzeit in Bytecode konvertiert wird und anschließend in der Python Virtual Machine (PVM) ausgeführt wird. Anders als bei den kompilierten Sprachen, wie z.B. Java, wird der Python Code nicht vor-kompiliert. Der Vorteil ist dabei, dass auf diese Weise Python-Code schnell entwickelt und ausgeführt werden kann. Die Kombination aus Python und Kivy ermöglicht die Entwicklung von Projekten auf Basis des Rapid Prototypings, sodass ein schneller Entwicklungszyklus ermöglicht wird.

Warum Kivy?

Für die Erstellung grafischer Benutzeroberflächen werden einige Frameworks im Python-Umfeld angeboten. Jedoch wirken viele Frameworks nicht sonderlich intuitiv, die Optik der Benutzeroberflächen sieht nicht zeitgemäß aus oder plattformübergreifende Portierungen sind nur mit großem Aufwand möglich.

Kivy bietet eine große Sammlung von Grafik- und Interaktionskomponenten an, mit denen plattformübergreifende Anwendungen entwickelt werden können. Es wird eine Vielzahl an Standard-Komponenten geboten, wie z.B. Text-Inputs, Sliders, (Toggle-) Buttons, Labels, Check-Boxen und vieles mehr. Es können auch Maus-, Tastatur- und (Multi-) Touchaktionen eingebunden werden. Dazu setzt es das Prinzip des Natural User Interfaces (NUI) um, was dem:der Nutzer:in eine direkte Interaktion mit der Bedienoberfläche durch Gesten ermöglicht.

Das Kivy-Toolkit ist optimal zur Gestaltung von Benutzerschnittstellen für Gerätesteuerungen oder -überwachungen im Smart-Home-Bereich geeignet. Auch auf Plattformen, die auf effiziente Ressourcenauslastung angewiesen sind, lassen sich Kivy-GUIs verwenden. Selbst auf Einplatinen-Rechnern, wie dem Raspberry Pi, sind Benutzeroberflächen flüssig und intuitiv bedienbar.

Abgrenzung von anderen GUI-Toolkits

Kivy unterscheidet sich von anderen GUI-Toolkits darin, dass das gesamte Layout der Benutzeroberfläche in eine eigene Beschreibungssprache ausgelagert ist. Die Kv-Beschreibungssprache ermöglicht eine strikte Trennung zwischen Layout und Anwendungslogik, sodass es mit dem Entwurfsprinzip des Separation of concerns einhergeht. Damit lassen sich Projekte anhand des Model-View-Controller-Entwurfsmusters (kurz: MVC-Muster) realisieren.

Dazu werden in einer kv-Datei einzelne grafische Komponenten, auch Widgets genannt, textuell beschrieben und hierarchisch angeordnet. Es lassen sich ebenfalls eigene, benutzerdefinierte Widgets auf Basis von bereits existierenden Widgets kreieren.

Darüber hinaus ist es erwähnenswert, dass innerhalb des Kivy-Frameworks eine OpenGL-basierte Grafik-Bibliothek eingebunden ist, die für Effizienz auf Systemen mit geringen Ressourcen sorgt.

Erste Schritte

Die Installation des Kivy-Frameworks ist auf allen gängigen Plattformen leicht durchzuführen. Da es bei der Integration von Kivy systemabhängige Unterschiede gibt, findet man genauere Anleitungen auf der offiziellen Webseite kivy.org.

Beispiel: Implementierung eines Taschenrechners mit Kivy

Um den Umgang mit Kivy beispielhaft zu demonstrieren, implementieren wir einen simplen Taschenrechner mit den mathematischen Standardoperationen.

Zur Visualisierung der grafischen Oberfläche zeigen wir Euch im folgenden Bild einen Screenshot des Taschenrechners.

Hier zu sehen: Screenshot des Taschenrechners, welcher mit Kivy erstellt wurde
Implementierung eines Taschenrechners mit Kivy

Der Taschenrechner soll am oberen Rand ein rechtsbündiges Label enthalten, das die Rechnung bzw. das Resultat der Rechnung zeigt.

Unter dem Label werden die Standardoperationen Division, Multiplikation, Subtraktion und Addition, sowie Buttons zum ganzheitlichen Löschen oder Löschen der letzten Eingabe bereitgestellt.

Die anderen Buttons sind analog zu einem herkömmlichen NumPad aufgebaut.

Damit das Programm starten kann, benötigen wir zunächst eine main-Funktion. Dafür implementieren wir eine Datei main.py, die nur die main-Funktion enthält, in der die App gestartet wird:

from calculator import CalculatorApp

if __name__ == '__main__':
CalculatorApp().run()
Code-Sprache: Python (python)

Außerdem erstellen wir eine Datei calculator.py, in der wir die Logik einiger Buttons bereitstellen:

from kivy.app import App
from kivy.uix.widget import Widget


class CalculatorApp(App):
    def build(self):
        return CalculatorWidget()


class CalculatorWidget(Widget):
    def add_Value(self, value):
        self.ids.calculation.text += value

    def clear(self):
        self.ids.calculation.text = ""

    def delete(self):
        calc = self.ids.calculation.text
        self.ids.calculation.text = calc[:len(calc)-1]

    def evaluate(self):
        try:
            result = str(eval(self.ids.calculation.text))
            self.ids.calculation.text = result
        except:
            pass
Code-Sprache: Python (python)

Im obigen Code sieht man eine Klasse namens CalculatorApp. Das Kivy-Framework benötigt diese Klasse, damit das Programm als Kivy-Applikation erkannt und gestartet werden kann. In der main-Funktion konnte man bereits sehen, dass dies die Schnittstelle zur Taschenrechner-App ist. Innerhalb der Klasse implementieren wir nun eine Funktion build , die den Taschenrechner als Widget instanziiert und zurückgibt.

Zudem implementieren wir eine Klasse CalculatorWidget , in der einige Funktionen zur Umsetzung der Taschenrechner-Logik zu finden sind.

Die Funktion add_Value nimmt einen Operanden bzw. eine Operation aus der UI als value entgegen und hängt es an den Term an. Während der Eingabe wird der Term im Layout zwischengespeichert und kann mittels self.ids.calculation.text angesprochen werden.

Die weiteren Funktionen clear löscht den bereits angegebenen Term komplett und delete löscht die letzte Eingabe.

Die letzte Funktion evaluate wertet den eingegebenen Term mithilfe der Python-internen Funktion eval aus und ersetzt den Term durch das Ergebnis.

Das Layout des Taschenrechner ist in der kv-Datei calculator.kv spezifiziert. Bei der Namensgebung der kv-Datei ist folgendes zu beachten: Die Datei muss in Kleinbuchstaben und ohne das Suffix App genauso bezeichnet werden wie die dazugehörige Klasse der App. In diesem Fall haben wir die Taschenrechner-Applikation CalculatorApp genannt, also muss die zugehörige kv-Datei calculator.kv heißen:

<CalculatorWidget>:

    BoxLayout:
        orientation: 'vertical'
        size: root.width, root.height

        #container for expression or result
        BoxLayout:
            height: 70
            size_hint_y: None
            #label for expression or result
            Label:
                id: calculation
                font_size: 50
                text_size: self.size
                halign: "right"
                valign: "middle"

        #container for NumPad
        BoxLayout:
            orientation: 'horizontal'

            #1st column of calculator
            BoxLayout:
                orientation: 'vertical'

                CustomButton:
                    text: "/"
                    on_release: root.add_Value(self.text)
                CustomButton:
                    text: "7"
                    on_release: root.add_Value(self.text)
                CustomButton:
                    text: "4"
                    on_release: root.add_Value(self.text)
                CustomButton:
                    text: "1"
                    on_release: root.add_Value(self.text)
                CustomButton:

            #2nd column of calculator
            BoxLayout:
                orientation: 'vertical'

                CustomButton:
                    text: "*"
                    on_release: root.add_Value(self.text)
                CustomButton:
                    text: "8"
                    on_release: root.add_Value(self.text)
                CustomButton:
                    text: "5"
                    on_release: root.add_Value(self.text)
                CustomButton:
                    text: "2"
                    on_release: root.add_Value(self.text)
                CustomButton:
                    text: "0"
                    on_release: root.add_Value(self.text)

            #3rd column of calculator
            BoxLayout:
                orientation: 'vertical'

                CustomButton:
                    text: "-"
                    on_release: root.add_Value(self.text)
                CustomButton:
                    text: "9"
                    on_release: root.add_Value(self.text)
                CustomButton:
                    text: "6"
                    on_release: root.add_Value(self.text)
                CustomButton:
                    text: "3"
                    on_release: root.add_Value(self.text)
                CustomButton:
                    text: "."
                    on_release: root.add_Value(self.text)

            #4th column of calculator
            BoxLayout:
                orientation: 'vertical'

                CustomButton:
                    text: "+"
                    size_hint_y: 0.2
                    on_release: root.add_Value(self.text)
                CustomButton:
                    text: "C"
                    size_hint_y: 0.2
                    on_release: root.clear()
                CustomButton:
                    text: "DEL"
                    size_hint_y: 0.2
                    on_release: root.delete()
                CustomButton:
                    text: "="
                    size_hint_y: 0.4
                    on_release: root.evaluate()

<CustomButton@Button>:
    font_size: 35
Code-Sprache: Python (python)

Charakteristisch für eine kv-Datei ist die hierarchische Anordnung der Komponenten in einer Baumstruktur. Dabei muss auf die Einrückung der Elemente geachtet werden.

In Zeile 1 sieht man, dass das Layout für die Klasse CalculatorWidget spezifiziert werden soll.

Kivy bietet eine Vielzahl an Layoutmanagern, die auch in vielen anderen GUI-Toolkits vorkommen. Unter anderem finden sich bekannte Container wie z.B. AnchorLayout, FloatLayout oder GridLayout wieder.

Für den Taschenrechner wählen wir das BoxLayout aus. Das bietet uns den Vorteil, mehrere Widgets unter- oder nebeneinander darstellen zu können. Somit ist es gut zur Erstellung des NumPads geeignet.

Für jede Kivy-Komponente (Widget) können wir die zugehörigen Properties konfigurieren. In den Zeilen 4 und 5 passen wir für den übergeordneten BoxLayout-Container die beiden Properties orientation und size an. Der Code orientation: 'vertical' bedeutet, dass die Komponenten innerhalb des Containers untereinander angeordnet werden sollen. Die Größe des Containers wird mithilfe des Codes size: root.width, root.height während der Laufzeit dynamisch an das Fenster angepasst. Es werden 2 Komponenten untereinander dargestellt: Ein Label zur Anzeige des Terms bzw. des Ergebnisses (Zeilen 7-17) und das NumPad für die Eingabe (Zeilen 19-100).

In den Zeilen 7-17 definieren wir für die Visualisierung des Terms ein Label, das ebenfalls von einem BoxLayout-Container umschlossen wird. In der Label-Spezifikation werden die Properties für die Textausrichtung und -größe, sowie die id gesetzt. Mithilfe der id kann die Komponente gezielt angesprochen werden.

In den Zeilen 19-100 implementieren wir das Layout für das NumPad. Hierbei spezifizieren wir für jede Spalte des Taschenrechners ein eigenes BoxLayout.

Auch für jede Taste des Numpads spezifizieren wir einen Button. In den Zeilen 27-29 wird bspw. der Button zur Eingabe der Divisions-Operation implementiert. Es handelt sich hierbei um eine eigene Komponente CustomButton, die von der Kivy-Komponente Button erbt. Hierbei wird zum einen mit text: "/" der anzuzeigende Text des Buttons definiert und zum anderen wird mit on_release: root.add_Value(self.text) das Event nach Knopfdruck implementiert. In diesem Fall verweist root auf die Wurzelkomponente CalculatorWidget und es wird nach Druck des Buttons die in der Python-Klasse CalculatorWidget implementierte Funktion add_Value mit dem Argument "/" aufgerufen. Die anderen Buttons werden analog dazu spezifiziert.

Eine Besonderheit von Kivy ist es, eigene Komponenten zu spezifizieren. So haben wir in diesem Beispiel einen eigenen Button CustomButton spezifiziert. Mithilfe des Codes CustomButton@Button erbt die Komponente alle Eigenschaften des Button-Widgets. Innerhalb der eigenen Komponente lassen sich die Properties beliebig anpassen. In diesem Fall haben wir die Textgröße angepasst, sodass wir nicht für jeden Button des NumPads denselben Code zur Anpassung der Textgröße schreiben müssen.

Der gesamte Code des Beispiels ist auch auf GitHub zu finden.

Fazit

Dieser Wissensbeitrag stellt eine Einführung in das Python-basierte GUI-Framework Kivy dar. Dabei haben wir die grundlegenden Eigenschaften von Kivy vermittelt und erläutert, warum sich Kivy von anderen GUI-Toolkits abhebt. Das abschließende Praxisbeispiel hat gezeigt, wie einfach es ist, grafische Benutzeroberflächen mit Kivy zu entwerfen und mit passender Python-Logik in Verbindung zu bringen.

Du hast Fragen oder möchtest weitere Infos über Kivy / Python? Dann nutze gerne unser Kontaktformular unten:

Kontaktformular

    Das könnte Dich auch noch interessieren

    Die Kombination von CQRS und Event Sourcing in Java

    Die Kombination von CQRS und Event Sourcing in Java

    In diesem Blogbeitrag wird gezeigt wie die Pattern CQRS und Event Sourcing zusammenspielen und warum sie zusammen gehören ...
    Die Anatomie von CQRS in Java

    Die Anatomie von CQRS in Java

    Aufgrund meiner festen Überzeugung, dass man Patterns am besten lernt, wenn man sie zunächst einmal selbst implementiert hat, erläutere ich ...
    Jenkins, Jenkins: Don't Repeat Yourself (DRY)!

    Jenkins, Jenkins: Don’t Repeat Yourself (DRY)!

    In modernen Microservice-Architekturen wird Software vollautomatisch über CI/CD-Pipelines ausgeliefert. Wie lassen sich diese Pipelines bei wachsender Komplexität und steigender Anzahl ...