pyqt

Einfache Softwarelösungen für verschiedene Probleme

Download .zip Download .tar.gz

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 oder python -m pip install pyqt5 oder py -m pip install pyqt5
  • Linux, MacOS: pip3 install pyqt5

Einfache Anwendung

Eine erste einfach Anwendung kann mit wenigen Zeilen Quelltext schnell erstellt werden.

Hallo Welt

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.

click mich

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

  1. Neu
  2. Qt
  3. Qt-Designer-Formular

qt-creator

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.

anwendung

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.