pyqt
Qt ist eine professionelle Bibliothek für die platformübergreifende Erstellung von Anwendungen - insbesondere von Desktop-Awendungen. Mit pyqt existiert eine Python Schnittstelle.
Installation - Qt
Die Installation von Qt erfolgt über einen Download der Open-Source Variante der Software von der Webseite.
Installation - pyqt
pyqt kann über pip installiert werden.
- Windows:
pip install pyqt5
oderpython -m pip install pyqt5
oderpy -m pip install pyqt5
- Linux, MacOS:
pip3 install pyqt5
Einfache Anwendung
Eine erste einfach Anwendung kann mit wenigen Zeilen Quelltext schnell erstellt werden.
from PyQt5.QtWidgets import QApplication, QLabel
app = QApplication([])
label = QLabel('Hallo Welt!')
label.show()
app.exec_()
print("finished")
finished
Die Klasse QApplication
stellt die Hauptanwendung dar. Sie erhält Parameter als Übergabewert. In diesem Fall eine leere Liste, wenn es keine Parameter gibt.
Interaktion mit einem Button
Nun soll etwas Leben ins Spiel kommen und eine Anwendung entstehen, die einen Button enthält. Damit der Button etwas tut, wenn man auf ihn drückt, müssen wir ihn mit einer Methode verknüpfen. Für die Verknüpfung von Ereignissen mit Aktionen nutzt Qt “Signals” (die Ereignisse) und “Slots” als Methoden, die auf die Ereignisse reagieren.
from PyQt5.QtWidgets import QApplication, QPushButton, QMessageBox
app = QApplication([])
button = QPushButton('Click mich')
def on_button_click():
alert = QMessageBox()
alert.setText('Du hast den Button gefunden')
alert.exec_()
button.clicked.connect(on_button_click) # connect signal clicked with method
button.show()
app.exec_()
0
QtCreator und QtDesigner
Das Erstellen einer GUI ist in Qt besonders einfach, da ein mächtiger GUI-Editor mitgeliefert wird. Mit ihm kann die GUI erstellt und als ui-Datei (ui=user interface) gespeichert werden.
Neues Design erstellen
Um ein neues GUI-Design erstellen zu können, wähle im Qt-Creator
- Neu
- Qt
- Qt-Designer-Formular
Erstelle nun zwei PushButton, einen TableView und ein Label mit den Namen btn_submit, btn_add_row, tableView und lbl_status. Die fertige Datei hat den Namen mainwindow.ui. Es handelt sich um eine XML-Datei, die von pyqt eingelesen und in Python-Quelltext umgewandelt werden kann.
file mainwindow.ui
mainwindow.ui: XML 1.0 document text, ASCII text
Um aus einer ui-Datei Python-Quelltext zu erzeugen, wird das Kommandozeilentool pyuic5
mitgeliefert (uic=user interface compiler).
pyuic5 -x -o mainwindow.py mainwindow.ui
Der Parameter -o
bestimmt den Name der Ausgabedatei. Durch -x
wird zusätzlich ein kleines Rahmenprogramm generiert, womit die GUI schnell angezeigt werden kann.
python mainwindow.py
QApplication: invalid style override passed, ignoring it.
Die generierte Datei sieht wie folgt aus.
cat mainwindow.py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_mainWindow(object):
def setupUi(self, mainWindow):
mainWindow.setObjectName("mainWindow")
mainWindow.resize(475, 381)
self.centralWidget = QtWidgets.QWidget(mainWindow)
self.centralWidget.setObjectName("centralWidget")
self.btn_submit = QtWidgets.QPushButton(self.centralWidget)
self.btn_submit.setGeometry(QtCore.QRect(10, 300, 85, 27))
self.btn_submit.setObjectName("btn_submit")
self.tableView = QtWidgets.QTableView(self.centralWidget)
self.tableView.setGeometry(QtCore.QRect(0, 0, 471, 291))
self.tableView.setSortingEnabled(True)
self.tableView.setObjectName("tableView")
self.lbl_status = QtWidgets.QLabel(self.centralWidget)
self.lbl_status.setGeometry(QtCore.QRect(200, 300, 251, 20))
self.lbl_status.setObjectName("lbl_status")
self.btn_add_row = QtWidgets.QPushButton(self.centralWidget)
self.btn_add_row.setGeometry(QtCore.QRect(100, 300, 31, 27))
self.btn_add_row.setObjectName("btn_add_row")
mainWindow.setCentralWidget(self.centralWidget)
self.menuBar = QtWidgets.QMenuBar(mainWindow)
self.menuBar.setGeometry(QtCore.QRect(0, 0, 475, 27))
self.menuBar.setObjectName("menuBar")
mainWindow.setMenuBar(self.menuBar)
self.mainToolBar = QtWidgets.QToolBar(mainWindow)
self.mainToolBar.setObjectName("mainToolBar")
mainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.mainToolBar)
self.statusBar = QtWidgets.QStatusBar(mainWindow)
self.statusBar.setObjectName("statusBar")
mainWindow.setStatusBar(self.statusBar)
self.retranslateUi(mainWindow)
QtCore.QMetaObject.connectSlotsByName(mainWindow)
def retranslateUi(self, mainWindow):
_translate = QtCore.QCoreApplication.translate
mainWindow.setWindowTitle(_translate("mainWindow", "Sql Demo Anwendung"))
self.btn_submit.setText(_translate("mainWindow", "Submit"))
self.lbl_status.setText(_translate("mainWindow", "Status: OK"))
self.btn_add_row.setText(_translate("mainWindow", "+"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = QtWidgets.QMainWindow()
ui = Ui_mainWindow()
ui.setupUi(mainWindow)
mainWindow.show()
sys.exit(app.exec_())
Eine komplexere Anwendung mit Datenbank-Anbindung
Probieren wir uns nun an einer komplexeren Anwendung, die auf eine Datenbank zugreift, sie darstellt und eine Änderung der Daten ermöglicht.
Erstellen einer Datenbank
Zunächst erstellen wir eine sqlite-Datenbank und darin eine Tabelle Person mit den Attributen id, name, geschlecht und gebjahr. Falls eine solche Datenbank schon vorhanden ist, löschen wir sie vorsorglich.
rm db.sqlite
Wir legen ein paar Konstanten fest, die für die gesamte Anwendung gelten sollen. Der Name der Datenbank, die Anzahl der zufällig zu erzeugenden Daten, eine Auswahl von Namen, die zufällig gewählt werden und das CREATE-Statement für die Erstellung der Tabelle person.
Das CREATE enthält zusätlich einen CONSTRAINT, der für das Geschlecht nur die Werte ‘w’, ‘m’ und ‘x’ zulässt.
DB_FILE = 'db.sqlite'
NUM_INIT_DATA = 10
NAMES = ['Peter', 'Susi', 'Moni', 'Max']
SQL_CREATE = '''
CREATE TABLE IF NOT EXISTS person
(
id integer primary key autoincrement,
name text not null,
geschlecht text not null,
gebjahr integer not null,
CONSTRAINT check_correct_val CHECK (geschlecht in ('w', 'm', 'x'))
)
'''
Nun erstellen wir die Datenbank und füllen sie mit Beispieldaten.
import sqlite3
import random
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
c.execute(SQL_CREATE)
for id in range(NUM_INIT_DATA):
gebjahr = random.randint(1950, 2010)
name = random.choice(NAMES)
geschlecht = random.choice(['m', 'w', 'x'])
c.execute('''INSERT INTO person (id,name,geschlecht,gebjahr)
VALUES (?,?,?,?)''',
(id, name, geschlecht, gebjahr))
conn.commit()
conn.close()
Mit sqlite3 können die Daten bereits auf der Kommandozeile ausgelesen werden.
sqlite3 --column --header db.sqlite 'SELECT * FROM person'
id name geschlecht gebjahr
---------- ---------- ---------- ----------
0 Susi m 1965
1 Peter x 1993
2 Moni m 2009
3 Peter m 1967
4 Susi w 1961
5 Max m 1950
6 Moni m 1952
7 Moni w 2009
8 Max w 1986
9 Max x 1954
Wir erstellen nun die Klasse MainWindow
, die von der Klasse Ui_mainWindow
erbt - letzte stammt aus der generierten Datei mainwindow.py
. Wir ändern nichts in der generierten Datei, damit spätere Änderungen an der GUI und ein anschließendes neues Erzeugen unsere Mühen nicht kaputt machen.
Die Daten für den TableView werden aus einem QSqlTableModel geladen. Dieses Modell stellt Daten aus einer Datenbank zur Verfügung.
import sys
import random
import sqlite3
import mainwindow
from PyQt5.QtWidgets import QApplication, QDialog, QMainWindow
from PyQt5.QtSql import QSqlTableModel, QSqlDatabase
class MainWindow(mainwindow.Ui_mainWindow):
def __init__(self, dbfile):
self.dbfile = dbfile
def setupUi(self, mainwindow):
super().setupUi(mainwindow)
# connect signals with methods
self.btn_submit.clicked.connect(self.submit_clicked)
self.btn_add_row.clicked.connect(self.add_row_clicked)
# create database object for sqlite-database
db = QSqlDatabase.addDatabase('QSQLITE')
db.setDatabaseName(self.dbfile)
# the table model will show the data from the db in the tableview
self.tablemodel = QSqlTableModel()
self.tablemodel.setTable('person')
self.tablemodel.setEditStrategy(QSqlTableModel.OnManualSubmit)
self.tablemodel.select() # populate model with data
self.tableView.setModel(self.tablemodel)
self.tableView.hideColumn(0) # hide ids
def submit_clicked(self):
'Write changes into the DB when submit button is clicked.'
succ = self.tablemodel.submitAll()
self.lbl_status.setText("Daten erfolgreich gespeichert? %s" % succ)
def add_row_clicked(self):
'Add new row to the table when +-Button is clicked.'
self.tablemodel.insertRows(self.tablemodel.rowCount(), 1)
Schließlich erzeugen wir eine neue Instanz der Klasse und starten das Programm.
app = QApplication([])
window = QMainWindow()
ui = MainWindow(DB_FILE)
ui.setupUi(window)
window.show()
app.exec_()
Eine Verbindung zwischen Tabellen, wie sie bei 1-n-Beziehungen auftritt, kann über ein anderes TableModel realisiert werden: das QSqlRelationalTableModel.
Deployment
Nachdem die Awendung erstellt wurde, soll sie natürlich auch auf anderen Rechnern laufen können. Um ein Programmversion zu erstellen, die alle notwendigen Komponenten enthält, bringt pyqt das Programm pyqtdeploy
und pyqtdeploy-build
mit. Deren Bedienung ist leider etwas komplizierter. Daher stelle ich an dieser Stelle die Verwendung von fbs vor.
Installation - fbs
Die Installation erfolgt wieder mit pip. Der Paketname lautet fbs: pip install fbs
. Danach steht ein Modul fbs zur Verfügung.
python -m fbs
usage: python -m fbs [-h] {run,installer,clean,test,freeze,startproject} ...
fbs
positional arguments:
{run,installer,clean,test,freeze,startproject}
run Run your app from source
installer Create an installer for your app
clean Remove previous build outputs
test Execute your automated tests
freeze Compile your application to a standalone executable
startproject Start a new fbs project in the current directory
optional arguments:
-h, --help show this help message and exit
fbs-Projekt erstellen, ausführen, einfrieren
Mit dem Kommando startproject
wird ein Beispielprojekt erstellt.
python -m fbs startproject
Nach Angabe eines Projektnamens wird ein Verzeichnis src
mit einem Minimalprojekt erstellt.
tree -d src
src
├── build
│ └── settings
└── main
├── icons
│ ├── base
│ ├── linux
│ └── mac
├── NSIS
├── python
│ └── __pycache__
└── resources
└── mac-frozen
└── Contents
13 directories
Wir konzenrtrieren uns an dieser Stelle auf den Ordner src/main/python
. In diesem befindet sich die Datei main.py
, die das Hauptprogramm enthält. Die anderen Ordner bieten Konfigurationsmöglichkeiten für verschiedene Zielplattformen.
tree src/main/python
src/main/python
├── main.py
└── __pycache__
└── main.cpython-35.pyc
1 directory, 2 files
Die Datei main.py
ersetzen wird nun durch unser Beispielprogramm.
cp mainwindow.py src/main/python/main.py
Mit dem Befehl run
kann das Porgramm testweise gestartet werden.
python -m fbs run
QApplication: invalid style override passed, ignoring it.
Das Fenster öffnet sich und die Anwendung kann getestet werden. Mit dem Befehl freeze
wird die Anwendung eingefrorern. Das bedeutet, dass alle benötigten Bibliotheken in einem Verzeichnis gesammelt werden.
python -m fbs freeze
3788 WARNING: Hidden import "sip" not found!
3789 WARNING: Hidden import "sip" not found!
4118 WARNING: Hidden import "sip" not found!
4193 WARNING: Hidden import "sip" not found!
4195 WARNING: Hidden import "sip" not found!
4252 WARNING: Hidden import "sip" not found!
Im Ordner target
werden die eingefrorenen Anwendungen abglegt.
tree -d target/MainWindow
target/MainWindow
└── PyQt5
└── Qt
└── plugins
├── iconengines
├── imageformats
├── platforms
├── platformthemes
└── printsupport
8 directories
Dieser Ordner target/MainWindow
- allgemein target/PROJKETNAME
- kann auf den Zielrechner kopiert werden und enthällt alle benötigten Dateien. Darin befindet sich eine Datei MainWindow
(unter Linux) oder MainWindow.exe
, die ausgeführt werden kann.