Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
1 Einleitung
2 Überblick über Python
3 Die Arbeit mit Python
4 Der interaktive Modus
5 Grundlegendes zu Python-Programmen
6 Kontrollstrukturen
7 Das Laufzeitmodell
8 Basisdatentypen
9 Benutzerinteraktion und Dateizugriff
10 Funktionen
11 Modularisierung
12 Objektorientierung
13 Weitere Spracheigenschaften
14 Mathematik
15 Strings
16 Datum und Zeit
17 Schnittstelle zum Betriebssystem
18 Parallele Programmierung
19 Datenspeicherung
20 Netzwerkkommunikation
21 Debugging
22 Distribution von Python-Projekten
23 Optimierung
24 Grafische Benutzeroberflächen
25 Python als serverseitige Programmiersprache im WWW mit Django
26 Anbindung an andere Programmiersprachen
27 Insiderwissen
28 Zukunft von Python
A Anhang
Stichwort

Download:
- ZIP, ca. 4,8 MB
Buch bestellen
Ihre Meinung?

Spacer
 <<   zurück
Python von Peter Kaiser, Johannes Ernesti
Das umfassende Handbuch - Aktuell zu Python 2.5
Buch: Python

Python
gebunden, mit CD
819 S., 39,90 Euro
Galileo Computing
ISBN 978-3-8362-1110-9
Pfeil 24 Grafische Benutzeroberflächen
  Pfeil 24.1 Toolkits
  Pfeil 24.2 Einführung in PyQt
    Pfeil 24.2.1 Installation
    Pfeil 24.2.2 Grundlegende Konzepte von Qt
  Pfeil 24.3 Entwicklungsprozess
    Pfeil 24.3.1 Erstellen des Dialogs
    Pfeil 24.3.2 Schreiben des Programms
  Pfeil 24.4 Signale und Slots
  Pfeil 24.5 Überblick über das Qt-Framework
  Pfeil 24.6 Zeichenfunktionalität
    Pfeil 24.6.1 Werkzeuge
    Pfeil 24.6.2 Koordinatensystem
    Pfeil 24.6.3 Einfache Formen
    Pfeil 24.6.4 Grafiken
    Pfeil 24.6.5 Text
    Pfeil 24.6.6 Eye-Candy
  Pfeil 24.7 Model-View-Architektur
    Pfeil 24.7.1 Beispielprojekt: Ein Adressbuch
    Pfeil 24.7.2 Auswählen von Einträgen
    Pfeil 24.7.3 Editieren von Einträgen
  Pfeil 24.8 Wichtige Widgets
    Pfeil 24.8.1 QCheckBox
    Pfeil 24.8.2 QComboBox
    Pfeil 24.8.3 QDateEdit
    Pfeil 24.8.4 QDateTimeEdit
    Pfeil 24.8.5 QDial
    Pfeil 24.8.6 QDialog
    Pfeil 24.8.7 QGLWidget
    Pfeil 24.8.8 QLineEdit
    Pfeil 24.8.9 QListView
    Pfeil 24.8.10 QListWidget
    Pfeil 24.8.11 QProgressBar
    Pfeil 24.8.12 QPushButton
    Pfeil 24.8.13 QRadioButton
    Pfeil 24.8.14 QScrollArea
    Pfeil 24.8.15 QSlider
    Pfeil 24.8.16 QTableView
    Pfeil 24.8.17 QTableWidget
    Pfeil 24.8.18 QTabWidget
    Pfeil 24.8.19 QTextEdit
    Pfeil 24.8.20 QTimeEdit
    Pfeil 24.8.21 QTreeView
    Pfeil 24.8.22 QTreeWidget
    Pfeil 24.8.23 QWidget


Galileo Computing - Zum Seitenanfang

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

Mit Qt4 wurde die sogenannte Model-View-Architektur in das Framework eingeführt. Die grundsätzliche Idee dieser Art der Programmierung ist es, Form und Inhalt voneinander zu trennen. Bezogen auf Qt bedeutet das, dass Klassen, die bestimmte Daten enthalten, von Klassen getrennt werden sollen, die diese Daten an der grafischen Benutzeroberfläche anzeigen. So soll es eine Modellklasse geben, die ein bekanntes Interface für die gespeicherten Daten bereitstellt, und eine Viewklasse, die über die Modellklasse auf die Daten zugreift und auf der grafischen Oberfläche anzeigt. Beachten Sie dabei, dass nicht vorausgesetzt wird, dass die Daten tatsächlich in der Modellklasse enthalten sind, sondern nur, dass die Modellklasse Methoden bereitstellt, um auf die Daten zuzugreifen. Die Daten selbst können durchaus in einer Datenbank oder Datei gespeichert sein.

