Die MessageBox
Methode, um mit dem Benutzer in Interaktion zu treten, ist zwar
ziemlich einfach, aber (fast) genauso einfach ist dann auch die Ebene der Kommunikation.
Was wir also brauchen ist ein eigenes Fenster, das wir so gestalten können, wie wir
das wollen und wie wir das brauchen. In diesem Tutorial lernst du zwar nur das
Grundgerüst eines Fensters, also ein leeres, zu schreiben, aber glaube mir, das ist
schon schwer genug. In den folgenden Tutorials lernst du dann, wie man das Fenster mit
Informationen füllt. Jedoch ist an unserem Fenster schon alles 'dran, was
ein Fenster ausmacht. Das Fenster hat eine Titelleiste, ein Systemmenü, den
Anwendungsbereich und besitzt die Möglichkeit vergrößert oder verkleinert
zu werden. Als erstes definieren wir wieder den Bezeichner STRICT
. Danach
müssen wir die windows.h
Headerdatei einbinden,
damit dem Compiler alle Prototypen und Konstanten Definitionen zur Verfügung stehen.
#define STRICT #include <windows.h>
Das Betriebssystem Windows arbeitet ereignisorientiert, das heißt, dass die Programme
nicht nach Informationen bezüglich Systemänderungen oder Benutzerereignissen
fragen, sondern sie bekommen diese Informationen bei Bedarf frei Haus geliefert. Etwas
technischer Beschrieben, ruft Windows eine Funktion des Programms auf, wenn eines
dieser Ereignisse eingetreten ist. Diese Funktion ist vom Programm definiert und
bestimmt so den Ablauf des Programms. So eine Funktion (auch Window Procedur
oder
kurz WndProc
genannt) ist jedoch nicht an ein Programm gebunden, sondern an
ein Fenster. Es kommt daher häufig vor, dass ein Programm mehrere solcher Funktionen
unterhält. Über die Parameter der WndProc
Funktion bekommt man
die Art (also, was passiert ist) der Nachricht übergeben. Denn, falls es noch nicht
deutlich geworden ist, eine WndProc
Funktion ist für alle Ereignisse
eines Fensters zuständig. Die Palette reicht vom skalieren des Fensters über
einen Mouseklick bis hin zum Empfangen von Daten.
Die WndProc
Funktion gibt einen LRESULT
Wert zurück.
LRESULT
ist ein typedef
auf einen long
Typ.
CALLBACK
ist genau das gleiche, wie WINAPI
(in Visual
Studio läuft das wieder auf __stdcall
hinaus), jedoch sollte man
zu Dokumentationszwecken diese beiden Konstanten unterscheiden. CALLBACK
sagt aus, dass diese Funktion von Windows aufgerufen wird. Und vielleicht ist
in einer folgenden Windows Version wieder ein Unterschied zwischen diesen Aufrufkonventionen.
Der Name der WndProc
Funktion ist frei wählbar. Der erste Parameter ist der
Handle zu dem Fenster, für das die Nachricht bestimmt ist. Ein Handle ist eine Kennziffer,
ein Verweis auf ein Objekt. Mit der nackten Zahl kann man nichts anfangen. Man gibt den
Handle an Windows Funktionen weiter, die sich dann das Objekt aus dem Speicher laden
können. Die Kennziffer ist in einer Tabelle dem Speicherplatz zugeordnet. So können
die Daten auf der Festplatte ausgelagert werden oder die Daten werden aus defragmenierungs
Gründen verschoben. HWND
steht also für Handle [to a] Window.
Der zweite Parameter message
enthält die Kennziffer der Nachricht.
Für alle Nachrichten sind in winuser.h
Konstanten deklariert. Die
nächsten beiden Parameter enthalten je nach Nachricht verschiedene Daten.
WPARAM
ist vom Typ unsigned int
. Das W
stammt noch
aus alten Tagen, als ein int
noch 16 Bit breit war, damals war WPARAM
also noch ein Word
.
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
Den Programmnamen speichern wir in einer globalen Konstante. Dies hat den Vorteil, dass wir im kompletten Programm nur einmal dafür Speichern benötigen und wir den Programmnamen ganz einfach ändern können.
const char szAppName[] = "Ein eigenes Fenster";
Wie in jedem Windows API Programm brauchen wir die WinMain
Funktion. Der erste
Parameter HINSTANCE
ist ein Handle auf diese Programminstanz. Mit dieser
Kennziffer kann man das ausführende Programm identifizieren. Der zweite Parameter
ist vom gleichen Typ. Jedoch wird dieser Wert in Win 32 immer Null sein. In den
alten 16 Bit Programmen teilten sich mehrere Instanzen eines Programms den Adressraum,
deshalb war in hPrevInstance
immer der Handle zu der vorherigen Instanz
gespeichert. In Win 32 sind die Adressräume streng von einander getrennt, weswegen
ein Handle auf die vorherige Instanz wenig Sinn haben würde. Der dritte Parameter
enthält die Kommandozeilen Parameter. Jedoch sind
sie in Windows noch nicht so schön aufgeteilt, wie in Dos (argc
und
argv
). Man muss sich bei Bedarf einen kleinen Parser schreiben. Mit dem
vierten Parameter wird übergeben, wie das eventuelle Fenster angezeigt werden soll
(maximiert, minimiert usw.). Mehr über die Funktion im MSDN: WinMain
.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
Wer C++ programmiert, kann die Variablen auch erst bei dem ersten Auftreten deklarieren und
dann gleich initalisieren.
Die erste Variable hWnd
soll später den Handle für unser Fenster
speichern. Die Struktur msg
wird gebraucht, um Windows Nachrichten abzuholen
und zu bearbeiten (im MSDN: MSG
). Die WNDCLASS
Struktur benutzen wir, um
den Typ der Fensterklasse festzulegen (im MSDN: WNDCLASS
).
HWND hWnd; MSG msg; WNDCLASS wc;
Windows ist in Bezug auf die Art der Fenster sehr vielfältig. Man muss also bevor man ein
Fenster erstellt ersteinmal genau festlegen, wie das Fenster aussehen soll. Man erstellt also
eine Fensterklassen. Dazu füllt man die WNDCLASS
Struktur mit Werten und
übergibt diese dann der RegisterClass
Funktion, die die Fensterklasse
registriert. Die Membervariable style
legt das technische Verhalten der
Fensterklasse fest. Wir geben zum Beispiel an, dass das Fenster bei einer Änderung
der Größe sowohl in horizontaler als auch in vertikaler Richtung neu gezeichnet
werden soll. In lpfnWndProc
(Long Pointer [to a] Function) wird die Adresse
der Funktion gespeichert, die die Nachrichtenbearbeitung übernehmen soll. In C/C++
ist der Name einer Funktion ohne den Klammeroperator() die Adresse der Funktion.
wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc;
Mit diesen Variablen kann man den zusätzlichen Speicher für die Fensterklasse bzw. das Fenster reservieren. In diesem Tutorial brauchen wir keinen extra Speicher.
wc.cbClsExtra = 0; wc.cbWndExtra = 0;
hInstance
müssen wir den Handle zu unserer Programminstanz übergeben,
damit nur unser Programm diese Fensterklasse benutzen kann. Mit hCursor
kann man
den Typ des Cursors bestimmen. Wenn man hier Null angibt, erscheint auf unserem Fenster kein
Cursor. Wir laden mit der Funktion LoadCursor
(im MSDN: LoadCursor
) den standard Pfeilcursor. Die
hIcon
Membervariable speichert einen Handle auf ein Icon. Auch hier laden wir ein
standard Icon mit der Funktion LoadIcon
(im MSDN: LoadIcon
). Dann müssen wir noch über die
Variable hbrBackground
(hbr
ist die Abkürzung für
Handle [to a] Brush) die Hintergrundfarbe des Fensters festlegen. Diesmal laden wir einen
weißen Hintergrund über die GetStockObject
Funktion (im MSDN: GetStockObject
). In C++ muss man den Rückgabetyp
noch auf HBRUSH
casten.
wc.hInstance = hInstance; wc.hCursor = LoadCursor(NULL,IDC_ARROW); wc.hIcon = LoadIcon(NULL,IDI_APPLICATION); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
Die lpszClassName
Membervariable soll den Fensterklassennamen speichern. Über
diesen Namen kann man dann später Fenster dieser Klasse erzeugen. Die Hauptfensterklasse
bekommt üblicherweise den Namen des Programms. Da unser Fenster (noch) kein Menü
haben soll, geben wir bei der lpszMenuName
Variablen Null an.
wc.lpszClassName = szAppName; wc.lpszMenuName = NULL;
Die RegisterClass
Funktion (im MSDN: RegisterClass
) registriert bzw. meldet unsere
Fensterklasse beim Fenstermanager von Windows an.
RegisterClass(&wc);
Mit der CreateWindow
Funktion (im MSDN: CreateWindow
) erstellen wir ein Fenster nach unserer
registrierten Fensterklasse. Dem ersten Parameter lpClassName
wird der Name
der Fensterklasse als Zeichenkette übergeben. Über den zweiten Parameter können
wir den Text in der Titelleiste beeinflussen. Im dritten Parameter speichern wir den Stil
des Fensters. Die nächsten vier Parameter sind für die räumliche Position des
Fensters verantwortlich. Der vorletzte Parameter muss wieder der Handle auf unsere Programm
Instanz sein, damit man das Fenster unserem Programm zuordnen kann. Die anderen drei
Parameter sind im Moment unwichtig, daher belassen wir sie bei Null. Die
CreateWindow
Funktion liefert als Rückgabewert den Handle auf das Fenster
zurück.
hWnd = CreateWindow(szAppName, "Titelleiste", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, /* X-Position auf dem Monitor */ CW_USEDEFAULT, /* Y-Position auf dem Monitor */ CW_USEDEFAULT, /* Fensterbreite */ CW_USEDEFAULT, /* Fensterhoehe */ NULL, NULL, hInstance, NULL);
Unser Fenster ist nun erstellt, es erscheint jedoch noch nicht auf dem Bildschirm. Dazu
müssen wir erst das Startzeichen geben. Dies tuen wir mit der ShowWindow
Funktion (im MSDN: ShowWindow
). Der erste Parameter ist der Handle
auf unser Fenster. Den zweiten Parameter iCmdShow
übernehmen wir aus
den Übergabeparametern der WinMain
Funktion. Man kann an der Stelle von
iCmdShow
auch Konstanten einsetzen, die dann festlegen, wie das Fenster
angezeigt werden soll (z.B. SW_SHOW
). Die UpdateWindow
Funktion
(im MSDN: UpdateWindow
) lässt den Anwendungsbereich,
also den freien Fensterbereich, sofort nach dem Start neu zeichnen.
ShowWindow(hWnd, iCmdShow); UpdateWindow(hWnd);
Das Fenster ist nun erstellt und wird angezeigt. Nun warten wir auf die Eingabe bzw.
Aktivitäten des Benutzers. Wenn etwas passiert, informiert uns Windows über die
entsprechende Nachricht. Jedoch werden uns die Nachrichten nicht aufgezwängt, sonder
sie werden in einer Warteschleife abgelegt, von der wir sie erst abholen müssen.
Dies tut die GetMessage
Funktion (im MSDN: GetMessage
) solange sie einen Wert ungleich Null
zurückliefert. Und Null wird nur zurückgegeben, wenn wir das Programm durch
einen Aufruf von PostQuitMessage
beenden wollen. Wenn keine Nachricht vorhanden
ist, dann bleibt unser Programm in der Funktion stehen und wartet auf die nächste
Nachricht. In dem ersten Parameter wird die Nachricht gespeichert. Im zweiten Parameter
können wir uns auf das Abholen von Nachrichten für nur ein Fenster beschränken.
Wenn wir dies wollten, müssten wir hier den Handle des Fensters eintragen. Jedoch
würde dann unser Programm nicht korrekt beenden, da zum Beispiel die Nachricht, die
PostQuitMessage
sendet nicht für unser Fenster bestimmt ist, da dies ja
schon zerstört wurde. Mit dem dritten und vierten Parameter kann man die Art der
Nachrichten beschränken. Es werden nur Nachrichten abgeholt, die zwischen diesen beiden
Werten liegen (bedenke, dass die Nachrichten als int
dargestellt werden).
Eine Ausnahme ist, wenn beide Parameter Null sind, dann werden alle Nachrichten
abgeholt.
while (GetMessage(&msg, NULL, 0, 0)) {
Den ersten Bearbeitungsschritt macht die TranslateMessage
Funktion, die jedoch
nur für die Verarbeitung von Tastaturnachrichten gebraucht wird. Die Funktion wird dann
in dem entsprechenden Tutorial erklärt. Den letzten und wichtigsten Schritt macht
jedoch die DispatchMessage
Funktion (im MSDN: DispatchMessage
), sie verteilt die Nachrichten an
die jeweilige Windows Prozedur, also auch an unsere WndProc
Funktion.
TranslateMessage(&msg); DispatchMessage(&msg); }
Wie oben schon gesagt, muss das Programm mit der Rückgabe eines Integer Wertes
beendet werden. Dieser Wert steht im wParam
der letzten Nachricht, die
GetMessage
bearbeitet hat.
return msg.wParam; }
Die WndProc
Funktion bearbeitet alle Nachrichten, die für unsere
Fensterklasse bestimmt sind.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
Als erstes müssen wir herausfinden, welche Nachricht uns Windows (oder ein anderes
Fenster) geschickt hat. Dies machen wir am besten mit einer switch
/case
Konstruktion, denn wir wollen normalerweise eine Variable mit viele Konstanten vergleichen.
switch (message) {
Wenn die Nachricht vom Typ WM_DESTROY
ist, dann senden wir mit Hilfe der
PostQuitMessage
Funktion (im MSDN: PostQuitMessage
) die Nachricht WM_QUIT
,
die die GetMesssage
Funktion veranlasst, dass Programm zu beenden. Der Parameter
wird später als Rückgabewert der WinMain
Funktion benutzt. Wenn
wir eine Nachricht bearbeitet haben, beenden wir die WndProc
Funktion mit der
Rückgabe von Null.
case WM_DESTROY: { PostQuitMessage(0); return 0; } }
Da man bei den meisten Nachrichten nichts spezielles machen möchte, kann man diese
Nachrichten an die DefWindowProc
Funktion (im MSDN: DefWindowProc
) weitergeben, die alle
Nachrichten mit einer standard Bearbeitung behandelt. Die Parametervariablen, die wir
durch die WndProc
Funktion erhalten haben, geben wir einfach an die
DefWindowProc
Funktion ("Default Window Procedur") weiter.
return DefWindowProc(hWnd, message, wParam, lParam); }
Dieses Programm bringt ein einfaches und leeres Fenster auf den Bildschirm (Screenshot).
In den folgenden Tutorials wird die WinMain
Funktion zum Großteil immer
gleich bleiben. Das eigentliche geschehen findet ja auch in der WndProc
Funktion
statt, obwohl das in diesem Tutorial noch nicht so deutlich werden mag. Windows ist ein
Ereignis gesteuertes System, das heißt, dass ein Programm nur aufgrund eines Ereignis'
(zum Beispiel ein Mouseklick) zum Leben erwacht. Wenn nichts zu tun ist, verbraucht ein
Programm keine Rechenzeit, da es in der GetMessage
Funktion stecken geblieben ist
und nicht eine ständig abfragende Schleife durchläuft. Denn Windows ist ein
Multitask fähiges Betriebssystem, das bedeutet, dass mehrere Programme (fast) gleichzeitig
laufen können. Wenn also ein Programm gerade nichts zu tun hat, kann die Rechenzeit einem
anderen Programm zugeschoben werden. Die neueren Windows Versionen (Win 32)
unterstützen auch noch Multithreating. Dies heißt wiederum, dass ein Programm aus
mehreren Prozessen bestehen kann, die auch praktisch gleichzeitig laufen.
MessageBox
Funktion ausgibt. Die WinMain
Funktion soll
nicht geändert werden.
WinMain
Funktion auf drei Funktionen. Teile die
Aufgaben Sinnvoll auf.