Windows-Programmierung: das Grundgerüst
Willemers Informatik-Ecke
Dies ist ein Rahmenprogramm für eine einfache Windows-Applikation. Viele IDEs werden bei Erzeugung eines Win32-Windows-Applikations-Projektes einen solchen Rahmen erstellen. Das Listing ist mit Hyperlinks ausgestattet, so daß man zwischen Erklärung und Source hin- und herspringen kann.

MainWin, das Hauptprogramm einer Windows-Applikation

MainWin heißt das Hauptprogramm unter Windows. Per Parameter erhält es seine Instanz, auf der es seine Initialisierung aufsetzt. Es registriert die Fensterklasse, erzeugt das Hauptfenster und läuft in eine Schleife, in der es alle Messages verteilt, bis die Message WM_QUIT zu einem Rückgabewert 0 der Funktion GetMessage führt und damit die Schleife beendet. Dies führt dann zum Ende des Programms. GetMessage kann im Fehlerfall auch -1 zurückgeben. Auch dann ist ein Verlassen der Schleife angebracht.

Die Instanz

Per Parameter erhält MainWin die Instanz von Windows. Diese Variable wird bei Aufrufen von diversen Funktionen benötigt, die an Windows gehen. An dieser Variablen erkennt Windows die Applikation wieder, auch dann, wenn sie kein Fenster geöffnet hat. Aus diesem Grund wird sie von den meisten Windowsprogrammen in einer globalen Variable gespeichert und direkt zugegriffen.

MyRegisterClass

Das Hauptfenster wird registriert und bei Windows angemeldet. Die Registrierung ist die Anmeldung einer Fensterklasse. Es können also auch mehrere Fenster der gleichen Klasse angemeldet werden.

Der RegisterClass-String

Welches Fenster zu welcher Klasse gehört, wird nicht durch ein Handle, sondern durch eine Zeichenkette beschrieben. Da diese Zeichenkette exakt identisch sein muß, wird dieser String zu Anfang in einem define-Statement festgelegt oder sogar in den Ressourcen abgelegt. Wichtig ist, daß er das Fenster im laufenden System eindeutig bezeichnet. Er wird einmal bei der Registrierung der Klasse und dann noch einmal beim Erzeugen des Fensters verwendet.

Festlegen der Eigenschaften

Das Registrieren der Klasse gibt Windows bekannt, welche Eigenschaften die Fenster dieser Klasse haben werden. Die wichtigste Information ist dabei die Adresse der Fensterfunktion WndProc. Da die Fensterklasse auf alle Ereignisse reagiert, bestimmt sie maßgeblich das Verhalten des Fensters.

Der Hintergrund des Fensters wird durch die Elementvariable hbrBackground festgelegt. Wird dieser Wert - wie im Listing - mit (HBRUSH)(COLOR_WINDOW+1) festgelegt, ist der Fensterhintergrund weiß, was sich besonders für das Zeichnen eignet. Wird stattdessen (HBRUSH)(COLOR_WINDOW) verwendet, hat das Fenster einen grauen Hintergrund, der geeigneter ist, wenn Kontrollelemente in das Fenster gesetzt werden.

InitMainWindow

In InitMainWindow wird das Fenster erzeugt (create) und angezeigt. In vielen Beispielen wird diese Funktion als InitInstance benannt.

Das Fenster wird durch den RegisterClass-String als zu der registrierten Klasse gehöriges Fenster erzeugt.

Als zweiten String hat das Fenster szTitle. Das ist der Text, der im blauen Balken zu sehen ist. Er hat nur informellen Charakter und könnte auch leer sein. Da dieser String oft sprachenabhängig ist, würde es Sinn machen, ihn in der Ressource abzulegen.

Die Fensterfunktion

Die Fensterfunktion WndProc behandelt diejenigen Ereignisse, die auf das Fenster einstürmen. Die Funktion wird von Windows gerufen, wenn immer ein Ereignis für dieses Fenster vorliegt.

Windows liefert vier Parameter.

Die Default-Fensterfunktion DefWindowProc

Werden Nachrichten nicht behandelt oder werden nur Teilaspekte des Ereignisses verarbeitet, so ruft die Fensterfunktion die Funktion DefWindowProc und übergibt ihr die Parameter, die sie von Windows erhalten hat. DefWindowProc ist die Default-Fensterfunktion und behandelt alle Ereignisse mit der Standardreaktion. So wird beispielsweise das Fenster vergrößert oder mit der Hintergrundfarbe weiß versehen.

Mausereignisse

Ein Mausklick mit der linken Taste führt zu einer Nachrich WM_LBUTTONDOWN beim Herunterdrück und WM_LBUTTONUP beim Loslassen der Maustaste. Diese Nachrichten müssen in einem case in der Fensterfunktion abgefangen werden. Die Mausposition wird in lParam übergeben.
int x = GET_X_LPARAM(lParam);
int y = GET_Y_LPARAM(lParam);
In den meisten Fällen wird ein Mausereignis dazu führen, dass das Fenster neu gezeichnet wird. Durch den Aufruf von
RedrawWindow(hWnd, 0, 0, RDW_INVALIDATE);
wird das Fenster sofort neu gezeichnet. Dagegen wird mit dem Aufruf von
InvalidateRect(hWnd, 0, 0);
die Meldung in die Warteschlange gestellt, dass das Rechteck im zweiten Parameter ungültig geworden ist und bei Gelegenheit neu gezeichnet werden sollte. Ist der zweite Parameter wie hier 0, wird das ganze Fenster neu gezeichnet.