Das Aufteilen der Programmlogik in Modell- und Viewklassen hat den Vorteil, dass das Programm insgesamt einfacher und besser strukturiert wird. Außerdem führen Änderungen beispielsweise in der Art, wie die Daten gespeichert sind, nicht dazu, dass die Anzeigeklasse angepasst werden muss. Umgekehrt ist es der Modellklasse egal, in welcher Form die von ihr bereitgestellten Daten am Bildschirm angezeigt werden.

Das Verhältnis zwischen Modell- und Viewklasse lässt sich durch Abbildung 24.23 anschaulich beschreiben.

Abbildung 24.23  Die Model-View-Architektur

Das Qt-Framework bietet einige Klassen, die dem Programmierer beim Erstellen einer Model-View-Architektur helfen. Darunter finden sich Basisklassen sowohl für die Modell- als auch für die Viewklassen.

Im Folgenden soll die Verwendung einiger dieser Klassen anhand einer einfachen Anwendung mit Model-View-Architektur demonstriert werden. Bei dieser Anwendung soll es sich um ein rudimentäres Adressbuch im Stil von Microsoft Outlook handeln.


Galileo Computing - Zum Seitenanfang

24.7.1 Beispielprojekt: Ein Adressbuch  Zur nächsten ÜberschriftZur vorigen Überschrift

In diesem Abschnitt bieten wir 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 anzeigt. Intern sollen dabei die Datensätze durch eine Modellklasse eingelesen und aufbereitet werden. Eine Viewklasse soll sich dann um die Anzeige der Daten kümmern.

Wir werden uns zunächst auf das bloße Einlesen und Anzeigen konzentrieren. Danach werden wir das Programm um bestimmte sinnvolle Extras erweitern, anhand derer weitere Aspekte von Model-View-Architekturen in Qt vorgestellt werden. Die vorläufige Anwendung, die in diesem Kapitel entwickelt wird, soll so aussehen wie in Abbildung 24.24.

Abbildung 24.24  Ein Adressbuch

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

Donald Duck 
don@ld.de 
Pechvogelstraße 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 stehen sollte, gibt es keine weiteren Anforderungen an die Formatierung der Daten. [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, schließlich geht es nicht darum, eine perfekte Applikation zu schreiben. ]

Das Adressbuch soll eine Beispielimplementation für eine Model-View-Architektur darstellen. Es ist auch relativ klar, welche Aufgaben dabei der Modell- und welche der Viewklasse zukommen.

Die Modellklasse hat die Aufgabe, die Quelldatei mit den Adressdaten einzulesen und eine Schnittstelle bereitzustellen, über die auf diese Daten zugegriffen werden kann.

Die Viewklasse soll auf die in der Modellklasse gespeicherten Daten zugreifen und diese dann in geeigneter Form auf dem Bildschirm präsentieren. 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. Hätten die Daten eine andere Struktur, könnten wir die Basisklassen QTreeView oder QTableView verwenden, die eine baumartige bzw. tabellarische Struktur der Daten visualisieren.

Abbildung 24.25 stellt die Programmstruktur grafisch dar.

Abbildung 24.25  Die Model-View-Architektur unseres Beispielprogramms

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

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

Es wird die Modellklasse Modell definiert, die von der Basisklasse QtCore. QAbstractListModel abgeleitet ist. Diese Basisklasse implementiert grundlegende Funktionalität einer Modellklasse für einen Datensatz, der als eindimensionale Folge von Werten, also als Liste, angesehen werden soll.

Im Konstruktor der Klasse Modell sollen die Adressdaten aus einer Textdatei des oben beschriebenen Formats geladen werden. Dazu bekommt der Konstruktor den Dateinamen 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 jeweils eine Zeile des Eintrags repräsentieren.

Beachten Sie zudem, dass die einzelnen Einträge als Instanzen der Klasse QVariant gespeichert werden. An der Schnittstelle zwischen Modell- und Viewklasse werden grundsätzlich QVariant-Instanzen übertragen. Die Klasse QVariant ist ein Konzept der C++-Welt, aus der Qt kommt, und ermöglicht es dort, Werte beliebiger Datentypen über dieselbe Schnittstelle zu schicken. Dieses Konzept erlaubt sehr flexible Schnittstellen in C++, ist aber im Zusammenhang mit Python eher kontraproduktiv, da Schnittstellen in Python von Haus aus flexibel bezüglich der verwendeten Datentypen sind. Trotzdem müssen wir QVariant verwenden, da PyQt auf größtmögliche Kompatibilität zu den C++-Schnittstellen setzt.

