Kleine Einführung in PyQt5 und QML

Während meiner Bachelorarbeit hat mich eines wirklich Nerven gekostet: Dass es einfach keine vernünftigen Beispiele mit PyQt5 und QML im Netz gibt (oder zumindest zum Zeitpunkt meines Schreibens gab). Nachdem ich mir dann stundenlang tagelang alle möglichen Minibeispiele aus den hintersten Ecken irgendwelcher Foren zusammen gesucht habe, kam mir die Idee nach Abschluss meiner Arbeit dann doch einmal ein kleines Tutorial dazu zu schreiben. Auf dass die Leser dieses Artikels nicht dieselbe Anzahl an grauen Haaren entwickeln, wie ich es einst während des Schreibens meiner Thesis tat (Naja, ich habe nicht wirklich graue Haare bekommen...das soll an dieser Stelle nur symbolisch verstanden werden). Besonders nervig war, dass ich keine guten Beispiele gefunden habe, wie ich die Darstellung meiner QML-Applikation zur Laufzeit ändern konnte. Auch dazu werde ich eine Lösung in diesem Tutorial vorstellen. Es kann gut sein, dass der eine oder andere Leser Verbesserungsvorschläge für die hier vorgestellten Lösungen anbieten möchte. Dazu verweise ich wie immer, auf die Kommentarfunktion.
Ich stelle in diesem Artikel einige Beispiele vor, welche Python3, PyQt5 und Qt5 erfordern.

Kleines Basis-Beispiel

Wir beginnen mit einem kleinen Beispiel, welches in QML geschrieben wird:

import QtQuick 2.5
import QtQuick.Controls 1.2
ApplicationWindow {
    width: 500; height: 500;
    objectName: "MainWindow";
    id: applicationwindow01;

    Rectangle {
        width: 250; height: 250;
        objectName: "Rectangle1";
        color: "red";
        id: rectangle01;
    }

    Rectangle {
        width: 250; height: 250;
        objectName: "Rectangle2";
        color: "green";
        id: rectangle02;
        anchors.left: rectangle01.right;
        anchors.top: rectangle01.bottom;
    }
}

Die QML-Typen lassen sich durch das Import-Statement über QtQuick 2.5 einbinden.  QtQuick.Controls benötigen wir, um Objekte vom Typ ApplicationWindow darstellen zu können. Die Größe unseres ApplicationWindow-Objekt wird über die Angaben der width- und height-Properties festgelegt. Die ID wird benötigt, um das Objekt innerhalb der QML-Umgebung anzusprechen und den objectName legen wir fest, um das Objekt später über Python ansprechen zu können. Unser ApplicationWindow enthält zudem zwei Rechtecke (Objekte vom Typ Rectangle). Die Rechtecke enthalten ebenfalls Größenangaben, sowie eine ID und einen Objektnamen. Zusätzlich erhalten die Rechtecke zur visuellen Unterscheidung Farbwerte. Das zweite Rechteck erhält zusätzlich sogenannte Anker-Eigenschaften. Objekte können in QML mit anderen Objekten verankert werden, um diese zu positionieren. In unserem Beispiel befindet sich Das 2. Rechteck rechts und unterhalb des ersten Rechtecks. Weitere Möglichkeiten zur Verankerung werden in der Qt-Dokumentation beschrieben. Der Code wird in die Datei beispiel.qml geschrieben, und kann wie folgt aufgerufen werden:

qmlscene beispiel.qml

Die Ausgabe entspricht dann Abbildung 1.

Ausgabe der QML-Datei
Abbildung 1: Ausgabe des QML-Beispiels über qmlscene.

Ausgabe über Python und PyQt5

Um das ganze jetzt über Python ausgeben zu lassen, nutzen wir in diesem Beispiel PyQt5. Hierzu nutzen wir folgendes Minimalbeispiel, welches sich im selben Unterordner wie die beispiel.qml-Datei befindet:

import sys
from PyQt5.QtCore import QUrl, Qt, pyqtSlot
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5 import QtQuick

class QmlAusgabe(object):
    def __init__(self, pathToQmlFile="beispiel.qml"):

        #QML-Engine
        self.__appEngine = QQmlApplicationEngine()
        self.__appEngine.load(pathToQmlFile)

        self.__appWindow = self.__appEngine.rootObjects()[0]

    def show(self):
        self.__appWindow.show()

if __name__ == "__main__":
    appQueue = QApplication(sys.argv)
    qdm = QmlAusgabe()
    qdm.show()
    sys.exit(appQueue.exec_())

 

Das Beispiel wirkt im ersten Moment nicht ganz trivial, allerdings ermöglicht es uns später komfortabel die QML-Oberfläche zur Laufzeit zu verändern. Wir bauen uns hierzu eine Klasse, welche eine Instanz der QQmlApplicationEngine-Klasse besitzt. Diese wird benötigt, um eine QML-Datei zu instanzieren (Natürlich wird dabei der Inhalt der Datei instanziert). Mittels der load()-Methode der Engine-Instanz lässt sich unsere QML-Datei instanzieren. Dem Attribut appWindow weisen wir die QML-Instanz (also unser geschriebenes ApplicationWindow) zu. Darüber können wir später dann auch alle anderen Objekte ansprechen. Da wir das Attribut als private deklariert haben, bauen wir uns eine eigene show()-Methode. Hierüber lässt sich dann die QML-Applikation darstellen.
Im Hauptprogramm brauchen wir die QApplication-Klasse, welche den GUI-Fluss kontrolliert. Die Ausgabe erfolgt dann über den Aufruf der show()-Methode einer Instanz unserer QmlAusgabe-Klasse.

