Rheinwerk Computing < openbook >

 
Inhaltsverzeichnis
1 Einleitung
2 Die Programmiersprache Python
Teil I Einstieg in Python
3 Erste Schritte im interaktiven Modus
4 Der Weg zum ersten Programm
5 Kontrollstrukturen
6 Dateien
7 Das Laufzeitmodell
8 Funktionen, Methoden und Attribute
9 Informationsquellen zu Python
Teil II Datentypen
10 Das Nichts – NoneType
11 Operatoren
12 Numerische Datentypen
13 Sequenzielle Datentypen
14 Zuordnungen
15 Mengen
16 Collections
17 Datum und Zeit
18 Aufzählungstypen – Enum
Teil III Fortgeschrittene Programmiertechniken
19 Funktionen
20 Modularisierung
21 Objektorientierung
22 Ausnahmebehandlung
23 Iteratoren
24 Kontextobjekte
25 Manipulation von Funktionen und Methoden
Teil IV Die Standardbibliothek
26 Mathematik
27 Kryptografie
28 Reguläre Ausdrücke
29 Schnittstelle zu Betriebssystem und Laufzeitumgebung
30 Kommandozeilenparameter
31 Dateisystem
32 Parallele Programmierung
33 Datenspeicherung
34 Netzwerkkommunikation
35 Debugging und Qualitätssicherung
36 Dokumentation
Teil V Weiterführende Themen
37 Anbindung an andere Programmiersprachen
38 Distribution von Python-Projekten
39 Grafische Benutzeroberflächen
40 Python als serverseitige Programmiersprache im WWW – ein Einstieg in Django
41 Wissenschaftliches Rechnen
42 Insiderwissen
43 Von Python 2 nach Python 3
A Anhang
Stichwortverzeichnis

Download:
- Beispielprogramme, ca. 464 KB

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Python 3 von Johannes Ernesti, Peter Kaiser
Das umfassende Handbuch
Buch: Python 3

Python 3
Pfeil 39 Grafische Benutzeroberflächen
Pfeil 39.1 Toolkits
Pfeil 39.2 Einführung in tkinter
Pfeil 39.2.1 Ein einfaches Beispiel
Pfeil 39.2.2 Steuerelementvariablen
Pfeil 39.2.3 Der Packer
Pfeil 39.2.4 Events
Pfeil 39.2.5 Steuerelemente
Pfeil 39.2.6 Zeichnungen – das Canvas-Widget
Pfeil 39.2.7 Weitere Module
Pfeil 39.3 Einführung in PyQt
Pfeil 39.3.1 Installation
Pfeil 39.3.2 Grundlegende Konzepte von Qt
Pfeil 39.3.3 Entwicklungsprozess
Pfeil 39.4 Signale und Slots
Pfeil 39.5 Wichtige Widgets
Pfeil 39.5.1 QCheckBox
Pfeil 39.5.2 QComboBox
Pfeil 39.5.3 QDateEdit, QTimeEdit, QDateTimeEdit
Pfeil 39.5.4 QDialog
Pfeil 39.5.5 QLineEdit
Pfeil 39.5.6 QListWidget, QListView
Pfeil 39.5.7 QProgressBar
Pfeil 39.5.8 QPushButton
Pfeil 39.5.9 QRadioButton
Pfeil 39.5.10 QSlider, QDial
Pfeil 39.5.11 QTextEdit
Pfeil 39.5.12 QWidget
Pfeil 39.6 Zeichenfunktionalität
Pfeil 39.6.1 Werkzeuge
Pfeil 39.6.2 Koordinatensystem
Pfeil 39.6.3 Einfache Formen
Pfeil 39.6.4 Grafiken
Pfeil 39.6.5 Text
Pfeil 39.6.6 Eye Candy
Pfeil 39.7 Model-View-Architektur
Pfeil 39.7.1 Beispielprojekt: ein Adressbuch
Pfeil 39.7.2 Auswählen von Einträgen
Pfeil 39.7.3 Bearbeiten von Einträgen
 
Zum Seitenanfang

39.7    Model-View-Architektur Zur vorigen ÜberschriftZur nächsten Überschrift