Am Ende der Klassendefinition werden noch zwei Methoden definiert, die jede Modellklasse implementieren muss. Diese Methoden bilden die Schnittstelle, über die die Viewklasse später auf die in der Modellklasse 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 soll an dieser Stelle keine Rolle spielen.

Die Methode data wird von der Viewklasse aufgerufen, um auf ein bestimmtes Element des Datensatzes zuzugreifen. Welches das ist, wird beim Aufruf der Methode data über den Parameter index mitgeteilt. Bei index handelt es sich aber nicht um eine ganze Zahl, sondern um eine QModelIndex-Instanz. Auf den tatsächlichen Index kann über die Methode row dieser Instanz zugegriffen werden. Die Methode data muss eine QVariant-Instanz zurückgeben.

So viel zur Modellklasse. Die dazu passende Viewklasse sieht folgendermaßen aus und ist in der Programmdatei view.py enthalten:

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

Die Viewklasse View wird von der Basisklasse QtGui.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 verwendet werden soll.

Dem Konstruktor der Klasse View wird die Instanz der soeben definierten Modellklasse Modell übergeben, die mithilfe der Viewklasse grafisch dargestellt werden soll. Um die Daten jedoch tatsächlich anzuzeigen, wird eine weitere Klasse benötigt, der sogenannte Delegate (dt. Abgesandter). Eine Instanz der Delegate-Klasse, die im Anschluss an die Viewklasse besprochen werden soll, wird der Viewklasse ü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 würde das QListView-Widget beim Verschieben der Scrollbar immer um ganze Einträge weiter scrollen, was bei wenigen großen Einträgen nicht schön aussieht.

Neben der Viewklasse wurde eine sogenannte Delegate-Klasse angesprochen, von der innerhalb der Viewklasse eine Instanz erstellt wurde. Die Aufgabe einer solchen Delegate-Klasse, die mehr oder weniger als Teil der Viewklasse zu betrachten ist, besteht darin, das Zeichnen eines einzelnen Eintrags in der Liste zu erledigen. Dazu kann die Delegate-Klasse über die Schnittstelle der Modellklasse auf den eingelesenen Datensatz zugreifen. Die Grafik, die eingangs die Model-View-Architektur des Beispielprogramms veranschaulichte, enthielt aus Gründen der Einfachheit keine Informationen über die Delegate-Klasse. Das möchten wir an dieser Stelle nachholen und zeigen in Abbildung 24.26, wie sich die Delegate-Klasse in die Model-View-Architektur integiert.

Abbildung 24.26  Die Model-View-Architektur des Beispielprogramms

Die Delegate-Klasse positioniert sich zwischen der View- und der Modellklasse und ist für das Zeichnen eines einzelnen Eintrags im ListView Widget zuständig. Bei der Delegate-Klasse handelt es sich nicht um ein Widget, sondern nur um eine Hilfsklasse der Viewklasse. Die Viewklasse ruft die Methode paint der Delegate-Klasse für jeden Eintrag im Datensatz auf und stellt aus den Einzelzeichnungen das ListView Widget zusammen, das an der grafischen Benutzeroberfläche angezeigt wird. Wie bei der View- und Modellklasse auch, existiert im Qt-Framework eine Basisklasse, von der eine selbst definierte Delegate-Klasse abgeleitet werden muss. Um das Zeichnen eines Eintrags dann an die jeweiligen Bedürfnisse anzupassen, müssen in der abgeleiteten Klasse diverse Methoden implementiert werden. Näheres dazu erfahren Sie anhand des Beispielprogramms im Laufe dieses Kapitels.

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

Im Folgenden soll die Delegate-Klasse für die vorher besprochene Viewklasse erläutert werden. Die Delegate-Klasse ist in der gleichen Programmdatei definiert wie die Viewklasse. Da die Delegate-Klasse vergleichsweise umfangreich ist, werden wir sie Methode für Methode besprechen:

