Child-Fenster

Einführung

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.

Änderungen

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.

Zusammenfassung

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)

Die nächsten Lektionen

In der nächsten Lektionen wird die Interaktion zwischen Benutzer und Programm mit Hilfe von  ersten Steuerelementen ergänzt.

Zurück Nach oben Weiter