Vom Programmierer zum Maler


Wie jedes gewöhnliche Fenster bekommt unser Eingabefeld die Nachricht "WM_PAINT" wenn es gezeichnet werden muss. Die Standard-Fensterprozedur sorgt normalerweise dafür, dass es wie gewohnt erscheint: vertieft und mit 3D-Effekt. Da nun aber unsere eigene Fensterprozedur etabliert ist, können wir die Nachricht "WM_PAINT" abfangen und das Eingabefeld durch unsere Routinen zeichnen lassen:

case uMsg of
  WM_PAINT:
  begin
    { ... }
  end;
end;

Machen wir doch mal ein Experiment und kommentieren alles aus, was zwischen begin und end steht. Und was sehen wir? Wir sehen, dass wir nichts sehen. (Sorry, aber den Kalauer konnte ich mir nicht verkneifen. :o))
Aber warum sehen wir nichts mehr? Weil wir die Nachricht abgefangen haben, allerdings nichts zeichnen. Genau dafür sind wir aber verantwortlich!

Beschäftigen wir uns also ein wenig damit. Der Vergleich mit einem Maler ist gar nicht so weit hergeholt. Wie auch ein Maler haben wir unsere Leinwand, auf die wir zeichnen, und wir haben auch unsere Stifte zum Zeichnen von Linien und unseren Pinsel zum Ausmalen von Flächen.

BeginPaint - EndPaint

Als erstes brauchen wir Zugriff auf unsere Leinwand (engl. canvas, und so heißt es auch unter Windows), welche durch unser Fenster repräsentiert wird. Dazu benötigen wir ein Handle auf die Leinwand. In Windows spricht man in diesem Zusammenhang von einem DeviceContext (DC). Unser DC vom Datentyp HDC (in Delphi auch nur ein Ganzzahl-Datentyp) wird uns von der Funktion BeginPaint geliefert:

dc := BeginPaint(hEdit, ps);

Der erste Parameter ist das Handle des Fensters, das von unserer Zeichenaktion betroffen ist. Der zweite Parameter ist ein Zeiger auf eine Variable vom Typ TPAINTSTRUCT. Auf die einzelnen Membervariablen dieses Records möchte ich an dieser Stelle nicht eingehen, da sie für uns nicht von Interesse sind.
Wichtig ist für uns nur der Rückgabewert der Funktion, nämlich unser DC. Außerdem ist noch zu sagen, dass "BeginPaint" das Zeichnen einleitet, während sein Gegenstück EndPaint Windows mitteilt, dass die Zeichenaktion abgeschlossen ist.

EndPaint(hEdit, ps);

Lässt man diese Funktion weg, weiß Windows nichts davon und sendet unserem Fenster weiterhin "WM_PAINT"-Nachrichten. Im ungünstigsten Fall wird unsere Anwendung nur noch diese Nachrichten verarbeiten und kommt zu nichts anderem mehr.


Der Zeichenstift

Genau wie unser Maler müssen auch wir zum Zeichnen einen Stift auswählen:

pen := CreatePen(PS_SOLID, 2, RGB(0,0,0));

Mit CreatePen wählen wir einen Stift aus bzw. erzeugen einen. Der Rückgabewert ist ein Handle auf den Stift. Der erste Parameter, "fnPenstyle", kann eine der folgenden Konstanten sein:

WertBedeutung
PS_SOLID Der Stift zeichnet eine durchgehende Linie.
PS_DASH Der Stift zeichnet gestrichelt.
PS_DOT Der Stift zeichnet gepunktet.
PS_DASHDOT Der Stift zeichnet abwechselnd gestrichelt und gepunktet.
PS_DASHDOTDOT Der Stift zeichnet abwechselnd gestrichelt, gefolgt von zwei Punkten.
PS_NULL Der Stift ist unsichtbar.
PS_INSIDEFRAME Dieser Stift zeichnet einen durchgehenden Rahmen und verringert die Maße des Elements, damit es in diesen Rahmen passt.

(Eine ausführlichere Beschreibung finden Sie im MSDN oder im Platform SDK.)

Der zweite Parameter, nWidth, ist die Breite des Stiftes in Pixeln, und der letzte Parameter, "Color" ist die gewünschte Farbe, die mit der Funktion "RGB()" erzeugt werden kann (s. Codebeispiel).

Mit SelectObject nehmen wir den Stift nun in die Hand. Unter Windows spricht man auch von "in den DC selektieren".

OldObject := SelectObject(dc, pen);

Der erste Parameter ist der DC, in den wir das Objekt selektieren wollen, und der zweite Parameter ist das Handle des Objektes; in dem Fall: unser eben erzeugter Stift. Als Rückgabewert erhalten wir ein Handle des zuvor in den DC selektierten Objekts. Diesen merken wir uns, da wir das gewählte Objekt mit DeleteObject wieder aus dem DC löschen müssen. Und damit der DC nicht leer zurückgelassen und wieder in den Urzustand versetzt wird, selektieren wir beim Löschen wieder das alte Objekt:

DeleteObject(SelectObject(dc, OldObjct));


Linien zeichnen

Auch wieder wie im richtigen Leben bewegen wir unseren virtuellen Zeichenstift zu dem Punkt, an dem die Linie beginnen soll. Und dann ziehen wir sie bis zu dem Punkt, an dem sie aufhören soll:

MoveToEx(dc, rect.Left-2, rect.Top, nil);
LineTo(dc, rect.Left-2, rect.Bottom-7);
LineTo(dc, rect.Right, rect.Bottom-7);


Rechtecke füllen

Den typischen weißen Hintergrund unseres Eingabefeldes erzeugen wir mit Hilfe folgender Codezeilen:

brush := CreateSolidBrush(RGB(255,255,255));
FillRect(dc, rect, brush);

Zuerst erzeugen wir einen so genannten "Brush". Wer sich mit den Farbwerten auskennt, sieht schon auf den ersten Blick, dass die Werte für Rot, Blau und Grün am Ende unser gewünschtes Weiß ergeben. Auf diese Weise lassen sich alle beliebigen Farben definieren. Ergebnis ist aber immer ein einfarbiger, solider Brush.
Wer Größeres vorhat, kann sich auch mit "CreateBrushIndirect" und eines zugehörigen LOGBRUSH-Records austoben. (Näheres dazu bitte im MSDN oder PSDK nachschlagen.)

Genau wie der Zeichenstift muss auch der Brush in den DC selektiert werden. Eigentlich. In unserem Fall ist dies nicht nötig, da das die Funktion "FillRect" für uns übernimmt.

Anmerkung

Man hätte sich das Erzeugen eines weißen Brushes sparen können und im Aufruf von FillRect eine Null als dritten Parameter übergeben können. Der Null-Brush ist nämlich weiß. (Ich wollte nur mal zeigen, was ich drauf habe. ;o))


Die komplette neue Fensterprozedur finden Sie im Beispielprogramm.