from PyQt4 import QtGui, QtCore
class ViewDelegate(QtGui.QItemDelegate): def __init__(self): QtGui.QItemDelegate.__init__(self)
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 einige Attribute initialisiert, die zum Zeichnen eines Adresseintrags von Bedeutung sind. Dazu zählen zum einen die Zeichenwerkzeuge wie beispielsweise Brushs und Pens, mit denen der Adresseintrag gezeichnet werden soll, und zum anderen einige Konstanten, die Abstände und Richtgrößen zum Zeichnen eines Eintrags festlegen. Um zu besprechen, welches Attribut wofür gedacht ist, vergegenwärtigen wir uns anhand von Abbildung 24.27 noch einmal, wie ein Eintrag im späteren Programm gezeichnet werden soll.

Abbildung 24.27  Ein Eintrag im Adressbuch

Die folgende Tabelle listet alle Attribute, darunter vor allem die angelegten Zeichenwerkzeuge, der Klasse ViewDelegate mit einer kurzen Beschreibung der jeweiligen Bedeutung auf.


Tabelle 24.7  Attribute der Klasse »ViewDelegate«
Attribut Beschreibung
rahmenStift

Der Pen, mit dem der dünne schwarze Rahmen um den Eintrag gezeichnet werden soll

titelTextStift

Der Pen, mit dem die Überschrift geschrieben werden soll

titelFarbe

Der Brush, mit dem das graue Rechteck unter der Überschrift gezeichnet wird

titelSchriftart

Die Schriftart, in der die Überschrift geschrieben werden soll

textStift

Der Pen, mit dem die Adressdaten geschrieben werden sollen

textSchriftart

Die Schriftart, in der die Adressdaten geschrieben werden sollen

zeilenHoehe

Die Höhe einer Zeile der Adressdaten in Pixel

titelHoehe

Die Höhe der Überschrift in Pixel

abstand

Der Abstand des Eintrags vom Dialogrand und 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 Texts von der Umrandung des Eintrags auf der linken Seite in Pixel


Damit wäre der Konstruktor vollständig beschrieben. Es folgt die Methode size Hint, die jede Delegate-Klasse implementieren muss. Diese Methode wird vom QListView-Widget aufgerufen, um die Dimensionen herauszufinden, die ein bestimmter Eintrag des Datensatzes in der Anzeige benötigt.

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

Die Methode wird aufgerufen, um die Höhe und die Breite eines einzelnen Eintrags in Erfahrung zu bringen. Dabei bekommt sie 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 QModel Index-Instanz, über die auf die Daten des Eintrags zugegriffen werden kann. Bei den Daten handelt es sich um die QVariant-Instanz, die in der Methode data der Modellklasse zurückgegeben wird.

In der Methode sizeHint wird jetzt über die Methode data der übergebenen QModelIndex-Instanz auf die Daten des Adresseintrags zugegriffen. Da es sich dabei um eine QVariant-Instanz handelt, muss diese erst durch Aufruf der Methode toList in eine Liste konvertiert werden. Danach wird die Größe berechnet, die der Eintrag beim späteren Zeichnen haben wird, und in Form einer QSize-Instanz zurückgegeben. Beachten Sie, dass die Breite der Einträge in diesem Beispiel bei konstanten 170 Pixeln liegt. [Dabei handelt es sich um eine Vereinfachung des Beispielprogramms. In einem wirklichen Programm müsste die Breite des Eintrags anhand der längsten Zeile berechnet werden. Dazu kann die Methode width einer QFontMetrics-Instanz verwendet werden. Näheres dazu finden Sie in der Qt- bzw. PyQt-Dokumentation. ]

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

    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().toList() 
        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].toString())
# 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, "%s" % eintrag.toString()) painter.restore()

Die Methode paint bekommt die drei Parameter painter, option und index übergeben. Für den Parameter painter wird eine QPainter-Instanz übergeben, die dazu verwendet werden soll, den Eintrag zu zeichnen. Die beiden Parameter option und index haben die gleiche Bedeutung wie bei der Methode sizeHint der Delegate-Klasse.

In der Methode paint werden zunächst einige Rechtecke berechnet, die nachher zum Zeichnen des Eintrags verwendet werden. Beachten Sie, dass option.rect eine QRect-Instanz referenziert, die das Rechteck beschreibt, in das der Eintrag gezeichnet werden soll. Alle Zeichenoperationen sollten sich also an diesem Rechteck ausrichten. Die angelegten lokalen Referenzen haben folgende Bedeutung:


Tabelle 24.8  Lokale Referenzen in der Methode »paint«
Attribut Beschreibung
rahmen

Das Rechteck, um das der dünne schwarze Rahmen gezogen werden soll

rahmenTitel

Das Rechteck der grau hinterlegten Titelzeile