Änderung von Eigenschaften der QML-Objekte

Als nächstes werden wir die Farben der Rechtecke vor dem Aufruf der show()-Methode ändern. Hierzu wird unsere QmlAusgabe-Klasse um die Method changeColor erweitert und die beiden Rechtecke als Klassen-Attribut ergänzt:

import sys
from PyQt5.QtCore import QUrl, Qt, pyqtSlot, QObject
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5 import QtQuick

class QmlAusgabe(object):
def __init__(self, pathToQmlFile="beispiel.qml"):
        #QML-Engine
        self.__appEngine = QQmlApplicationEngine()
        self.__appEngine.load(pathToQmlFile)
        self.__appWindow = self.__appEngine.rootObjects()[0]

        self.__rectangle1 = self.__appWindow.findChild(QObject, "Rectangle1")
        self.__rectangle2 = self.__appWindow.findChild(QObject, "Rectangle2")

    def changeColor(self):
        self.__rectangle1.setProperty("color", "yellow")
        self.__rectangle2.setProperty("color", "blue")

    def show(self):
        self.__appWindow.show()

if __name__ == "__main__":
    appQueue = QApplication(sys.argv)
    qdm = QmlAusgabe()
    qdm.changeColor()
    qdm.show()
    sys.exit(appQueue.exec_())

Hierbei sollte beachtet werden, dass wir außerdem die Klasse QObject importieren. Um die beiden Rechteck-Objekte als Attribut zu speichern, suchen wir diese über die im QML-Code festgelegten Objektnamen. Anschließend werden die neuen Farben über den Aufruf der setProperty()-Methode in der changeColor()-Methode zugewiesen. Die Ausgabe entspricht dann Abbildung 2.

QML-Ausgabe über Python nach Änderung der Farben.
QML-Ausgabe über Python nach Änderung der Farben.

Änderung der Farben zur Laufzeit

Interessant wird es, wenn man die Rechtecke zuerst in der Farbe darstellen möchte, welche im QML-Code festgelegt wurden und sich diese dann erst nach 5 Sekunden ändern sollen. Der erste naive Ansatz wäre wohl, das Main-Programm wie folgt zu schreiben:

if __name__ == "__main__":
    appQueue = QApplication(sys.argv)
    qdm = QmlAusgabe()
    qdm.show()
    sleep(5)
    qdm.changeColor()
    sys.exit(appQueue.exec_())

 

Leider wird schnell klar, dass das nicht funktioniert. Wir machen hierzu lieber von einem QML-Timer gebrauch. Dazu modifizieren wir den QML-Code wie folgt:

import QtQuick 2.5
import QtQuick.Controls 1.2

ApplicationWindow {
    width: 500; height: 500;
    objectName: "MainWindow";
    id: applicationwindow01;

    signal timerSignal();

    Rectangle {
        width: 250; height: 250;
        objectName: "Rectangle1";
        color: "red";
        id: rectangle01;
    }

    Rectangle {
        width: 250; height: 250;
        objectName: "Rectangle2";
        color: "green";
        id: rectangle02;
        anchors.left: rectangle01.right;
        anchors.top: rectangle01.bottom;
    }

    Timer {
        interval: 1000;
        running: true;
        repeat: true;
        onTriggered: timerSignal()
    }
}


Hierbei wird alle 1000ms (Spricht: jede Sekunde) das Signal timerSignal() emittiert. Wenn wir das Signal jetzt an einen pyqtSlot koppeln, wird die im PyQt-Slot gesetzte Methode jede Sekunde aufgerufen:

import sys
from time import sleep
from PyQt5.QtCore import QUrl, Qt, pyqtSlot, QObject
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5 import QtQuick

class QmlAusgabe(object):
    def __init__(self, pathToQmlFile="beispiel.qml"):


        #QML-Engine
        self.__appEngine = QQmlApplicationEngine()
        self.__appEngine.load(pathToQmlFile)
        self.__appWindow = self.__appEngine.rootObjects()[0]

        self.__rectangle1 = self.__appWindow.findChild(QObject, "Rectangle1")
        self.__rectangle2 = self.__appWindow.findChild(QObject, "Rectangle2")

        self.__changed = False
        #Connect Signal to Slot
        self.__appWindow.timerSignal.connect(self.changeColor)

    @pyqtSlot()
    def changeColor(self):
            if(self.__changed == False):
                self.__rectangle1.setProperty("color", "yellow")
                self.__rectangle2.setProperty("color", "blue")
                self.__changed = True
            else:
                self.__rectangle1.setProperty("color", "red")
                self.__rectangle2.setProperty("color", "green")
                self.__changed = False


    def show(self):
        self.__appWindow.show()

if __name__ == "__main__":
    appQueue = QApplication(sys.argv)
    qdm = QmlAusgabe()
    qdm.show()
    sys.exit(appQueue.exec_())

Schreibe einen Kommentar