Das Menü

Definition

In der Ressource wird das Menü beschrieben

Anmeldung

Das Menü wird beim Registrieren der Fensterklasse dem Fenster zugeordnet. Dazu wird dem Element lpszMenuName der Fensterklassenstruktur der in der Ressource definierte Namen des Menüs zugewiesen.

Ereignis der Menüauswahl

Das Anklicken eines Menüpunktes löst eine Nachricht WM_COMMAND aus, die in der Fensterfunktion verarbeitet wird. Welcher Menüpunkt ausgewählt wurde, ist im niedrigeren Teil des ersten Parameters wParam kodiert. Für das Extrahieren stellt Windows das Makro LOWORD zur Verfügung.

Der Fensterinhalt

Alles, was im Fenster gezeichnet oder angezeigt werden soll, wird nicht direkt auf das Fenster gezeichnet. Das hätte den Nachteil, daß alles verschwunden ist, wenn das Fenster kurzzeitig überdeckt wurde. Ein Fenster erhält in dem Falle, daß der Inhalt neu zu zeichnen ist, die Nachricht WM_PAINT. Hier muß es seinen Inhalt neu darstellen. Den gleichen Mechanismus benutzt man, wenn man im Fenster zeichnen will. Man manipuliert die Daten, aus denen die Fensterfunktion das Fenster rekonstruiert und ruft die Funktion Invalidate auf, und täuscht so dem Fenster vor, daß eine Neuzeichnung notwendig sei.
InvalidateRect(hWnd, 0, 0);
Die Parameter:

Beenden einer Windows-Anwendung

Der Menüpunkt für das Ende der Applikation ruft WindowDestroy auf. Dies erzeugt wiederum die Nachricht WM_DESTROY an das eigene Fenster. Hier erst setzt die Anwendung den Befehl PostQuitMessage(0); ab, das wiederum eine Nachricht absetzt. Das "Post" führt aber im Gegensatz zu "Send" dazu, daß an dieser Stelle nicht auf die Abarbeitung gewartet wird.

Die Quit-Nachricht wird nicht in der Fensterfunktion bearbeitet, sondern bewirkt ein Unterbrechen der Main-Loop und führt so zum Ende der Applikation.

Der scheinbare Umweg über das Nachrichtensystem führt dazu, daß alle beteiligten Komponenten informiert sind, daß dieses Programm sich abbaut.

Der Quelltext

#include <windows.h>
#include "resource.h"

HINSTANCE hInst;
#define szTitle "Fenstertitel"
#define szWindowClass "MyWindowClass"

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;

	switch (message) {
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		}
		break;
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		// do the painting of the window
		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	}
	return DefWindowProc(hWnd, message, wParam, lParam);
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex = {0};

	wcex.cbSize        = sizeof(WNDCLASSEX); 
	wcex.style         = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc   = (WNDPROC)WndProc;
	wcex.hInstance     = hInstance;
	wcex.hIcon         = LoadIcon(hInstance, (LPCTSTR)IDI_RAHMEN);
	wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName  = (LPCSTR)IDC_RAHMEN;
	wcex.lpszClassName = szWindowClass;
	wcex.hIconSm       = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
	return RegisterClassEx(&wcex);
}

BOOL InitMainWindow(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;

	hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
	if (!hWnd) return FALSE;

	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	return TRUE;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine, int       nCmdShow)
{
MSG msg;
HACCEL hAccelTable;

	MyRegisterClass(hInstance);
	hInst = hInstance;

	if (!InitMainWindow (hInstance, nCmdShow)) return FALSE;

	hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_RAHMEN);

	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}
	return msg.wParam;
}

Die Ressourcen

#include "resource.h"

#include <windows.h>

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.

IDI_RAHMEN       ICON    DISCARDABLE     "Rahmen.ICO"
IDI_SMALL        ICON    DISCARDABLE     "SMALL.ICO"


IDC_RAHMEN MENU DISCARDABLE
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "E&xit",                IDM_EXIT
    END
    POPUP "&Help"
    BEGIN
        MENUITEM "&About ...",           IDM_ABOUT
    END
END

IDC_RAHMEN ACCELERATORS MOVEABLE PURE
BEGIN
    "?",            IDM_ABOUT,              ASCII,  ALT
    "/",            IDM_ABOUT,              ASCII,  ALT
END

STRINGTABLE DISCARDABLE 
BEGIN
   IDC_RAHMEN    "RAHMEN"
   IDS_APP_TITLE "Rahmen"
   IDS_HELLO     "Hello World!"
END

Die Headerdatei der Ressourcen

#define IDR_MAINFRAME		128
#define IDD_RAHMEN_DIALOG	102
#define IDS_APP_TITLE		103
#define IDM_ABOUT		104
#define IDM_EXIT		105
#define IDS_HELLO		106
#define IDI_RAHMEN	        107
#define IDI_SMALL		108
#define IDC_RAHMEN	        109
#define IDC_MYICON		2
#define IDC_STATIC	        -1