Mit der Version 4 wurde die Model-View-Architektur in das Qt-Framework eingeführt. Die grundlegende Idee dieser Art der Programmierung ist es, Form und Inhalt voneinander zu trennen. Bezogen auf Qt bedeutet das, dass Klassen, die Daten enthalten, von Klassen getrennt werden sollen, die diese Daten anzeigen. So gibt es eine Model-Klasse, die ein bekanntes Interface für die gespeicherten Daten bereitstellt, und eine View-Klasse, die über die Model-Klasse auf die Daten zugreift und diese auf der grafischen Oberfläche anzeigt. Es wird nicht vorausgesetzt, dass die Daten tatsächlich in der Model-Klasse gespeichert sind, sondern nur, dass die Model-Klasse Methoden bereitstellt, um auf die Daten zuzugreifen. Die Daten selbst können in einer Datenbank oder Datei stehen.

Das Aufteilen der Programmlogik in Model- und View-Klassen hat den Vorteil, dass das Programm insgesamt einfacher und besser strukturiert wird. Außerdem führen Änderungen in der Datenrepräsentation nicht dazu, dass die Anzeigeklasse angepasst werden muss. Umgekehrt ist es der Model-Klasse egal, in welcher Form die von ihr bereitgestellten Daten am Bildschirm angezeigt werden.

Das Verhältnis zwischen Model- und View-Klasse wird in Abbildung 39.50 veranschaulicht.

Das Qt-Framework bietet einige Klassen, die dem Programmierer beim Erstellen einer Model-View-Architektur helfen. Darunter finden sich Basisklassen sowohl für die Model- als auch für die View-Klassen. Im Folgenden besprechen wir eine rudimentäre Adressbuch-Applikation als Beispiel für eine Model-View-Architektur.

Die Model-View-Architektur

Abbildung 39.50    Die Model-View-Architektur

 
Zum Seitenanfang

39.7.1    Beispielprojekt: ein Adressbuch Zur vorigen ÜberschriftZur nächsten Überschrift

In diesem Abschnitt zeigen wir Ihnen einen praxisorientierten Einstieg in die Programmierung einer Model-View-Architektur anhand eines einfachen Beispielprogramms. Dazu dient ein grafisches Adressbuch, das beim Starten mehrere Adresssätze aus einer Textdatei einliest und dann grafisch auf dem Bildschirm darstellt. Intern werden die Datensätze durch eine Model-Klasse eingelesen und aufbereitet. Eine View-Klasse kümmert sich dann um die Anzeige der Daten.

Im ersten Schritt konzentrieren wir uns dabei auf das Einlesen und Anzeigen der Daten. Danach werden durch Erweiterungen des Beispielprogramms weitere Aspekte der Model-View-Programmierung in Qt vorgestellt. Die vorläufige Anwendung, die in diesem Abschnitt entwickelt wird, ist in Abbildung 39.51 dargestellt.

Ein Adressbuch

Abbildung 39.51    Ein Adressbuch

Die Adressdaten sollen aus einer Datei des folgenden Formats ausgelesen werden:

Donald Duck
don@ld.de
Pechvogelgasse 13
12345 Entenhausen
01234/313

Dagobert Duck
d@gobert.de
Geldspeicherweg 42
12345 Entenhausen
0190/123456
[…]

Die Adressdaten sind also zeilenweise in einer Datei gespeichert. Zwei Einträge im Adressbuch werden durch eine Leerzeile in der Quelldatei voneinander getrennt. Abgesehen davon, dass der Name der Person, zu der der Eintrag gehört, in der ersten Zeile des Eintrags steht, gibt es keine weiteren Anforderungen an die Formatierung der Daten.[ 199 ](Tatsächlich ist das Dateiformat für den vorgestellten Verwendungszweck eher ungeeignet, da es beispielsweise für das Programm, das die Datei einliest, keine effiziente Möglichkeit gibt, die einzelnen Teilinformationen des Eintrags zuzuordnen, beispielsweise also die E‐Mail-Adresse herauszufiltern. Das Dateiformat wird hier jedoch aufgrund seiner Einfachheit verwendet. )

Das Adressbuch stellt eine Beispiel-Implementation für eine Model-View-Architektur dar:

  • Die Model-Klasse hat die Aufgabe, die Quelldatei mit den Adressdaten einzulesen und eine Schnittstelle bereitzustellen, über die auf diese Daten zugegriffen werden kann.
  • Die View-Klasse greift auf die in der Model-Klasse gespeicherten Daten zu und präsentiert diese dann in geeigneter Form auf dem Bildschirm. Da es sich bei dem Adressbuch im Prinzip um eine Liste von Adresseinträgen handelt, können wir hier auf die Basisklasse QListView des Qt-Frameworks zurückgreifen, die die grundlegende Funktionalität zum Anzeigen von Modelldaten mit Listenstruktur bereitstellt. Lägen die Daten in einer anderen Struktur vor, könnten die Basisklassen QTreeView oder QTableView verwendet werden, die eine baumartige bzw. tabellarische Struktur der Daten visualisieren.

