The K Desktop Environment

Weiter Zurück Inhaltsverzeichnis

9. Definition der Ansicht

9.1 Interaktion mit dem Benutzer

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;

     }

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:


    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.

9.3 Zeichnen und Größe des Dokuments ändern.

Ü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