Das K Desktop Environment

11.3. Adaptieren der Ansicht

Wie oben gesagt, müssen wir zuerst einige Dinge in der Schnittstelle von KScribbleView ändern. Der folgende Code zeigt diese Änderungen:

   1 #include <qscrollview.h>
   2 
   3 class KScribbleView : public QScrollView
   4 {
   5   Q_OBJECT
   6 
   7   protected:
   8     /** changed from mousePressEvent() overwriting QScrollView method */
   9     virtual void viewportMousePressEvent( QMouseEvent* );
  10     /** changed from mouseReleaseEvent() overwriting QScrollView method */
  11     virtual void viewportMouseReleaseEvent( QMouseEvent* );
  12     /** changed from mouseMoveEvent() overwriting QScrollView method */
  13     virtual void viewportMouseMoveEvent( QMouseEvent* );
  14 
  15     /** commeted out because we have a document size defined */
  16 //    resizeEvent( QResizeEvent* );
  17 
  18     /** changed from paintEvent() overwriting QScrollView method */
  19     virtual void viewportPaintEvent( QPaintEvent* );
  20 }

An dieser Stelle haben wir zuerst QWidget durch QScrollView als Basisklasse ersetzt und in die erforderliche Headerdatei eingesetzt.Außerdem haben wir alle implementierten Event Handler, die mit Interaktion bezüglich des Inhalts der Rollansicht zu tun haben, durch die entsprechenden Methoden aus QScrollView ersetzt und haben das resizeEvent auskommentiert. Nun können wir mit der Implementation dieser Methoden beginnen und die Größe unseres Bildes verwenden. Da eine Ansicht immer erst nach dem Dokument existiert, können wir sowohl die Größe des Widgets (die Viewportgröße), als auch die des Inhalts direkt im Konstruktor anpassen.

   1 #include <qsize.h>
   2 
   3 KScribbleView::KScribbleView(KScribbleDoc* pDoc, QWidget *parent, const char* name, int wflags)
   4  : QScrollView(parent, name, wflags | WPaintClever | WNorthWestGravity | WRepaintNoErase)
   5 {
   6     doc=pDoc;
   7 		mousePressed=false;
   8     polyline=QPointArray(3);
   9 
  10 ->  setResizePolicy ( QScrollView::ResizeOne );
  11 ->  viewport()->setCursor( Qt::crossCursor );
  12 
  13 ->    QSize size=doc->docSize();
  14       // resize the viewport - this makes the resizeEvent obsolete
  15 ->    resizeContents(size.width(), size.height());
  16       // resize the widget to show up with the document size
  17 ->    resize(size);
  18 }

Beachten Sie, daß sich vorher resizeEvent() um die Gleichheit der Größe der Zeichenfläche und des Widgets gekümmert hat. Zur gleichen Zeit wurde dabei auch die Größe des Dokuments geändert, sodaß das Bild immer die gleiche Größe wie das Widget hatte. Da wir jetzt bereits die Größe des Dokuments initialisiert haben (in newDocument() und openDocument()), passen wir jetzt die Größe des Inhalts einfach durch Aufruf von resizeContents() aus QScrollView an die Größe des Dokuments an. Sie sehen auch, daß wir den Cursor für das Widget von dem allumfassenden Widget auf den Viewport geändert haben, den wir mit viewport() ermitteln können. Jetzt können wir wieder die Event Handler implementieren. Zuerst sollten wir auf das paintEvent achten, da dies eines der wichtigsten Events ist, weil es immer aufgerufen wird, wenn das Widget sichtbar wird oder seine Größe ändert.

Achtung: Denken Sie daran die resizeEvent() Implementation auszukommentieren!

Jetzt wird der paintEvent das Pixmap aus dem Puffer an die entsprechende Position der Ansicht kopieren müssen. Zu diesem Zweck müssen wir das Ziel von bitBlt() von this nach viewport() ändern, die linke, obere Position auf 0,0 setzen und das Ziel (den Puffer) so einstellen, daß von den contentsX und contentsY Positionen in den Viewport kopiert wird:

   1 void KScribbleView::viewportPaintEvent( QPaintEvent *e )
   2 {
   3   bitBlt( viewport(),0,0, &&;doc->buffer,contentsX() ,contentsY() );
   4 }

contentsX() ist die Position in X-Richtung der Rollansicht - die absolut der Position 0 des Viewports entspricht, und damit der linken, oberen Ecke der Ansicht. Das gleiche gilt auch für die Y-Richtung. Dieser Teil ist manchmal schwer zu verstehen und Sie müssen vielleicht ein wenig "Versuch und Irrtum" bei der Implementation Ihrer eigenen rollbaren Ansichten spielen. Der andere mögliche Aufruf von bitBlt() würde sein, die Werte der Positionen zu switchen und die Werte der Inhalte zu vertauschen:

bitBlt( viewport(), -contentsX(), -contentsY(), &&;doc->buffer, 0, 0 );

Die letzte Änderung, die wir noch brauchen, ist die Änderung des Mauseventhandlers. Im Moment hat mouseMoveEvent(), das zu viewportMouseMoveEvent() wird, auch einen bitBlt() Aufruf. Hier müssen wir die gleichen Änderungen vornehmen, wie beim paint Event. Weiterhin haben wir im mousePressEvent() und dem mouseMoveEvent() die Position der Events mit e->pos() geholt. Diese Abfrage liefert uns nun die Position eines Widgets - nicht die des Inhalts, also müssen wir dies mit viewportToContents() übersetzen, damit an die korrekte Position des Dokuments gezeichnet wird:

   1   void KScribbleView::viewportMousePressEvent( QMouseEvent *e )
   2   {
   3     mousePressed = TRUE;
   4 ->  doc->polyline[2] = doc->polyline[1] = doc->polyline[0] = viewportToContents(e->pos());
   5     doc->updateAllViews(this);
   6   }
   7 
   8   void KScribbleView::viewportMouseMoveEvent( QMouseEvent *e )
   9   {
  10     if ( mousePressed ) {
  11   ....
  12       doc->polyline[1] = doc->polyline[0];
  13 ->    doc->polyline[0] = viewportToContents(e->pos());
  14       painter.drawPolyline( doc->polyline );
  15   ....
  16       r.setBottom( r.bottom() + doc->penWidth() );
  17 
  18   	  doc->setModified();
  19 ->    bitBlt(viewport(), r.x()-contentsX(), r.y()-contentsY() ,
  20 ->            &&;doc->buffer, r.x(), r.y(), r.width(), r.height() );
  21   	  doc->updateAllViews(this);
  22     }
  23   }

In viewportMouseMoveEvent() mußten wir wieder das Ziel von this nach viewport() ändern - und damit die Positionen übersetzen. Diesmal haben wir die zweite Variante des Aufrufs, den wir in viewportPaintEvent() verwendet haben, benutzt, indem wir contentsX und contentsY subtrahiert haben um das Rechteck, das die aktuelle Zeichnung enthält, an die korrekte Stelle des Viewports zu kopieren.

Schließlich werden wir noch eine kleine Änderung an der update() Methode vornehmen: warum sollten wir jedesmal das ganze Widget neuzeichnen? Dies wird in den meisten Fällen die Performance herabsetzen und zu dem sogenannten "Flicker" Effekt führen. Dieser Effekt tritt manchmal bei Widgets auf, aber es gibt einige Möglichkeiten dieses Verhalten zu verringern. Statt repaint() aufzurufen, könnten wir ebensogut repaint(false) verwenden. Dadurch werden die Inhalte nicht vor dem Zeichnen gelöscht. Da wir den Inhalt des Dokuments direkt in das Widget kopieren, müssen wir die Daten nicht löschen, weil sie sowieso überschrieben werden. In Verbindung mit QScrollView können wir die Zeichenaktionen sogar noch weiter reduzieren: wir beschränken die update Methode darauf repaint() auf dem viewport() Widget aufzurufen, da dies wiederum viewportPaintEvent() aufruft. Außerdem können wir, wenn das Dokument kleiner als der Viewport ist, das paint Event noch auf das Rechteck des Viewports beschränken, in dem das Dokument angezeigt wird. Die sichtbare Höhe und Breite können wir uns holen und zu dem zu zeichnenden Rechteck zusammensetzen. Zusätzlich verwenden wir false als erase Parameter, so daß die Dokumentenfläche nicht gelöscht wird:

   1 void KScribbleView::update(KScribbleView* pSender){
   2 if(pSender != this)
   3 viewport()->repaint(0,0,visibleWidth(), visibleHeight(), false);
   4 }

Jetzt sind wir fertig ! Dieses Kapitel war sicher eines, der am schwierigsten zu implementierenden und zu verstehenden - insbesondere dort, wo es um die, sich ändernden Geometrien ging. Andererseits haben wir unserer Anwendung durch die rollbaren und synchronisierten Ansichten, eine vollkommen andere Funktionalität verliehen.

Damit begeben wir uns in das letzte Kapitel unserer Einführung. Dort werden wir nur noch einige, wenige Änderungen vornehmen und Gebrauch von einigen neuen Methoden der KDE 2 Bibliotheken machen, doch wie immer, wird uns dies wieder eine interessante Funktionalität eröffnen - KScribble wird in der Lage sein, eine ganze Reihe von Bildformaten zu öffnen und zu speichern, und damit wird die Einschränkung, nur mit png Dateien arbeiten zu können, entfallen.