Abbildung 39.52 stellt die Programmstruktur grafisch dar.

Die Model-View-Architektur des Beispielprogramms

Abbildung 39.52    Die Model-View-Architektur des Beispielprogramms

Der Quellcode der Model-Klasse befindet sich in der Programmdatei modell.py und sieht folgendermaßen aus:

from PyQt5 import QtCore
class Modell(QtCore.QAbstractListModel):
def __init__(self, dateiname, parent=None):
super().__init__(parent)
self.datensatz = []
# Lade Datensatz
with open(dateiname) as f:
lst = []
for zeile in f:
if not zeile.strip():
self.datensatz.append(lst)
lst = []
else:
lst.append(zeile.strip())
if lst:
self.datensatz.append(lst)
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.datensatz)
def data(self, index, role=QtCore.Qt.DisplayRole):
return self.datensatz[index.row()]

Es wird die Model-Klasse Modell definiert, die von der Basisklasse QtCore.QAbstractListModel erbt. Diese Basisklasse implementiert die grundlegende Funktionalität einer Model-Klasse für einen Datensatz mit Listenstruktur.

Im Konstruktor der Klasse Modell werden die Adressdaten aus einer Textdatei des oben beschriebenen Formats geladen. Dazu wird dem Konstruktor der Dateiname dieser Datei übergeben. Da das Dateiformat, in dem die Daten vorliegen, sehr einfach ist, ist auch der Einlesevorgang vergleichsweise simpel und braucht nicht näher erläutert zu werden. Wichtig ist aber, dass die einzelnen Einträge des Adressbuchs klassenintern in einer Liste gespeichert werden, die durch das Attribut self.datensatz referenziert wird. Jeder Eintrag dieser Liste ist wiederum eine Liste von Strings, die den Zeilen des Eintrags entsprechen.

Am Ende der Klassendefinition werden noch zwei Methoden definiert, die jede Model-Klasse implementieren muss. Diese Methoden bilden die Schnittstelle, über die die View-Klasse später auf die in der Model-Klasse gespeicherten Daten zugreifen kann.

Die Methode rowCount muss die Anzahl der Elemente als ganze Zahl zurückgeben, die der Datensatz enthält. Der dabei übergebene Parameter parent spielt an dieser Stelle keine Rolle.

Die Methode data wird von der View-Klasse aufgerufen, um auf ein bestimmtes Element des Datensatzes zuzugreifen. Welches das ist, wird über den Parameter index mitgeteilt. Bei index handelt es sich aber nicht um eine ganze Zahl, sondern um eine QModelIndex-Instanz. Auf den numerischen Index kann über die Methode row dieser Instanz zugegriffen werden.

Die zur Model-Klasse passende View-Klasse ist in der Programmdatei view.py enthalten und sieht folgendermaßen aus:

class View(QtWidgets.QListView):
def __init__(self, modell, parent=None):
super().__init__(parent)
self.delegate = ViewDelegate()
self.setItemDelegate(self.delegate)
self.setModel(modell)
self.setVerticalScrollMode(QtWidgets.QListView.ScrollPerPixel)

Die View-Klasse View wird von der Basisklasse QListView abgeleitet. Diese Basisklasse stellt die Funktionalität bereit, die benötigt wird, um einen listenartigen Datensatz grafisch darzustellen. Alternativ hätten auch die Klassen QTreeView und QTableView als Basisklassen dienen können, wenn zur Darstellung der Daten eine baumartige oder tabellarische Struktur besser gewesen wäre.

Dem Konstruktor der Klasse View wird eine Instanz der soeben definierten Model-Klasse Modell übergeben, deren Inhalt grafisch dargestellt werden soll. Um die Daten jedoch tatsächlich anzuzeigen, wird eine weitere Klasse benötigt, der sogenannte Delegate (dt. »Abgesandter«). Ein Delegate, den wir im Anschluss an die View-Klasse besprechen, wird der View-Klasse über die Methode setItemDelegate zugewiesen. Die Delegate-Klasse enthält die Zeichenroutinen für ein Element des Datensatzes.

Zum Schluss wird noch das Modell mittels setModel eingebunden und, was eher eine kosmetische Angelegenheit ist, der Scrollmodus auf »pixelweise« gesetzt. Im Normalzustand verschiebt das QListView-Widget den Inhalt beim Scrollen immer um ganze Einträge, was bei großen Einträgen nicht schön aussieht.

