Das K Desktop Environment

9.2. Reimplementierung von Event Handlern

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:

   1     kscribbleview.cpp
   2 
   3 
   4     KScribbleView::KScribbleView(KScribbleDoc* pDoc, QWidget *parent, const char* name, int wflags)
   5      : QWidget(parent, name, wflags)
   6     {
   7         doc=pDoc;
   8 
   9 ->      setBackgroundMode( QWidget::NoBackground );
  10 ->      setCursor( Qt::crossCursor );
  11 ->    	mousePressed=false;
  12 ->      polyline=QPointArray(3);
  13     }

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 !

   1 void KScribbleView::mousePressEvent( QMouseEvent *e )
   2 {
   3   mousePressed = TRUE;
   4   polyline[2] = polyline[1] = polyline[0] = e->pos();
   5 }

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:

   1 void KScribbleView::mouseMoveEvent( QMouseEvent *e )
   2 {
   3   if ( mousePressed ) {
   4 		
   5     QPainter painter;
   6     painter.begin( &&;doc->buffer );
   7     painter.setPen( doc->currentPen() );
   8     polyline[2] = polyline[1];
   9     polyline[1] = polyline[0];
  10     polyline[0] = e->pos();
  11     painter.drawPolyline( polyline );
  12     painter.end();
  13 
  14     QRect r = polyline.boundingRect();
  15     r = r.normalize();
  16     r.setLeft( r.left() - doc->penWidth() );
  17     r.setTop( r.top() - doc->penWidth() );
  18     r.setRight( r.right() + doc->penWidth() );
  19     r.setBottom( r.bottom() + doc->penWidth() );
  20 
  21 	  doc->setModified();
  22     bitBlt( this, r.x(), r.y(), &&;doc->buffer, r.x(), r.y(), r.width(), r.height() );
  23   }
  24 }

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:

   1 void KScribbleView::mouseReleaseEvent( QMouseEvent * ) {
   2 	mousePressed = FALSE;
   3 }

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.