rahmenTitelText

Das Rechteck, in das der Text in der Titelzeile geschrieben wird. Dazu wird ein Rechteck benötigt, da der Text vertikal zentriert werden soll.


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. Beachten Sie, dass ein auf einem solchen Wege übergebener Painter immer in den Ausgangszustand zurückversetzt werden sollte, nachdem die Zeichenoperationen durchgeführt wurden, da sonst ein ungewollter Seiteneffekt in der übergeordneten Funktion, in diesem Fall also im Qt-Framework, auftritt.

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 des Eintrags. Dazu werden zunächst die passende Schriftart und das gewünschte Stiftwerkzeug mittels setFont und setPen ausgewählt. Die Titelzeile des Eintrags wird mit der fetten Schriftart titel Schriftart und einem weißen Pen geschrieben. Außerdem wird sie im Rechteck rahmenTitelText vertikal zentriert und horizontal linksbündig positioniert.

Beachten Sie, dass die Methode drawText des Painters in mehreren Varianten aufgerufen werden kann. 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 riskieren wir noch einen Blick auf das Hauptprogramm, das in der Programmdatei programm.py stehen soll:

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

Nachdem vor allem die lokalen Module modell und view eingebunden wurden, wird eine Instanz der Klasse Modell erzeugt, die den Datensatz aus der Datei adressbuch.txt repräsentieren soll. Nachdem die Modellklasse instanziiert wurde, wird nach dem bekannten Schema eine PyQt-Applikation erstellt.

Beachten Sie dabei, dass die Viewklasse View als einziges Widget der Applikation gleichzeitig als Fensterklasse dient. Bevor das Widget mittels show angezeigt wird, wird seine Größe durch Aufruf der Methode resize auf einen sinnvollen Wert (200 Pixel breit und 500 Pixel hoch) gesetzt.

Wenn das Hauptprogramm ausgeführt wird, können Sie sehen, dass sich die Basisklasse QListView der Viewklasse 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 24.28).

Abbildung 24.28  Scrollen im Adressbuch


Galileo Computing - Zum Seitenanfang

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

Nachdem wir die Adressdaten erfolgreich als eine Art Liste in einem Widget angezeigt haben, drängt sich die Frage auf, ob abgesehen vom bloßen Anzeigen der Daten noch weitere Aktionen durchgeführt werden können. So soll das Programm in diesem Abschnitt dahingehend weiterentwickelt werden, dass der Benutzer einen Eintrag des Adressbuchs auswählen kann.

An der Grundstruktur des Beispielprogramms und insbesondere der Viewklasse muss dafür nicht viel verändert werden, denn genau genommen ist das Auswählen im vorherigen Beispielprogramm schon möglich gewesen, allerdings haben wir bis dato alle Einträge der Liste gleich gezeichnet. Was noch fehlt, ist also die grafische Hervorhebung des ausgewählten Eintrags, damit der Benutzer erkennen kann, welcher Eintrag momentan selektiert ist.

Ein ausgewählter Eintrag im Adressbuch soll später folgendermaßen aussehen:

Abbildung 24.29  Ein ausgewählter Eintrag im Adressbuch

Der ausgewählte Eintrag soll sich in der Farbe der Titelleiste von den anderen unterscheiden. Statt in einem dunklen Grau soll sie in einem Blauton gezeichnet werden. Dazu legen wir im Konstruktor der Delegate-Klasse zunächst einen neuen Brush mit diesem Blauton als Farbe an:

def __init__(self): 
    QtGui.QItemDelegate.__init__(self) 
    […] 
    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 & QtGui.QStyle.State_Selected: 
    painter.fillRect(rahmen, self.hintergrundFarbeAktiv) 
    painter.fillRect(rahmenTitel, self.titelFarbeAktiv) 
else: 
    painter.fillRect(rahmenTitel, self.titelFarbe)

Beachten Sie, dass dieser Code vor dem Zeichnen des dünnen schwarzen Rahmens stehen muss:

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

Das waren tatsächlich schon alle notwendigen Schritte, um es dem Benutzer zu erlauben, einen Eintrag des Adressbuchs auszuwählen. Beachten Sie, dass mit dem binären UND-Operator & überprüft wird, ob das Statusflag Status_Selected gesetzt ist oder nicht.

Neben Status_Selected existieren noch weitere vordefinierte Zustände, von denen mitunter auch mehrere gleichzeitig gesetzt sein können. Diese Zustände sind teilweise sehr speziell und sollen hier nicht näher erläutert werden.