Noch zu besprechen ist die Delegate-Klasse, die das Zeichnen der Einträge übernimmt. Dazu kann sie über die Schnittstelle der Model-Klasse auf den eingelesenen Datensatz zugreifen. In der Grafik, die eingangs die Model-View-Architektur des Beispielprogramms veranschaulichte, wurde die Delegate-Klasse aus Gründen der Einfachheit weggelassen. Das möchten wir an dieser Stelle nachholen und zeigen in Abbildung 39.53, wie sich die Delegate-Klasse in die Model-View-Architektur integriert.

Die Model-View-Architektur des Beispielprogramms

Abbildung 39.53    Die Model-View-Architektur des Beispielprogramms

Die Delegate-Klasse positioniert sich als Hilfsklasse zwischen der View- und der Model-Klasse. Die View-Klasse ruft die Methode paint der Delegate-Klasse für jeden Eintrag im Datensatz auf und stellt aus den Einzelzeichnungen das ListView-Widget zusammen, das auf der grafischen Benutzeroberfläche angezeigt wird. Wie bei der View- und Model-Klasse existiert im Qt-Framework eine Basisklasse, von der eine selbst definierte Delegate-Klasse abgeleitet werden muss.

Um einen Eintrag adäquat zeichnen zu können, kann die Delegate-Klasse über die von der Model-Klasse bereitgestellte Schnittstelle auf den Datensatz zugreifen. Selbstverständlich kann auch die View-Klasse selbst auf diesem Wege Daten des Datensatzes lesen.

Im Folgenden wird das Beispielprogramm um die noch fehlende Delegate-Klasse erweitert. Diese ist in der gleichen Programmdatei definiert wie die View-Klasse. Da die Delegate-Klasse vergleichsweise umfangreich ist, werden wir sie Methode für Methode besprechen:

from PyQt5 import QtWidgets, QtGui, QtCore
class ViewDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self.rahmenStift = QtGui.QPen(QtGui.QColor(0,0,0))
self.titelTextStift = QtGui.QPen(QtGui.QColor(255,255,255))
self.titelFarbe = QtGui.QBrush(QtGui.QColor(120,120,120))
self.textStift = QtGui.QPen(QtGui.QColor(0,0,0))
self.titelSchriftart = QtGui.QFont("Helvetica", 10, QtGui.QFont.Bold)
self.textSchriftart = QtGui.QFont("Helvetica", 10)
self.zeilenHoehe = 15
self.titelHoehe = 20
self.abstand = 4
self.abstandInnen = 2
self.abstandText = 4

Im Konstruktor der Klasse ViewDelegate werden die Attribute initialisiert, die zum Zeichnen eines Adresseintrags von Bedeutung sind. Dazu zählen zum einen die Zeichenwerkzeuge, mit denen der Adresseintrag gezeichnet werden soll, und zum anderen Konstanten, die Richtgrößen zum Zeichnen eines Eintrags festlegen. Um zu besprechen, welches Attribut wozu gedacht ist, vergegenwärtigen wir uns anhand von Abbildung 39.54 noch einmal, wie ein Eintrag im späteren Programm aussieht.

Ein Eintrag im Adressbuch

Abbildung 39.54    Ein Eintrag im Adressbuch

Tabelle 39.27 listet alle Attribute der Klasse ViewDelegate mit einer kurzen Beschreibung der jeweiligen Bedeutung auf.

Attribut Beschreibung
rahmenStift der Stift, mit dem der dünne schwarze Rahmen um den Eintrag gezeichnet wird
titelTextStift der Stift, mit dem die Überschrift geschrieben wird
titelFarbe der Pinsel, mit dem das graue Rechteck unter der Überschrift gezeichnet wird
titelSchriftart die Schriftart, in der die Überschrift geschrieben wird
textStift der Stift, mit dem die Adressdaten geschrieben werden
textSchriftart die Schriftart, in der die Adressdaten geschrieben werden
zeilenHoehe die Höhe einer Adressdatenzeile in Pixel
titelHoehe die Höhe der Überschrift in Pixel
abstand der Abstand eines Eintrags vom Dialogrand und von anderen Einträgen in Pixel
abstandInnen der Abstand zwischen dem grauen Rechteck unter der Überschrift und der Umrandung des Eintrags in Pixel
abstandText der Abstand des Textes von der Umrandung des Eintrags auf der linken Seite in Pixel

Tabelle 39.27    Attribute der Klasse ViewDelegate

Damit ist der Konstruktor vollständig beschrieben. Es folgt die Methode sizeHint, die jede Delegate-Klasse implementieren muss.

    def sizeHint(self, option, index):
