Im folgenden soll das Programm insbesondere bzgl. der Benutzerfreundlichkeit erweitert werden. Das Programm besitzt gegenwärtig ein Fenster im klassischen Sinne. Wie bereits anfangs erwähnt, handelt es sich aber auch bei einem Textfeld, bei einer Schaltfläche oder bei einem Listenfeld um ein Fenster mit einer eigenen Fensterklasse. Letztere werden im Gegensatz zu dem von uns zuvor programmierten Fenster nie alleine existieren, d.h. sie werden immer zu einem übergeordnetem Fenster ("Parent") gehören. Das eine Windowsanwendung wie die unsere nur ein Fenster besitzt, ist äußerst selten. Vielmehr gibt es ein Hauptfenster, dem alle anderen Fenster untergeordnet sind und welches selber vornehmlich dafür zuständig ist, die untergeordneten Fenster ("Child") auszurichten (u.a. bei Größenänderungen) und eventuelle Benutzereingaben an untere Fenster weiterzugeben. Bevor wir uns bei dem Programm um die Benutzerfreundlichkeit kümmern, werden wir unser Programm um ein Fenster erweitern, oder besser gesagt unserem jetzigen Hauptfenster die Hauptarbeit abnehmen und sie an ein unterstelltes Fenster abgeben.
Um uns möglichst wenig Arbeit machen zu müssen, schreiben wir für unser Hauptfenster eine komplett neue Fensterprozedur (nennen wir sie 'MainWndProc') und verwenden die alte für unser erstes "Child"-Fenster nachdem wir sie in 'DrawWndProc' umgenannt haben. Gehen wir nun den Quelltext durch und ändern die Datei "main.cpp" Abschnitt für Abschnitt wie folgt:
Die Deklarationen:
//Windows-API #include <windows.h> //Deklaration der Hauptfensterprozedur LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM); //"neue" Fensterprozedur //Deklaration der Childfensterprozedur LRESULT CALLBACK DrawWndProc(HWND, UINT, WPARAM, LPARAM); //"alte" Fensterprozedur
Bei der Methode 'WinMain' muss eigentlich nur die Zeile geändert werden, in der die Fensterprozedur innerhalb der Fensterklasse angegeben wird:
wc.lpfnWndProc = MainWndProc;
Allerdings sollten zwei weitere Änderungen bzgl. Hintergrund und Cursor ergänzt werden:
wc.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); //neuer Hintergrund wc.hCursor = LoadCursor(NULL, IDC_ARROW); //grundsätzlich der Pfeil
Zur zweiten Zeile ist zu sagen, dass diese Änderung längst überfällig ist, da in der Anwendung beim Zeichnen vorher oft der Cursor nicht angepasst wurde. So wird jetzt grundsätzlich innerhalb des Anwendungsbereiches der Pfeil angezeigt. Die Farbe Grau als Hintergrundfarbe soll lediglich eine klare Abgrenzung zum "Child"-Fenster ermöglichen.
Die vorläufige Implementierung der entsprechenden Fensterprozedur 'MainWndProc' beschränkt sich auf folgende Zeilen:
//Implementierung der Hauptfensterprozedur LRESULT CALLBACK MainWndProc(HWND hWnd, UINT uiMessage, WPARAM wParam, LPARAM lParam) { switch (uiMessage) { case WM_CREATE: //Fenster wird erstellt return 0; case WM_CLOSE: //Fenster wird geschlossen DestroyWindow(hWnd); return 0; case WM_DESTROY: //Fenster wird zerstört PostQuitMessage(0); return 0; default: break; } return DefWindowProc(hWnd, uiMessage, wParam, lParam); }
Bei der "neuen alten" Fensterprozedur (jetzt 'DrawWndProc') wird die Behandlung der Nachricht 'WM_CLOSE' wieder gänzlich dem System überlassen und bei der Behandlung der Nachricht 'WM_DESTROY' wird der Aufruf von 'PostQuitMessage' ersatzlos gestrichen (Das Hauptfenster kümmert sich darum.)
Nun muss aber noch ein das "Child"-Fenster erstellt werden, in dem die Zeichenoperationen künftig durchgeführt werden. Auch hier muss zunächst die Fensterklasse erstellt und registriert werden, bevor das Fenster erstellt werden kann. Dies sollte in der Fensterprozedur des Hauptfensters 'MainWndProc' während der Bearbeitung der 'WM_CREATE'-Nachricht geschehen. Dazu muss zuvor eine neue Variable des Typs 'HWND' deklariert werden, die eben genau dieses Fenster repräsentiert. Eine Möglichkeit der Implementierung innerhalb der 'MainWndProc' sähe wie folgt aus:
//Implementierung der Hauptfensterprozedur LRESULT CALLBACK MainWndProc(HWND hWnd, UINT uiMessage, WPARAM wParam, LPARAM lParam) { //Child-Fenster static HWND hWndDraw = 0; WNDCLASS wc; switch (uiMessage) { case WM_CREATE: //Fenster wird erstellt wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = NULL; wc.hInstance = (HINSTANCE)GetWindowLong( hWnd, GWL_HINSTANCE); wc.lpfnWndProc = DrawWndProc; wc.lpszClassName = TEXT("DrawWnd"); wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; //Fensterklasse registrieren RegisterClass(&wc); //Fenster erstellen hWndDraw = CreateWindow(wc.lpszClassName, 0, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hWnd, NULL, wc.hInstance, NULL); return 0; case WM_CLOSE: //Fenster wird geschlossen DestroyWindow(hWnd); return 0; case WM_DESTROY: //Fenster wird zerstört PostQuitMessage(0); return 0; default: break; } return DefWindowProc(hWnd, uiMessage, wParam, lParam); }
Neu ist hier die Funktion 'GetWindowLong', mit der man von einem vorhandenen Fenster gewisse Daten, unter anderem auch die Instanz der Anwendung (wie hier geschehen) durch Angabe des Fenster-Handles ermitteln kann. Dass die Variable 'hWndDraw' für das erstellte Fenster "statisch" ist sollte hier nicht verwundern, da die Funktion 'MainWndProc' (von Windows) immer wieder aufgerufen wird, wobei eben auf das erstellte Fenster immer wieder zugegriffen werden muss, wie im folgenden zu sehen.
Weiterhin ist der kombinierte Fensterstil ('WS_CHILD' und 'WS_VISIBLE') zu erwähnen. 'WS_CHILD' gibt an, dass es sich um ein untergeordnetes Fenster handelt und 'WS_VISIBLE', dass es angezeigt werden soll. An dieser Stelle ist noch einmal zu erwähnen, dass immer dort wo Konstanten kombiniert werden sollen der '|'-Operator (bitweises Oder) verwendet wird.
Das Fenster ist zwar jetzt erstellt worden, ist aber noch keineswegs verwendbar, denn es hat ein Größe von 0 x 0 Pixeln. Zunächst soll das "Child"-Fenster die komplette Größe des "Parent"-Fensters ausfüllen, also auch wenn die Größe eben von diesem Fenster geändert wird. Dazu versendet Windows die Nachricht 'WM_SIZE'. Dabei wird die neue Größe jeweils in dem Parameter 'lParam' weitergegeben und zwar in zwei 16-Bit Werten für Breite und Höhe, die mit Hilfe der bekannten Makros 'LOWORD' und 'HIWORD' ermittelt werden können. Danach lässt sich das Child-Fenster mit Hilfe der Funktion 'MoveWindow' formatfüllend anzeigen. Die Behandlung der Nachricht 'WM_SIZE' könnte so aussehen:
case WM_SIZE: //Fenstergröße wird geändert //neue Fensterdimensionen bestimmen lWndWidth = LOWORD(lParam); //Breite lWndHeight = HIWORD(lParam); //Höhe //Childfenster formatfüllend anzeigen MoveWindow( hWndDraw, //zu verschiebendes Fenster 0, //X-Position 0, //Y-Position, lWndWidth, //Breite des Fensters lWndHeight, //Höhe des Fensters TRUE); //Fenster neu zeichnen (Aufruf von ShowWindow nun unnötig) return 0;
Dazu müssen natürlich zuvor noch die Variablen 'lWndWidth' und 'lWndHeight' deklariert werden. Diese sollten ebenfalls 'static' sein, denn so sind die aktuellen Dimensionen innerhalb der Fensterprozedur jederzeit geläufig und nicht nur während des Aufrufs von 'WM_SIZE'.
Da 'WM_SIZE' noch während des Anzeigevorgangs aufgerufen wird (also noch nach 'WS_CREATE') und nicht nur bei Größenänderungen reicht, eine Anpassung des "Child"-Festers hier aus.
Wenn, das Programm jetzt kompiliert und ausgeführt wird, unterscheidet es sich vom Aussehen und der Funktionalität her quasi nicht im Geringsten von dem letzten. Nur lassen sich jetzt bereits angesprochene Erweiterungen leicht ergänzen.
Hier sollte die Erstellung von "Child" und "Parent"-Fenstern verdeutlicht werden. Nebenbei wurden ein paar API-Funktionen sowie die Behandlung der Nachricht 'WM_SIZE' eingebunden. Beides wird in Folgelektionen immer wieder zur Anwendung kommen.
Hier noch einmal der komplette Quelltext dieses Programms:
Winprog_v1_2b_src.exe (Quelltext, gepackt)
In der nächsten Lektionen wird die Interaktion zwischen Benutzer und Programm mit Hilfe von ersten Steuerelementen ergänzt.