Pre DirectX Tutorial: Teil 4 - WinProc
Heya!
Dies wird der letzte Teil mit reinem Win API Zeugs sein. Wir werden einen Blick
auf den Event Handler (oder WinProc) werfen. Ein Thema welches schon so manchen
harten Programmierer in den Abgrund getrieben hat. Gute Aussichten, oder?
Fangen wir gleich richtig an. Ich sagte in einem früheren Teil das Windows
"event driven" ist. Intern werde Nachrichten an Programme verschickt,
mit denen das Programm über das System informiert wird: "wurde eine
Taste gedrückt", "geht gerade der Bildschirmschoner los?",
und so weiter. Ich sagte auch jedes Programm brauche so eine Art Schnittstelle
zum System, welche diese Nachrichten verwaltet. Und eben dies macht der Event
Hanlder unseres Programmes.
Der Event Handler eures Programmes wird von euch geschrieben, aber keine Sorge:
ihr müsst euch nicht um alle Nachrichten kümmern, die da ankommen.
Was kümmert es eure Demo, wenn ne Mail kommt? Ihr könnt also nur bestimmte
Nachrichten aussondern, diese bearbeiten und den Rest Windows überlassen.
Noch mal eine Rückblende. Als ihr euer Fenster registriert habt, kam die
Zeile WNDPROC lpfnWndProc; vor. Damit habt ihr eure WinProc angegeben. Die WinProc
ist euer Event Handler und euer fenster nutzt von nun an die angegebene WinProc
zum verabreiten von Windows Nachrichten.
In Wirklichkeit kommen diese Nachrichten nicht wild auf euer Programm zu. Es
hätte dann auch gar keine Zeit mehr sich um seine eigenen Aufgaben zu kümmern.
Windows legt die Nachrichten vielmehr in einer Art Zwischenspeicher ab. Dort
bleiben sie dann bis sie verarbeitet werden.
Um es wirklich glasklar zu machen ein Beispiel: Windows hat eine Nachricht,
die Nachricht wird auf den Stapel gelegt. Ihr schaut nach was auf dem Stapel
liegt. Wenn es euch interessiert, holt es ab und bearbeitet es. Wenn nicht,
dann lasst Windows sich darum kümmern.
Schauen wir uns mal eine solche WinProc an.
--------------------------------------------------------------------------------
LRESULT CALLBACK WinProc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
--------------------------------------------------------------------------------
Ein Blick auf die Parameter:
hwnd: Das ist unser Window Handle. Ist eigentlich nur wichtig, wenn wir mehrere
Fenster der gleichen Klasse geöffnet haben. hwnd überprüft in
dem Fall welche Nachrichten von welchem Fenster verursacht wurden.
msg: Das ist die eigentliche Nachricht, die WinProc verarbeitet. naja - in
Wirklichkeit ist es nur eine ID Nummer, kommt aber nahe dran.
Wparam/ Lparam: Für weitergehende Bearbeitung der Nachrichten.
Wichtig ist LResult Callback. Nicht vergessen. So. Nun läuft die ganze
Sache folgendermaßen ab: Wir holen die Nachricht ab. Schauen nach ob die
Nachricht ID in msg für uns interessant ist und wenn ja, kommt ein Stück
code wie wir weiter damit verfahren wollen. In msg stehen reine IDs, hier mal
eine Auswahl:
WM_ACTIVATE - Ein Fenster wird aktiviert.
WM_CLOSE - Ein Fenster wird geschlossen.
WM_CREATE - Ein Fenster wird neu erzeugt.
WM_DESTROY - Ein Fenster wird zerstört.
WM_MOUSEMOVE - Die Maus wird bewegt
Das sind natürlich nur ein paar. Wie immer gilt: schnappt euch ein Windows
Buch oder die Win API Hilfe Files von Microsoft um einen kompletten Überblick
zu bekommen.
Um es wirklich supereinfach zu machen interessiert us erst mal nur eine Nachricht:
WM_DESTROY. Die brauchen wir um festzustellen ob der User unser Fenster geschlossen
hat, damit wir unser Programm daraufhin beenden. Merke: nur weil der User das
Fenster zumacht wird unser Programm von Windows nicht automatisch beendet! Das
ist unsr Job! Hier also unsere WinProc:
--------------------------------------------------------------------------------
LRESULT CALLBACK WinProc( HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
PAINTSTRUCT ps; // used in WM_PAINT
HDC hdc; // handle to a device context
switch(msg)
{
case WM_CREATE:
{
// - 1 -
return(0);
} break;
case WM_PAINT:
{
// - 2 -
hdc = BeginPaint(hwnd,&ps);
EndPaint(hwnd,&ps);
return(0);
} break;
case WM_DESTROY:
{
// - 3 -
PostQuitMessage(0);
return(0);
} break;
default:break;
}
}
--------------------------------------------------------------------------------
Boah, oder? Es sieht wirklich komplizierter aus als es ist. Diese 3 "Break
Punkte" sind zum besseren Verständnis. Schritt für Schritt:
Erst eröffnen wir die WinProc mit dem üblichen Unsinn (siehe oben),
dann definieren wir einmal die PAINTSTRUCT, die brauchen wir für WM_PAINT.
Dann noch ein Handle zum Device Kontext: HDC. Dann fangen wir an die Nachrichten
zu bearbeiten: wir schaun erst mit switch nach welche Nachricht eigentlich in
msg gespeichert ist. Und dann folgt eine simple und einfache Bedingungsabfrage:
Wenn die Nachricht dasunddas ist, dann mache dies, wenn sie diesunddas ist,
dann mache das und so weiter.
Der erste Break Punkt wird erreicht, wenn das Fenster gerade geöffnet
wurde. Dann wird einfach nur 0 zurückgeliefert und alles ist Sahne.
Der zweite Break Punkt wird im Falle einer WM_PAINT Nachricht erreicht. das
bedeutet wahrscheinlich das ein User ein anderes Fenster über euers gelegt
hat und der Fensterinhalt neu gezeichnet werden woll. Bei Vollbild Fenstern
ist das weniger dramatisch, aber bei normalen Fenstern schon. Im Moment sagen
wir Windows einfach "Jaja, wir kümmern uns drum", damit es uns
nicht noch mehr Nachrichten schickt und fertig.
Zum dritten Break Punkt kommen wir wenn der User das Fenster schließt.
Wie gesagt läuft unser Programm dennoch weiter. Interpretieren wir aber
das schließen unseres Fensters als gewünschten Abbruch unseres Programms,
so sollten wir nun evtl. Speicher aufräumen und was nicht alles. In dem
Fall sagen wir wieder "Jaja, schon klar" und lassen es auf sich beruhen.
Wars das endlich? Naja...ja und nein - eure WinProc Routine ist fertig, ja.
Aber ist euch aufgefallen das wir nirgendwo die Nachrichten abholen und an die
WinProc weiterleiten? Das müssen wir in der WinMain, also unserer Hauptschleife
erledigen. Ihr erinnert euch vom allerersten Beispiel? Die WinMain ist die Hauptprozedur
unseres Programmes. Da fängts an.
Also los gehts:
--------------------------------------------------------------------------------
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
--------------------------------------------------------------------------------
Was tun wir da? PeekMessage schaut nach ob eine Nachricht vorhanden ist und
wenn ja, dann steht sie in &msg. Wenn der User das Fenster geschlossen hat
können wir uns die Arbeit auch sparen - ansonsten schau dir die Nachricht
an ud bearbeite sie.
Und das ist wirklich alles. Hmm...ich sehe schon wie ihr mit Fragezeichen im
Gesicht vor eurem Monitor sitzt, aber es ist wirklich einfacher als man denkt
- Übung macht hier den Meister. Also - im nächsten Kurs geht es um
eure Compiler, bis dahin...
Cheers,
Delax
[delax@sundancerinc.de]