anz = len(index.data())
return QtCore.QSize(170, self.zeilenHoehe*anz + self.titelHoehe)

Die Methode wird von der View-Klasse aufgerufen, um die Höhe und die Breite eines einzelnen Eintrags in Erfahrung zu bringen. Dabei werden ihr zwei Parameter übergeben: option und index.

Für den Parameter option wird eine Instanz der Klasse QStyleOptionViewItem übergeben, die verschiedene Anweisungen enthalten kann, in welcher Form der Eintrag gezeichnet werden soll. Da diese Formatanweisungen möglicherweise auch Einfluss auf die Maße eines Eintrags haben, werden sie auch der Funktion sizeHint übergeben. In unserem Beispielprogramm ist der Parameter option nicht von Belang und wird nicht weiter erläutert.

Mit dem zweiten Parameter, index, wird das Element spezifiziert, dessen Dimensionen zurückgegeben werden sollen. Für index wird eine Instanz der Klasse QModelIndex übergeben. Wichtig ist vor allem die Methode data der QModelIndex-Instanz, über die auf die Daten des Eintrags zugegriffen werden kann.

Die Implementierung der Methode sizeHint berechnet Breite und Höhe eines Eintrags und gibt die Werte als QSize-Instanz zurück. Die Breite liegt dabei bei konstanten 170 Pixeln.[ 200 ](Dabei handelt es sich um eine Vereinfachung des Beispielprogramms. Alternativ kann die Breite des Eintrags anhand der längsten Zeile berechnet werden. Dazu verwenden Sie die Methode width einer QFontMetrics-Instanz. )

Die folgende Methode paint muss von einer Delegate-Klasse implementiert werden und wird immer dann aufgerufen, wenn ein einzelner Eintrag neu gezeichnet werden muss. Es wird pro paint-Aufruf immer nur ein Eintrag gezeichnet.

    def paint(self, painter, option, index):
rahmen = option.rect.adjusted(self.abstand, self.abstand,
-self.abstand, -self.abstand)
rahmenTitel = rahmen.adjusted(self.abstandInnen,
self.abstandInnen, -self.abstandInnen+1, 0)
rahmenTitel.setHeight(self.titelHoehe)
rahmenTitelText = rahmenTitel.adjusted(self.abstandText,
0, self.abstandText, 0)
datensatz = index.data()
painter.save()
painter.setPen(self.rahmenStift)
painter.drawRect(rahmen)
painter.fillRect(rahmenTitel, self.titelFarbe)
# Titel schreiben
painter.setPen(self.titelTextStift)
painter.setFont(self.titelSchriftart)
painter.drawText(rahmenTitelText,
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
datensatz[0])
# Adresse schreiben
painter.setPen(self.textStift)
painter.setFont(self.textSchriftart)
for i, eintrag in enumerate(datensatz[1:]):
painter.drawText(rahmenTitel.x() + self.abstandText,
rahmenTitel.bottom() + (i+1)*self.zeilenHoehe,
eintrag)
painter.restore()

Der Methode paint werden die drei Parameter painter, option und index übergeben. Für den Parameter painter wird eine QPainter-Instanz übergeben, die zum Zeichnen verwendet werden soll. Die beiden Parameter option und index haben die gleiche Bedeutung wie bei der Methode sizeHint.

In der Methode paint werden zunächst einige Rechtecke berechnet, die nachher zum Zeichnen des Eintrags verwendet werden. Beachten Sie, dass option.rect die Zeichenfläche für den Eintrag in Form einer QRect-Instanz beschreibt. Alle Zeichenoperationen sollten sich also an diesem Rechteck ausrichten. Die angelegten lokalen Referenzen haben die folgenden Bedeutungen:

Attribut Beschreibung
rahmen das Rechteck, um das der dünne schwarze Rahmen gezogen wird
rahmenTitel das Rechteck der grau hinterlegten Titelzeile
rahmenTitelText das Rechteck, in das der Text in der Titelzeile geschrieben wird

Tabelle 39.28    Lokale Referenzen in der Methode paint

Nachdem die lokalen Referenzen angelegt wurden, wird der Status des Painters mittels save gespeichert, um ihn am Ende der Methode mittels restore wiederherstellen zu können. Ein als Parameter übergebener Painter sollte immer in den Ausgangszustand zurückversetzt werden, nachdem die Zeichenoperationen durchgeführt wurden, da sonst ein ungewollter Seiteneffekt in einer übergeordneten Funktion auftreten könnte.

