Weiter Zurück Inhaltsverzeichnis
In diesem Kapitel werden wir uns der View Klasse von KScribble zuwenden, um zu definieren, wie die Kindfenster arbeiten sollen.
Als erstes sehen wir, daß KScribbleView standarmäßig von QWidget
abgeleitet ist. Das ist die Mindestanforderung für ein
Kindfenster, aber es reicht bereits für unsere Bedürfnisse aus. Wenn es darum geht, das Verhalten eines neuen Widgets zu definieren,
müssen wir wissen, wie der Benutzer mit dem Fenster interagieren soll. In unserem Beispiel, soll dies offensichtlich mit Hilfe der Maus
geschehen. Daher müssen wir einige virtuelle Methoden von QWidget
überschreiben, die die Mausereignisse verarbeiten die unser
Widget empfängt. Was wir wissen müssen ist, wann der Benutzer eine Maustaste drückt, weil nur gezeichnet werden soll, wenn eine
Maustaste gedrückt ist. Außerdem müssen wir wissen, wann die Maus bewegt wird (und wohin), sowie den Zeitpunkt, zu dem die Maustaste
losgelassen wird, da dann der Zeichenzug beendet ist. Weiterhin wollen wir, daß unser Bild im Fenster gezeichnet wird und seine
Größe angepasst wird, wenn der Benutzer sich entschließt, das Fenster in der Größe zu verändern. Wir werden auch ein Member
QPointArray
und einen boolschen Wert mousePressed hinzufügen. Fügen Sie den, mit dem Pfeil gekennzeichneten Code, Ihrer Klasse
KScribbleView hinzu:
kscribbleview.h -> #include <qpointarray.h> class KScribbleView { . . protected: virtual void closeEvent(QCloseEvent* ); -> virtual void mousePressEvent( QMouseEvent * ); -> virtual void mouseReleaseEvent( QMouseEvent * ); -> virtual void mouseMoveEvent( QMouseEvent * ); -> virtual void resizeEvent( QResizeEvent * ); -> virtual void paintEvent( QPaintEvent * ); KScribbleDoc *doc; -> private: -> bool mousePressed; -> QPointArray polyline; }
Wir kommen jetzt zu der tatsächlichen Implementierung der Ereignis Handler. Wie in
The KDE Library Reference Guide erkärt, hat Qt gute Methoden, Ereignisse zu behandeln, besonders wenn die Ziele Widgets sind.
QWidget
als Basisklasse, preselektiert die Events und stellt Basis Event Handler zur Verfügung, die, da sie als virtuell
deklariert sind, überladen werden können, so daß wir definieren können, wie unser Widget auf Ereignisse reagieren soll.
Eine Methode ist bereits überladen: die closeEvent()
Methode. Dies ist notwendig, weil unser Hauptfenster, repräsentiert in der
App Klasse, bereits das Schließen von Kind Fenstern preselektiert und dies behandelt; daher muß der Standard Event Handler, der nur
das Schließen akzeptiert, überschrieben werden und diese Arbeit die App Klasse machen lassen.
Als erstes müssen wir im Konstruktor das Standardverhalten des Widgets deklarieren, indem wir Member initialisieren und vordefinierte Werte setzen:
kscribbleview.cpp KScribbleView::KScribbleView(KScribbleDoc* pDoc, QWidget *parent, const char* name, int wflags) : QWidget(parent, name, wflags) { doc=pDoc; -> setBackgroundMode( QWidget::NoBackground ); -> setCursor( Qt::crossCursor ); -> mousePressed=false; -> polyline=QPointArray(3); }
Wir setzen den Hintergrund auf NoBackground, setzen einen Cursor (crossCursor) und initialisieren mousePressed und polyline. Nun beginnen
wir mit der Implementation unseres ersten Ereignis Handlers, mousePressEvent()
, um zu erkennen wann der Benutzer
die Maus drückt und wo.
Beachten Sie: Die folgenden Implementationen müssen komplett eingefügt werden, es gibt also keinen Pfeil !
void KScribbleView::mousePressEvent( QMouseEvent *e ) { mousePressed = TRUE; polyline[2] = polyline[1] = polyline[0] = e->pos(); }
Hier setzen wir mousePressed auf true, wir haben das Event also irgendwie behandelt. Die zweite Zeile ist nicht so offensichtlich: wir
speichern die Position, an der die Maustaste gedrückt wurde, in den ersten drei Elementen unseres Feldes. Da das Feld ein
QPointArray
ist, kann es Werte vom Typ QPoint
speichern (die selber wieder einen x und y Wert enthalten). Wir werden in
diesem Feld Mauspositionen speichern und daraus die Zeichenroutine im mouseMoveEvent entwickeln:
void KScribbleView::mouseMoveEvent( QMouseEvent *e ) { if ( mousePressed ) { QPainter painter; painter.begin( &doc->buffer ); painter.setPen( doc->currentPen() ); polyline[2] = polyline[1]; polyline[1] = polyline[0]; polyline[0] = e->pos(); painter.drawPolyline( polyline ); painter.end(); QRect r = polyline.boundingRect(); r = r.normalize(); r.setLeft( r.left() - doc->penWidth() ); r.setTop( r.top() - doc->penWidth() ); r.setRight( r.right() + doc->penWidth() ); r.setBottom( r.bottom() + doc->penWidth() ); doc->setModified(); bitBlt( this, r.x(), r.y(), &doc->buffer, r.x(), r.y(), r.width(), r.height() ); } }
Dieser Event Handler ist wahrscheinlich der schwierigste, wir werden ihn also Schritt für Schritt durchgehen, um zu verstehen was
gemacht wird. Als erstes empfängt der Handler alle Mausbewegungen über dem Widget. Da wir aber nur an Bewegungen interessiert sind,
wenn gleichzeitig eine Maustaste gedrückt ist, weil dann gezeichnet werden muß, haben wir gefragt ob mousePressed true ist.
Dies wurde vom mousePressEvent()
Handler bereits gemacht, daher brauchen wir uns darum nicht weiter zu kümmern. Nun
beginnen wir zu zeichnen. Als erstes erzeugen wir einen QPainter
und lassen ihn in den Puffer des Dokumentes zeichnen.
Das ist wichtig, da der Puffer die wirklichen Daten enthält, die Ansicht dient nur als Kommunikator zwischen Dokument und
Benutzer. Den Stift holen wir ebenfalls aus der Dokumentinstanz, indem wir currentPen()
aufrufen. Die nächsten drei
Zeilen weisen die Werte des polyline QPoint
zu und setzen Punkt 2 auf 1, 1 auf 0 und 0 auf den Punkt, zu dem die
Mausbewegung ging (das ist der Wert an dem wir interessiert sind). Angenommen wir haben gerade die Maus gedrückt (also enthalten
alle Werte diese Position) und das erste Mausereignis, das die Position enthält, zu der eine Linie gezeichnet werden soll, findet
statt; dann wird dieser Wert wieder in das erste Element des Feldes eingetragen. Sie mögen sich fragen, warum wir dann drei
Elemente im Feld brauchen, wenn wir nur eine Linie von einer zu nächsten Position zeichnen wollen. Die folgenden Zeilen erklären
das: nachdem die Übertragung in unseren Puffer erfolgt ist (mit drawPolyline()
und painter.end()
), erstellen wir
ein Rechteck r und verwenden boundingRect()
von QPointArray
um ein QRect
zu erhalten, das alle drei
Punkte enthält. Daher brauchen wir die drei Punkte, um ein fast fertiges Rechteck zu erhalten. Dann verwenden wir
normalize()
, damit der linke, obere Wert am kleinsten ist (da Koordinaten von links, oben nach rechts, unten zunehmen).
Das nächste ist, die Größe des Rechtecks der Breite des Stiftes anzupassen, weil der Stift eine bestimmte Breite hat, die wir mit
penWidth()
ermitteln, und um das Rechteck um die Breite des Stiftes zu erweitern (Stellen Sie sich vor, die Maus wäre nur um
zwei Pixel bewegt worden, der Stift hätte aber eine Breite von 10 Pixeln, dann würde das Rechteck nicht den ganzen gezeichneten
Bereich enthalten). Schließlich markieren wir das Dokument noch als modifiziert und verwenden die bitBlt()
Funktion, um
das Rechteck aus dem Puffer in das Widget zu kopieren. bitBlt arbeitet bitweise und ist sehr schnell, dies ist also eine
bessere Methode den gezeichneten Bereich in das Widget zu kopieren, als das ganze Widget neuzuzeichnen. Seine Argumente sind:
Erst das Objekt in das gezeichnet werden soll (das Ziel), in diesem Fall ist es unser Widget, wir müssen also den Zeiger
this verwenden. Die nächsten beiden Argumente sind die linke, obere Ecke des Zieles, gefolgt von der Quelle, mit deren Höhe
und Breite. Da die pixmap Koordinaten die gleichen sind, die auch unser Widget verwendet (weil unser Pixmap in der linken oberen
Ecke gezeichnet wurde), sind die Koordinaten für den linken, oberen Punkt bei Quelle und Ziel identisch. Darauf muß in einem der
nächsten Schritte geachtet werden, daher wird es hier schon erwähnt. Als nächstes kommt, was beim Loslassen der Maustaste
geschieht. Es muß dann aufgehört werden bei Mausbewegung zu zeichnen, also setzen wir mousePressed auf false:
void KScribbleView::mouseReleaseEvent( QMouseEvent * ) { mousePressed = FALSE; }
Wir sind jetzt mit der Implementierung der Benutzerinteraktion fertig, was die Zeichenfunktionen angeht. Das Beispiel zeigt, daß es nicht allzu kompliziert ist, ein Document View Modell zu benutzen. Erzeugen Sie nur die Dokumentinstanz, so daß sie die Inhalte enthält, und kopieren Sie die Inhalte in Ihre Ansicht.
Übrig bleiben zwei virtuelle Handler, die reimplementiert werden müssen. Als erstes müssen wir darauf achten, daß unser Bild im Fenster neu gezeichnet wird, wenn etwas anderes passiert: wenn Sie ein anderes Fenster öffnen, das die Zeichnung verdeckt, wird sie nicht mehr da sein, wenn sie wieder zu Ihrer Zeichnung wechseln, es sei denn, Ihr Zeichenereignis wird ausgeführt, so daß das Bild neu gezeichnet wird:
void KScribbleView::paintEvent( QPaintEvent *e ) { QWidget::paintEvent( e ); QRect r = e->rect(); bitBlt( this, r.x(), r.y(), &doc->buffer, r.x(), r.y(), r.width(), r.height() ); }
Diese Methode verwendet ebenfalls bitBlt()
zum Zeichnen des Puffers in das Widget. Hier brauchen wir nur den Ausschnitt
der, neugezeichnet wird, also holen wir uns die Geometrie des Events ( e->rect()
) und verwenden die Koordinaten für
bitBlt()
genau wie wir es bei mouseMoveEvent()
gemacht haben.
Das einzige, um das wir uns nicht gekümmert haben, ist die Größe des Pixmaps. Wir haben sie nirgendwo gesetzt - wir haben noch
nicht einmal das Pixmap aus der Dokumentklasse benutzt, außer zum Laden und Speichern- aber diese Methoden werden nicht
aufgerufen, wenn ein neues Bild erzeugt wird. Es scheint also, unser Pixmap habe weder eine Größe noch einen vordefinierten Hintergrund
(selbst wenn wir die Größe gesetzt hätten, wären die Inhalte zufällige Farben, weil es nicht initialisiert wurde). Andererseits
haben wir die Tatsache, daß die KScribbleView Instanzen in der Größe angepasst werden wenn sie angezeigt werden- wenigstens auf
die Minimalgröße. Das ist der Punkt, an dem wir auch die Initialisierung vornehmen können, weil der Benutzer die Größe manuell
ändern kann und das Widget ebenfalls ein resize Event erhält. Aus Gründen der Einfachheit, setzen wir die Pixmap Größe gleich der
Widgetgröße. All dies geschieht im Event Handler resizeEvent()
:
void KScribbleView::resizeEvent( QResizeEvent *e ) { QWidget::resizeEvent( e ); int w = width() > doc->buffer.width() ? width() : doc->buffer.width(); int h = height() > doc->buffer.height() ? height() : doc->buffer.height(); QPixmap tmp( doc->buffer ); doc->buffer.resize( w, h ); doc->buffer.fill( Qt::white ); bitBlt( &doc->buffer, 0, 0, &tmp, 0, 0, tmp.width(), tmp.height() ); }
Hier wird zunächst der resizeEvent Handler von QWidget
aufgerufen. Dann berechnen wir die Größe unseres Bildes- da wir
ein Fenster sowohl kleiner als auch größer machen können, müssen wir diese beiden Fälle unterscheiden: wenn wir verkleinern, soll
das Bild immer noch seinen gesamten Inhalt behalten. Wenn wir jedoch das Widget vergrößern, müssen wir das Pixmap auch auf diese
neue Größe bringen. Die errechneten Werte werden in w und h gespeichert. Bevor jedoch die Größe verändert wird, erzeugen wir eine
Kopie des Pixmaps in tmp. Dann verändern wir den Puffer (das Dokument), füllen es mit weißer Farbe und kopieren dann den Inhalt
von tmp in den Puffer zurück. Die ändert unser Pixmap immer synchron mit dem Widget, von dem es angezeigt wird, aber es verliert
nicht die Daten, die außerhalb des sichtbaren Bereiches liegen, wenn das Widget verkleinert wird.
Wir sind an einem Punkt angekommen, an dem wir die Funktionalität unserer Anwendung testen können. Drücken Sie "Ausführen", und nachdem KScribble angezeigt wird, sind Sie bereit, Ihr erstes Bild damit zu zeichnen !
Weiter Zurück Inhaltsverzeichnis