Galileo Computing - Zum Seitenanfang

24.7.3 Editieren von Einträgen  topZur vorigen Überschrift

Nachdem wir uns damit beschäftigt haben, wie die Adressdaten in einem QList View-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 Editieren eines Datensatzes erlauben können. Es ist zwar nicht ganz so banal wie das Selektieren eines Eintrags im vorherigen Kapitel, doch auch für das Editieren eines Eintrags bietet die Model-View-Architektur von Qt eine komfortable Schnittstelle an.

Im späteren Programm soll das Editieren eines Eintrags so aussehen, wie es in Abbildung 24.30 gezeigt ist.

Abbildung 24.30  Editieren eines Adresseintrags

Um das Editieren von Einträgen zu ermöglichen, müssen die einzelnen Einträge des Datensatzes von der Modellklasse zunächst explizit als editierbar gekennzeichnet werden. Dazu muss die Modellklasse 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 Item IsEnabled 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 Item IsEnabled.

Zusätzlich zur Methode flags sollte die Modellklasse 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.emit(QtCore.SIGNAL("layoutChanged()")) 
    return True

Die Methode bekommt den Index des veränderten Eintrags und den veränderten Inhalt dieses Eintrags übergeben. Der zusätzliche Parameter role soll an dieser Stelle nicht weiter interessieren. Im Körper der Methode wird der alte Eintrag in dem in der Modellklasse gespeicherten Datensatz self.datensatz durch den veränderten ersetzt. Danach wird das Signal layoutChanged gesendet, das die Viewklasse dazu anhält, 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 Modellklasse 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, setModel Data 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 statt des entsprechenden Eintrags zum Editieren angezeigt wird.

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

Die Methode bekommt 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 Elternwidget des Editorwidgets eingetragen werden soll. In diesem Fall erstellen wir ein QTextEdit-Widget, in dem der Benutzer den Eintrag editieren 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): 
    l = [unicode(s.toString()) for s in index.data().toList()] 
    text = "\n".join(l) 
    editor.setPlainText(text)

Dazu bekommt die Methode das Editorwidget 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 Editorwidgets 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 Editorwidget editor übergeben. In diesem Fall verpassen wir dem Editorwidget mittels setGeometry die gleiche Größe, die der entsprechende Eintrag gehabt hätte, wenn er normal gezeichnet werden würde.

Die Methode setModelData wird aufgerufen, wenn das Editieren durch den Benutzer erfolgt ist, um die veränderten Daten aus dem Editorwidget auszulesen und an die Modellklasse weiterzureichen.

def setModelData(self, editor, model, index): 
    txt = editor.toPlainText() 
    l = [QtCore.QVariant(s) for s in txt.split("\n")] 
    model.setData(index, QtCore.QVariant(l))

Die Methode bekommt sowohl das Editorwidget als auch die Modellklasse in Form der Parameter editor und model übergeben. Zusätzlich wird eine QModel Index-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 Modellklasse 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 kein besonders glücklicher Umstand und sollte behoben werden. Dazu implementieren wir die Methode eventFilter, die immer dann aufgerufen wird, wenn ein sogenanntes 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 QtGui.QItemDelegate.eventFilter(self, editor, event)

Die Methode bekommt das Editorwidget editor und eine QEvent-Instanz übergeben, die das eingetretene 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 Return-Taste handelt. Beachten Sie, dass es einen Unterschied zwischen diesen beiden Tasten gibt. Enter finden Sie auf der Tastatur unten rechts vom Nummernblock, während die Return-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 Return-Taste handelt, wird die Standardimplementation der Methode aufgerufen, beispielsweise soll also bei gedrückter Escape-Taste weiterhin das Editieren des Eintrags abgebrochen werden. Im Falle der Enter- oder der Return-Taste wird nichts dergleichen unternommen.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.






 <<   zurück
  
  Zum Katalog
Zum Katalog: Python






Python
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Linux






 Linux


Zum Katalog: Ubuntu GNU/Linux






 Ubuntu GNU/Linux


Zum Katalog: Praxisbuch Web 2.0






 Praxisbuch Web 2.0


Zum Katalog: UML 2.0






 UML 2.0


Zum Katalog: Praxisbuch Objektorientierung






 Praxisbuch Objektorientierung


Zum Katalog: Einstieg in SQL






 Einstieg in SQL


Zum Katalog: IT-Handbuch für Fachinformatiker






 IT-Handbuch für Fachinformatiker


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





Copyright © Galileo Press 2008
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.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de