Danach werden mithilfe der Methoden drawRect und fillRect des Painters der Rahmen um den Eintrag und die grau hinterlegte Titelzeile gezeichnet. Jetzt fehlen nur noch die Beschriftungen. Dazu werden zunächst die passende Schriftart und das gewünschte Stiftwerkzeug mittels setFont und setPen ausgewählt. Die Titelzeile des Eintrags wird im Rechteck rahmenTitelText linksbündig und vertikal zentriert mit der fetten Schriftart titelSchriftart und einem weißen Pen geschrieben.

Die Methode drawText des Painters kann in mehreren Varianten aufgerufen werden. So ist es beispielsweise möglich, (wie bei der Titelzeile) ein Rechteck und eine Positionsanweisung innerhalb dieses Rechtecks zu übergeben oder (wie bei den Adresszeilen des Eintrags) direkt die Koordinaten anzugeben, an die der Text geschrieben werden soll.

Zu guter Letzt werfen wir noch einen Blick auf das Hauptprogramm, das in der Programmdatei programm.py steht:

from PyQt5 import QtWidgets
import sys, modell, view
m = modell.Modell("adressbuch.txt")
app = QtWidgets.QApplication(sys.argv)
liste = view.View(m)
liste.resize(200, 500)
liste.show()
sys.exit(app.exec_())

Nachdem die lokalen Module modell und view eingebunden wurden, wird eine Instanz der Klasse Modell erzeugt, die den Datensatz aus der Datei adressbuch.txt repräsentiert.

Die View-Klasse View dient als einziges Widget der Applikation gleichzeitig als Fensterklasse. Bevor das Widget mittels show angezeigt wird, setzen wir seine Größe durch Aufruf der Methode resize auf einen sinnvollen Wert (200 Pixel breit und 500 Pixel hoch).

Wenn das Hauptprogramm ausgeführt wird, können Sie sehen, dass sich die Basisklasse QListView der View-Klasse tatsächlich um Feinheiten wie das Scrollen von Einträgen oder das Anpassen der Einträge bei einer Größenänderung kümmert (siehe Abbildung 39.55).

Scrollen im Adressbuch

Abbildung 39.55    Scrollen im Adressbuch

 
Zum Seitenanfang

39.7.2    Auswählen von Einträgen Zur vorigen ÜberschriftZur nächsten Überschrift

Nachdem die Adressdaten als Liste angezeigt wurden, stellt sich die Frage, in welcher Form Benutzerinteraktionen umgesetzt werden können. In diesem Abschnitt wird das Beispielprogramm dahingehend erweitert, dass der Benutzer einen Eintrag des Adressbuchs auswählen kann. Im darauffolgenden Abschnitt behandeln wir dann das Editieren von Einträgen.

An der Grundstruktur des Beispielprogramms muss dafür nicht viel verändert werden, denn genau genommen ist das Auswählen im letzten Beispielprogramm schon möglich gewesen, wir haben bis jetzt nur alle Einträge der Liste gleich gezeichnet. Was noch fehlt, ist also die grafische Hervorhebung des ausgewählten Eintrags.

Das Adressbuch mit einem ausgewählten Eintrag ist in Abbildung 39.56 dargestellt.

Ein ausgewählter Eintrag im Adressbuch

Abbildung 39.56    Ein ausgewählter Eintrag im Adressbuch

Der ausgewählte Eintrag unterscheidet sich in der Farbe der Titelleiste und des Hintergrunds von den anderen Einträgen. Statt in Grau werden diese Flächen bei ausgewählten Einträgen blau gezeichnet. Dazu legen wir im Konstruktor der Delegate-Klasse zunächst einen neuen Brush mit den gewünschten Blautönen als Farbe an:

def __init__(self, parent=None): 
super().__init__(parent)
[…]
self.titelFarbeAktiv = QtGui.QBrush(QtGui.QColor(0,0,120))
self.hintergrundFarbeAktiv = QtGui.QBrush(QtGui.QColor(230,230,255))
[…]

Jetzt muss nur noch beim Zeichnen eines Eintrags, also in der Methode paint, unterschieden werden, ob es sich bei dem zu zeichnenden Eintrag um den momentan ausgewählten handelt oder nicht. Dies lässt sich anhand des Attributs state der QStyleOptionViewItem-Instanz feststellen, die beim Aufruf der Methode paint für den Parameter option übergeben wird.

Wir ändern also das Zeichnen des grauen Titelrechtecks in folgenden Code:

if option.state & QtWidgets.QStyle.State_Selected:
painter.fillRect(rahmen, self.hintergrundFarbeAktiv)
painter.fillRect(rahmenTitel, self.titelFarbeAktiv)
else:
painter.fillRect(rahmenTitel, self.titelFarbe)

