# 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.