Ich habe bereits in Ersatzteilkatalog - Bildnavigation mit GDI+
erzählt, dass ich versuche in Visual Foxpro 9 mit GDI+ direkt auf VFP
Formen zu zeichnen. Inzwischen funktioniert das eigentlich ganz gut.
Einige Probleme waren noch zu lösen und den Bildaufbau habe ich noch
optimiert.
In Craig Boyd's Artikelserie in FoxTalk 2.0
wird eine komplette Form mit GDI+ bearbeitet. In unserer SPCat
Applikation ist aber nur ein Bereich der Form für die Darstellung der
Bilder genutzt. Der restliche Bereich der Form soll weiterhin von
Foxpro selbst gezeichnet werden. Deswegen wollte ich die ganze
Bilddarstellung in eine Container Klasse verpacken und die eigentliche Form so wenig wie möglich verändern.
Die eigentlichen GDI+ Zeichenroutinen stelle ich nicht vor. Es geht
mir jetzt mehr darum wie ich diese Routinen mit den Mechanismen von VFP
verheiratet habe, damit Foxpro und GDI+ Elemente auf einer Form
dargestellt werden. Foxpro weiß ja nichts davon, dass man ihm mit
eigenen Routinen in die Bildschirmdarstellung pfuscht. Deswegen wird es
bei jedem Update des Bildschirminhalts einfach seine eigenen Elemente zeichnen.
Mit BINDEVENT hat man die Möglichkeit in die Nachrichten von Windows an
das Foxpro Fenster zum Neuzeichnen des Inhalts einzugreifen. Hiermit
werden die relevanten Events auf die eigene Prozedur WinEventHandler umgeleitet:
#DEFINE WM_ERASEBKGND 0x0014
#DEFINE WM_PAINT 0x000F
=BINDEVENT(THISFORM.hwnd, WM_ERASEBKGND, THIS, 'WinEventHandler')
=BINDEVENT(THISFORM.hwnd, WM_PAINT, THIS, "WinEventHandler")
Wir brauchen auch noch zwei Aufrufe in die Windows API:
DECLARE LONG CallWindowProc IN User32 ;
INTEGER lpPrevWndFunc, LONG HWND, ;
INTEGER Msg ,INTEGER wParam, ;
INTEGER lParam
DECLARE INTEGER GetUpdateRect IN User32 LONG HWND, STRING @lpRect, INTEGER bErase
CallWindowProc ruft die Routine auf, die normalerweise den
WM_PAINT Event behandelt. Also in diesem Fall die interne Foxpro Funktion, die dessen Elemente zeichnet.
GetUpdateRect liefert das Rechteck innerhalb dessen etwas zu
zeichnen ist. Damit kann man den Einsatz der eigenen Zeichenroutinen
auf die benötigten Fälle reduzieren. Dieses Rechteck ist als folgendes Struct definiert:
typedef struct _RECT {
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT, *PRECT;
In der WinEventHandler Prozedur werden die Windows Nachrichten bearbeitet:
PROCEDURE WinEventHandler
LPARAMETERS HWND, msg, wParam, LPARAM
#DEFINE WM_ERASEBKGND 0x0014
#DEFINE WM_PAINT 0x000F
DO CASE
CASE msg = WM_PAINT
WITH THIS
IF .autodraw
LOCAL lpRect,lnUpdate
lpRect=SPACE(16)
* Das Rechteck holen, in dem gezeichnet werden muss.
lnUpdate=GetUpdateRect(HWND, @lpRect, 0)
* Dieser
Aufruf führt dazu, dass VFP alles zeichnt, wofür es zuständig ist.
CallWindowProc(.WinProcAddress, HWND, msg, wParam, lParam)
IF lnUpdate=1
* In hitdetection() wird geprüft, ob dieses
Control im zu zeichnenden Rechteck liegt.
IF .hitdetection(lpRect)
.drawdb()
ENDIF
ENDIF
ELSE
* Hier ist nur VFP für das Zeichnen verantwortlich.
CallWindowProc(.WinProcAddress, HWND, msg, wParam, lParam)
ENDIF
ENDWITH
CASE msg = WM_ERASEBKGND
RETURN 1
ENDCASE
Die Nachricht zum Löschen des Hintergrunds WM_ERASEBKGND
wird komplett ignoriert. Mit WM_PAINT treffen alle relevanten
Zeichnungsanforderungen ein. Mit der autodraw Eigenschaft kann ich die
eigenen GDI+ Routinen an- und ausschalten. Das ist notwendig, da das
Control auch unsichtbar sein kann. Dann dürfen natürlich meine
GDI+ Routinen nicht auf der Form zeichnen. Bei der Behandlung des
WM_PAINT Events darf zuerst Foxpro seine Aufgabe erledigen. Danach
teste ich, ob der Container im zu zeichnenden Bereich liegt und starte
entsprechende meine Zeichenroutine. Diese Reihenfolge ist wichtig,
damit am Ende auch meine Ausgabe zu sehen ist. Sonst würde ja Foxpro
den tatsächlichen Container darüber malen.
Damit kann ich jetzt grundsätzlich auf die Fläche des Containers zeichnen und zusätzlich auf der Form weitere Foxpro Elemente anzeigen. Ich möchte aber den Container und damit die GDI+ Zeichnung unsichtbar machen können. Genau genommen möchte ich an der Stelle des Containers auch andere Objekte anzeigen können. Das läuft aber auch auf einen unsichtbaren Container hinaus.
Natürlich kann ich nicht einfach die Visible Eigenschaft auf .F.
stellen. Dann würde zwar Foxpro den leeren Container nicht mehr
zeichnen, aber meine Routinen würden munter weiter zeichnen. Über
visible_assign werden bei Änderungen der Sichtbarkeit auch meine
Zeichenroutinen mit umgestellt.
Es hat sich aber gezeigt, dass VFP seltsamerweise Teile des
Containers zeichnet wenn Visible auf .T. gesetzt, obwohl der Container
bereits sichtbar ist. Das führt leider zu einem Flackern. Da ich ja
selbst erst nach Foxpro zeichne und damit darunterliegende Objekte kurz aufblitzen. Also wird
dieser Fall auch in visible_assign abgefangen:
PROCEDURE visible_assign
LPARAMETERS vNewVal
WITH THIS
IF m.vNewVal
IF .Visible
* Wenn das Control schon sichtbar
ist, nicht nochmal auf sichtbar schalten.
* Andernfalls würde VFP das Control zeichnen und es flackert.
.clearborder()
ELSE
* Die eigene GDI+ Zeichenroutine anschalten.
.redraw()
.Visible=.T.
ENDIF
ELSE
* Die eigene GDI+ Zeichenroutine abschalten.
.autodraw=.F.
.Visible=.F.
ENDIF
ENDWITH
Es kann sein, dass das Bild, das mit GDI+ gezeichnet wird, kleiner als der Container ist. In WM_PAINT Routine wird aus Performance Gründen nur dieser Teil gezeichnet. Damit das Rest des Containers in der Hintergrundfarbe erscheint, lösche ich diesen Bereich von Hand mit clearborder(). Da bei einem Wechsel des Bildes auch die Visible Eigenschaft auf .T. gesetzt wird, ist das die einzige Stelle, wo dieser Rand manuell gelöscht werden muß.
Insgesamt bin ich mit dem Ergebnis jetzt sehr zufrieden. Leider
feuert aus mir unbekannten Gründen unregelmässig ein WM_PAINT Event,
der offensichtlich grundlos die ganze Form neu zeichnen lässt. Das
führt zu einem Flackern. Insbesondere auf langsameren Rechner kann das
stören. Trotzdem bin ich beeindruckt, was mit VFP 9, GDI+ und ein paar
Tricks möglich ist.