Dieser Code muss vor dem Zeichnen des dünnen schwarzen Rahmens stehen:

painter.setPen(self.rahmenStift)
painter.drawRect(rahmen)

Das waren schon alle notwendigen Schritte, um es dem Benutzer zu erlauben, einen Eintrag des Adressbuchs auszuwählen.

 
Zum Seitenanfang

39.7.3    Bearbeiten von Einträgen Zur vorigen ÜberschriftZur nächsten Überschrift

Nachdem wir uns damit beschäftigt haben, wie die Adressdaten in einem QListView-Widget angezeigt werden können, und das Beispielprogramm dahingehend erweitert haben, dass ein Eintrag vom Benutzer ausgewählt werden kann, liegt die Frage nahe, ob wir dem Benutzer auch das Bearbeiten eines Datensatzes erlauben können. Es ist zwar nicht so einfach wie das Selektieren im vorangegangenen Abschnitt, doch auch für das Editieren eines Eintrags bietet die Model-View-Architektur von Qt eine komfortable Schnittstelle an.

Im späteren Programm wird das Bearbeiten eines Eintrags so aussehen, wie es in Abbildung 39.57 gezeigt ist.

Bearbeiten eines Adresseintrags

Abbildung 39.57    Bearbeiten eines Adresseintrags

Um das Bearbeiten von Einträgen zu ermöglichen, müssen die einzelnen Einträge des Datensatzes von der Model-Klasse zunächst explizit als editierbar gekennzeichnet werden. Dazu muss die Model-Klasse die Methode flags implementieren:

def flags(self, index):
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable\
| QtCore.Qt.ItemIsEnabled

Diese Methode wird immer dann aufgerufen, wenn das QListView-Widget nähere Informationen über den Eintrag erhalten will, der durch die QModelIndex-Instanz index spezifiziert wird. In unserem Fall werden unabhängig vom Index des Eintrags pauschal die Flags ItemIsSelectable, ItemIsEditable und ItemIsEnabled zurückgegeben, die für einen selektierbaren, editierbaren und aktivierten Eintrag stehen. Standardmäßig – also wenn die Methode flags nicht implementiert wird – erhält jeder Eintrag die Flags ItemIsSelectable und ItemIsEnabled.

Zusätzlich zur Methode flags sollte die Model-Klasse die Methode setData implementieren, die die Aufgabe hat, die vom Benutzer veränderten Einträge in den Datensatz zu übernehmen.

def setData(self, index, value, role=QtCore.Qt.EditRole):
self.datensatz[index.row()] = value
self.layoutChanged.emit()
return True

Der Methode werden der Index des veränderten Eintrags und der veränderte Inhalt dieses Eintrags übergeben. Der zusätzliche Parameter role soll uns an dieser Stelle nicht weiter interessieren. Im Körper der Methode wird der alte Eintrag in dem in der Model-Klasse gespeicherten Datensatz self.datensatz durch den veränderten ersetzt. Danach wird das Signal layoutChanged gesendet, das die View-Klasse dazu veranlasst, die Anzeige vollständig neu aufzubauen. Das ist sinnvoll, da sich durch die Änderungen des Benutzers die Zeilenzahl und damit die Höhe des jeweiligen Eintrags verändert haben könnte.

Das sind alle Änderungen, die an der Model-Klasse vorgenommen werden müssen, um das Editieren eines Eintrags zu erlauben. Doch auch die Delegate-Klasse muss einige zusätzliche Methoden implementieren. Dabei handelt es sich um die Methoden createEditor, setEditorData, updateEditorGeometry, setModelData und eventFilter, die im Folgenden besprochen werden.

Die Methode createEditor wird aufgerufen, wenn der Benutzer doppelt auf einen Eintrag klickt, um diesen zu editieren. Die Methode createEditor muss ein Widget zurückgeben, das dann anstelle des entsprechenden Eintrags zum Editieren angezeigt wird.

def createEditor(self, parent, option, index):
return QtWidgets.QTextEdit(parent)

Der Methode werden die bereits bekannten Parameter option und index übergeben, die den zu editierenden Eintrag spezifizieren. Zusätzlich wird für parent das Widget übergeben, das als Eltern-Widget des Editor-Widgets eingetragen werden soll. In diesem Fall erstellen wir ein QTextEdit-Widget, in dem der Benutzer den Eintrag bearbeiten soll.

Die Methode setEditorData wird vom QListView-Widget aufgerufen, um das von createEditor erzeugte Widget mit Inhalt zu füllen.

def setEditorData(self, editor, index):
editor.setPlainText("\n".join(index.data()))

Dazu bekommt die Methode das Editor-Widget in Form des Parameters editor und den bekannten Parameter index übergeben, der den zu editierenden Eintrag spezifiziert. Im Methodenkörper werden die Daten des zu editierenden Eintrags ausgelesen und mittels join zu einem einzigen String zusammengefügt. Dieser String wird dann durch Aufruf der Methode setPlainText in das QTextEdit-Widget geschrieben.

Die Methode updateEditorGeometry wird vom QListView-Widget aufgerufen, um die Größe des Editor-Widgets festlegen zu lassen.

def updateEditorGeometry(self, editor, option, index):
rahmen = option.rect.adjusted(self.abstand, self.abstand,
-self.abstand, -self.abstand)
editor.setGeometry(rahmen)

Die Methode bekommt die bekannten Parameter option und index und zusätzlich das Editor-Widget editor übergeben. In diesem Fall verpassen wir dem Editor-Widget mittels setGeometry die gleiche Größe, die der entsprechende Eintrag gehabt hätte, wenn er normal gezeichnet worden wäre.

Die Methode setModelData wird aufgerufen, wenn das Editieren durch den Benutzer erfolgt ist, um die veränderten Daten aus dem Editor-Widget auszulesen und an die Model-Klasse weiterzureichen.

def setModelData(self, editor, model, index):
model.setData(index, editor.toPlainText().split("\n"))

Die Methode bekommt sowohl das Editor-Widget als auch die Model-Klasse in Form der Parameter editor und model übergeben. Zusätzlich wird eine QModelIndex-Instanz übergeben, die den editierten Eintrag spezifiziert. In der Methode wird der Text des QTextEdit-Widgets ausgelesen und in einzelne Zeilen unterteilt. Danach wird die vorhin angelegte Methode setData der Model-Klasse aufgerufen.

Damit ist die grundlegende Funktionalität zum Editieren eines Eintrags implementiert. Allerdings werden Sie beim Ausführen des Programms feststellen, dass die (Enter)-Taste beim Editieren eines Eintrags sowohl eine neue Zeile beginnt als auch das Editieren des Eintrags beendet. Das ist nicht besonders glücklich und sollte behoben werden. Dazu implementieren wir die Methode eventFilter, die immer dann aufgerufen wird, wenn ein Event eintritt. Ein Event ist beispielsweise das Drücken einer Taste während des Editierens eines Eintrags.

def eventFilter(self, editor, event):
if event.type() == QtCore.QEvent.KeyPress \
and event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
return False
return QtWidgets.QItemDelegate.eventFilter(self, editor, event)

Die Methode bekommt das Editor-Widget editor und eine QEvent-Instanz übergeben, die das aufgetretene Event spezifiziert. Im Körper der Methode wird überprüft, ob es sich bei dem Event um einen Tastendruck handelt und – wenn ja – ob es sich bei der gedrückten Taste um die (Enter)- oder die (¢)-Taste handelt.[ 201 ](Es gibt einen Unterschied zwischen diesen beiden Tasten. (Enter) finden Sie auf der Tastatur in der unteren rechten Ecke des Nummernblocks, während die (¢)-Taste diejenige ist, die Sie verwenden, um in einem Text eine neue Zeile zu beginnen. ) Nur wenn es sich bei dem Event nicht um eine gedrückte (Enter)- oder (¢)-Taste handelt, wird die Standard-Implementation der Methode aufgerufen, beispielsweise soll also bei gedrückter (Esc)-Taste weiterhin das Editieren des Eintrags abgebrochen werden. Im Falle der (Enter)- oder der (¢)-Taste wird nichts dergleichen unternommen.

 


Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Python 3 Python 3
Jetzt Buch bestellen

 Buchempfehlungen
Zum Rheinwerk-Shop: Einstieg in Python
Einstieg in Python


Zum Rheinwerk-Shop: Python. Der Grundkurs
Python. Der Grundkurs


Zum Rheinwerk-Shop: Algorithmen mit Python
Algorithmen mit Python


Zum Rheinwerk-Shop: Objektorientierte Programmierung
Objektorientierte Programmierung


Zum Rheinwerk-Shop: Raspberry Pi. Das umfassende Handbuch
Raspberry Pi. Das umfassende Handbuch


Zum Rheinwerk-Shop: Roboter-Autos mit dem Raspberry Pi
Roboter-Autos mit dem Raspberry Pi


Zum Rheinwerk-Shop: Neuronale Netze programmieren mit Python
Neuronale Netze programmieren mit Python


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2020
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 
[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern