Kurze Einführung in die Windows ... - GIS-Management
Kurze Einführung in die Windows ... - GIS-Management
Kurze Einführung in die Windows ... - GIS-Management
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
Objektorientierte Programmierung<br />
Prof. Dr. –Ing. Franz-Josef Behr<br />
FACHHOCHSCHULE<br />
HOCHSCHULE FÜR<br />
STUTTGART<br />
TECHNIK<br />
Fachbereich Vermessung und Geo<strong>in</strong>formatik<br />
<strong>Kurze</strong> E<strong>in</strong>führung <strong>in</strong> <strong>die</strong> W<strong>in</strong>dows Programmierung<br />
mittels API<br />
1 E<strong>in</strong>leitung<br />
Wenn Sie <strong>in</strong> den vergangenen Semestern e<strong>in</strong> C++-Programm entwickelt und gestartet haben, geschieht<br />
im Pr<strong>in</strong>zip nicht viel mehr, als dass Code für Programm und Daten <strong>in</strong> den Arbeitsspeicher Ihres<br />
Rechners geladen werden und <strong>die</strong> Ausführung mit dem E<strong>in</strong>sprungspunkt, also der Funktion ma<strong>in</strong>()<br />
beg<strong>in</strong>nt.<br />
Bei W<strong>in</strong>dows-basierten Programmen läuft e<strong>in</strong>iges mehr und anders ab. Dieses „Mehr“ und „Andere“<br />
soll <strong>in</strong> den nachfolgenden Abschnitten vorgestellt werden. Dabei bauen <strong>die</strong> vorgestellten<br />
Beispielprogramme auf dem W<strong>in</strong>dows 32-Bit API (application programm<strong>in</strong>g <strong>in</strong>terface), e<strong>in</strong>er<br />
geme<strong>in</strong>samen Programmierschnittstelle für alle 32-Bit W<strong>in</strong>dows Plattformen auf.<br />
Die hierbei e<strong>in</strong>gesetzten Pr<strong>in</strong>zipien werden Ihnen das Verständnis<br />
? für <strong>die</strong> objektorientierte Entwicklung von W<strong>in</strong>dows-Programmen<br />
? für bestimmte Zusammenhänge beim E<strong>in</strong>satz der COM-Technologie<br />
erleichtern.<br />
Das W<strong>in</strong>dows API ist e<strong>in</strong>e Sammlung von Befehlen, mit denen man Zugriff auf Funktionen und<br />
Objekte <strong>in</strong> W<strong>in</strong>dows hat. API-Befehle s<strong>in</strong>d nicht an e<strong>in</strong>e e<strong>in</strong>zige Programmiersprache gebunden. So<br />
können API - Befehle <strong>in</strong> C++, Visual Basic, Delphi und <strong>in</strong> anderen Sprachen e<strong>in</strong>gesetzt werden. Die<br />
Befehle s<strong>in</strong>d immer gleich, unterschiedlich ist oft der Zugriff auf vordef<strong>in</strong>ierte Strukturen bed<strong>in</strong>gt<br />
durch <strong>die</strong> unterschiedliche Syntax der Programmiersprachen.<br />
2 Nachrichtenschleifen und Fensterprozeduren<br />
Wir beg<strong>in</strong>nen mit unserem ersten W<strong>in</strong>dows-Programm, das gerade mal vier Zeilen umfasst:<br />
#<strong>in</strong>clude <br />
<strong>in</strong>t WINAPI W<strong>in</strong>Ma<strong>in</strong>(HINSTANCE d1, HINSTANCE d2, LPSTR d3, <strong>in</strong>t d4)<br />
{<br />
MessageBox(NULL, "Hello, World!", "", MB_OK);<br />
}<br />
In der ersten Zeile wird <strong>die</strong> W<strong>in</strong>dows API-Headerdatei e<strong>in</strong>gebunden. In <strong>die</strong>se Datei werden wiederum<br />
andere Dateien e<strong>in</strong>gebunden, so dass Sie schließlich alle Prototypen und Konstanten <strong>in</strong> Ihrem<br />
Programm deklariert und verfügbar haben, <strong>die</strong> zum Ablauf API-basierter Programme nötig se<strong>in</strong>. Diese<br />
<strong>in</strong>clude-Anweisung muss deshalb <strong>in</strong> jedem Programm, dass Teile der W<strong>in</strong>dows API benutzt,<br />
vorhanden se<strong>in</strong>!
E<strong>in</strong>leitung 2<br />
Nun zu den verwendeten Funktionen!<br />
2.1 W<strong>in</strong>Ma<strong>in</strong><br />
Die W<strong>in</strong>Ma<strong>in</strong>-Funktion ist das Gegenstück zur ma<strong>in</strong>-Funktion e<strong>in</strong>es Konsolenprogramms und stellt<br />
den E<strong>in</strong>stiegspunkt <strong>in</strong> e<strong>in</strong>e W<strong>in</strong>32-Applikation. dar:<br />
W<strong>in</strong>Ma<strong>in</strong> verfügt über folgende Übergabeparameter:<br />
<strong>in</strong>t WINAPI W<strong>in</strong>Ma<strong>in</strong>(<br />
HINSTANCE hInstance, // handle to current <strong>in</strong>stance<br />
HINSTANCE hPrevInstance, // handle to previous <strong>in</strong>stance<br />
LPSTR lpCmdL<strong>in</strong>e, // po<strong>in</strong>ter to command l<strong>in</strong>e<br />
<strong>in</strong>t nCmdShow // show state of w<strong>in</strong>dow<br />
);<br />
Die Parameter haben folgende Bedeutung:<br />
? hInstance : Handle auf <strong>die</strong> aktuelle Instanz der Applikation.<br />
? hPrevInstance: Handle auf e<strong>in</strong>e andere, früher gestartete Instanz der Applikation. Für e<strong>in</strong>e<br />
W<strong>in</strong>32-basierende Applikation ist <strong>die</strong>ser Parameter immer NULL.<br />
? lpCmdL<strong>in</strong>e: Po<strong>in</strong>ter auf e<strong>in</strong>en null-term<strong>in</strong>ierte Zeichenkette, der <strong>die</strong> Kommandozeile des<br />
Aufrufs <strong>die</strong>ses Programms enthält. Der Programmname ist dar<strong>in</strong> nicht enthalten. Um <strong>die</strong><br />
Kommandozeile vollständig zurückzuerhalten, können Sie ggf. <strong>die</strong> GetCommandL<strong>in</strong>e-<br />
Funktion nutzen.<br />
? nCmdShow: Spezifiziert, wie das Fenster angezeigt wird:<br />
Value<br />
Mean<strong>in</strong>g<br />
SW_HIDE<br />
SW_MINIMIZE<br />
SW_RESTORE<br />
SW_SHOW<br />
SW_SHOWMAXIMIZED<br />
Hides the w<strong>in</strong>dow (and activates another one).<br />
M<strong>in</strong>imizes the specified w<strong>in</strong>dow and activates the<br />
top-level w<strong>in</strong>dow <strong>in</strong> the system's list.<br />
Activates and displays a w<strong>in</strong>dow. If the w<strong>in</strong>dow is<br />
m<strong>in</strong>imized or maximized, the system restores it to its<br />
orig<strong>in</strong>al size and position (same as<br />
SW_SHOWNORMAL).<br />
Activates a w<strong>in</strong>dow and displays it <strong>in</strong> its current size<br />
and position.<br />
Activates a w<strong>in</strong>dow and displays it as a maximized<br />
w<strong>in</strong>dow.
E<strong>in</strong>leitung 3<br />
SW_SHOWMINIMIZED<br />
SW_SHOWMINNOACTIVE<br />
SW_SHOWNA<br />
SW_SHOWNOACTIVATE<br />
SW_SHOWNORMAL<br />
Activates a w<strong>in</strong>dow and displays it as an icon.<br />
Displays a w<strong>in</strong>dow as an icon. The active w<strong>in</strong>dow<br />
rema<strong>in</strong>s active.<br />
Displays a w<strong>in</strong>dow <strong>in</strong> its current state. The active<br />
w<strong>in</strong>dow rema<strong>in</strong>s active.<br />
Displays a w<strong>in</strong>dow <strong>in</strong> its most recent size and<br />
position. The active w<strong>in</strong>dow rema<strong>in</strong>s active.<br />
Activates and displays a w<strong>in</strong>dow. If the w<strong>in</strong>dow is<br />
m<strong>in</strong>imized or maximized, the system restores it to its<br />
orig<strong>in</strong>al size and position (same as SW_RESTORE).<br />
2.1.1 Handles<br />
In der Beschreibung <strong>die</strong>ser Parameter tauscht der Begriff „Handle“ (Handgriff) auf. E<strong>in</strong> Handle liefert<br />
den Bezug zu e<strong>in</strong>em W<strong>in</strong>dows-Objekt. Stellen Sie sich vor, dass alle W<strong>in</strong>dows-Objekte (Fenster,<br />
E<strong>in</strong>gabefelder, Schaltflächen, ...) durchnummeriert s<strong>in</strong>d. E<strong>in</strong> Handle (d.h. e<strong>in</strong>e Handle-Variable)<br />
enthält dann <strong>die</strong> Nummer des zugehörigen Objekts.<br />
Es existieren verschiedene Untertypen von Handles, z. B. Handles für Fenster (Datentyp HWND 1 ),<br />
Handles für Ausgabekontexte (HDC), für Menüs (HMENU) usw.<br />
2.1.2 Instanz<br />
W<strong>in</strong>dows ist e<strong>in</strong> Multitask<strong>in</strong>g-Betriebssystem: Mehrere Programme laufen gleichzeitig ab, e<strong>in</strong><br />
Programm kann auch mehrfach gestartet werden. Sie können zwischen den verschiedenen<br />
Programmen h<strong>in</strong>- und herwechseln.<br />
Jedes laufende Programm wird als Instanz bezeichnet. Wird e<strong>in</strong>e Programm mehrfach gestartet, wird<br />
nur e<strong>in</strong>e Instanz des Programmcodes <strong>in</strong> den Speicher geladen, <strong>die</strong> Daten müssen natürlich getrennt <strong>in</strong><br />
eigenen Speicherbereichen gehalten werden.<br />
2.2 Die MessageBox Funktion<br />
Die MessageBox Funktion gibt <strong>in</strong> e<strong>in</strong>em separaten Fenster e<strong>in</strong>e Nachricht aus. Der erste Parameter<br />
soll normalerweise e<strong>in</strong> Handle auf e<strong>in</strong> Fenster se<strong>in</strong>, zu dem <strong>die</strong> Nachricht gehören soll, aber da wir<br />
ke<strong>in</strong> Fenster besitzen, geben wir Null an. Dies bedeutet, dass <strong>die</strong> Nachricht als eigenständiges<br />
Fenster dargestellt wird.<br />
3 Hello World mit e<strong>in</strong>facher Nachrichtenschleife<br />
Das folgende Programm hello2.c stellt e<strong>in</strong>e den „Hello World“-Text <strong>in</strong> e<strong>in</strong>em eigenen Fenster dar:<br />
1 HWND steht also für Handle [to a] W<strong>in</strong>dow.
E<strong>in</strong>leitung 4<br />
Der zugehörige Source-Code ist <strong>in</strong> folgender Box zusammengestellt:<br />
#<strong>in</strong>clude <br />
<strong>in</strong>t WINAPI W<strong>in</strong>Ma<strong>in</strong>(HINSTANCE hInstance, HINSTANCE d2,<br />
LPSTR d3, <strong>in</strong>t d4)<br />
{<br />
MSG msg;<br />
HWND hwnd;<br />
}<br />
hwnd = CreateW<strong>in</strong>dow("BUTTON", "Hello, World!",<br />
WS_VISIBLE | BS_CENTER, 100, 100, 100, 80,<br />
NULL, NULL, hInstance, NULL);<br />
while (GetMessage(&msg, NULL, 0, 0))<br />
{<br />
if (msg.message == WM_LBUTTONUP)<br />
{<br />
DestroyW<strong>in</strong>dow(hwnd);<br />
PostQuitMessage(0);<br />
}<br />
DispatchMessage(&msg);<br />
}<br />
return msg.wParam;<br />
BUTTON steht für e<strong>in</strong>en bestimmten, vordef<strong>in</strong>ierten Fenstertyp, der als Fensterklasse bezeichnet wird.<br />
Zum Erzeugen des Fensters registriert das Programm zunächst <strong>die</strong>se Fensterklasse:<br />
hwnd = CreateW<strong>in</strong>dow("BUTTON", "Hello, World!",<br />
WS_VISIBLE | BS_CENTER, 100, 100, 100, 80,<br />
NULL, NULL, hInstance, NULL);<br />
Der return-Wert <strong>die</strong>ser Funktion ist e<strong>in</strong> Handle auf das erzeugte Fenster. Es handelt sich tatsächlich<br />
um e<strong>in</strong> Fenster, auch wenn es nur e<strong>in</strong> Button (= Schaltfläche) ist – unter W<strong>in</strong>dows ist fast alles (wie<br />
der Name ja auch deutlich macht) e<strong>in</strong> Fenster!<br />
Selbstverständlich ist es möglich, eigene Fensterklassen zu def<strong>in</strong>ieren, wie wir es später auch tun<br />
werden!<br />
Nach <strong>die</strong>ser Registrierung wird e<strong>in</strong>e Nachrichtenschleife durchlaufen. Dieser Mechanismus gehört<br />
zum wesentlichen Bestandteil von W<strong>in</strong>dows-Anwendungen. Jedes Ereignis, etwa e<strong>in</strong>e<br />
Mausbewegung, e<strong>in</strong> Tastendruck usw., veranlasst W<strong>in</strong>dows, <strong>die</strong>sen „Event“ <strong>in</strong> Form e<strong>in</strong>er Meldung<br />
(message) an das Programm weiterzuleiten, das sich se<strong>in</strong>erseits um <strong>die</strong> Abarbeitung kümmern muss.<br />
while (GetMessage(&msg, NULL, 0, 0))<br />
{<br />
if (msg.message == WM_LBUTTONUP)<br />
{<br />
DestroyW<strong>in</strong>dow(hwnd);<br />
PostQuitMessage(0);
E<strong>in</strong>leitung 5<br />
}<br />
}<br />
DispatchMessage(&msg);<br />
Mit der GetMessage-Funktion holen wir uns e<strong>in</strong>e Meldung (Nachricht) ab und untersuchen sie. In<br />
<strong>die</strong>sem Beispiel werten wir nur <strong>die</strong> Meldung WM_LBUTTONUP aus und rufen gegebenenfalls <strong>die</strong><br />
DestroyW<strong>in</strong>dow- und PostQuitMessage-Funktion auf.<br />
Die DestroyW<strong>in</strong>dow-Function löscht das angegeben Fenster. Besitzt das Fenster K<strong>in</strong>dfenster,<br />
werden <strong>die</strong>se ebenfalls entfernt.<br />
Mit PostQuitMessage wird e<strong>in</strong>e WM_QUIT-Nachricht an das System gesandt, um dem System<br />
anzuzeigen, dass <strong>die</strong> Anwendung beendet werden will. Als Parameter wird der ExitCode der<br />
Anwendung (meist 0) übergeben. Vielfach wird PostQuitMessage als Reaktion auf <strong>die</strong> Nachricht<br />
WM_DESTROY <strong>in</strong> der Nachrichtenschleife der Anwendung aufgerufen.<br />
Wie Sie bereits festgestellt haben, verwendet man <strong>in</strong> der W<strong>in</strong>API-Programmierung eigene<br />
Bezeichnungen für Datentypen. Die nachfolgende Zusammenstellung zeigt <strong>die</strong> Zusammenhänge<br />
zwischen W<strong>in</strong>API und C/C++: 2<br />
Bezeichnung <strong>in</strong> C / C++ Bemerkung<br />
TRUE 1, true<br />
FALSE 0, false<br />
NULL 0 wird <strong>in</strong>sbesondere für Po<strong>in</strong>ter auf "Nichts" verwendet<br />
UINT unsigned <strong>in</strong>t 16bit / 32bit<br />
BYTE unsigned char 8bit<br />
WORD unsigned short 16bit<br />
DWORD unsigned long 32bit<br />
LONG long 32bit<br />
VOID void 3 z.B. als Rückgabewert von Funktionen<br />
LPSTR char* Po<strong>in</strong>ter auf e<strong>in</strong>en Str<strong>in</strong>g (char-Array)<br />
LPCSTR const char* Po<strong>in</strong>ter auf e<strong>in</strong>en konstanten Str<strong>in</strong>g (char-Array)<br />
HANDLE void* Handle für verschiedenste W<strong>in</strong>dows-Elemente<br />
HWND - Handle e<strong>in</strong>es Fensters<br />
PASCAL pascal WINAPI = FAR PASCAL<br />
WPARAM unsigned <strong>in</strong>t 16bit / 32bit<br />
LPARAM long 32bit<br />
2 Quelle: http://www.henkessoft.de/api1.htm<br />
3 engl.: leer, nichts
E<strong>in</strong>leitung 6<br />
LRESULT long 32bit<br />
HINSTANCE - Handle e<strong>in</strong>er Instanz<br />
MSG<br />
Meldung<br />
4 Fensterprozeduren<br />
Tabelle 1: Wesentliche Datentypen <strong>in</strong> W<strong>in</strong>dows.<br />
Die nächste Version unseres Programms registriert se<strong>in</strong>e eigene Fensterklasse:<br />
Der Source dazu wird um e<strong>in</strong>iges umfangreicher:<br />
#<strong>in</strong>clude <br />
void DrawHello(HWND hwnd)<br />
{<br />
HDC hDC;<br />
PAINTSTRUCT pa<strong>in</strong>tStruct;<br />
RECT clientRect;<br />
}<br />
hDC = Beg<strong>in</strong>Pa<strong>in</strong>t(hwnd, &pa<strong>in</strong>tStruct);<br />
if (hDC != NULL)<br />
{<br />
GetClientRect(hwnd, &clientRect);<br />
DPtoLP(hDC, (LPPOINT)&clientRect, 2);<br />
DrawText(hDC, "Hello, World!", -1, &clientRect,<br />
DT_CENTER | DT_VCENTER | DT_SINGLELINE);<br />
EndPa<strong>in</strong>t(hwnd, &pa<strong>in</strong>tStruct);<br />
}<br />
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg,<br />
WPARAM wParam, LPARAM lParam)<br />
{<br />
switch(uMsg)<br />
{<br />
case WM_PAINT:<br />
DrawHello(hwnd);<br />
break;<br />
case WM_DESTROY:<br />
PostQuitMessage(0);<br />
break;<br />
default:<br />
return DefW<strong>in</strong>dowProc(hwnd, uMsg, wParam, lParam);
E<strong>in</strong>leitung 7<br />
}<br />
}<br />
return 0;<br />
<strong>in</strong>t WINAPI W<strong>in</strong>Ma<strong>in</strong>(HINSTANCE hInstance, HINSTANCE hPrevInstance,<br />
LPSTR d3, <strong>in</strong>t nCmdShow)<br />
{<br />
MSG msg;<br />
HWND hwnd;<br />
WNDCLASS wndClass;<br />
char* szMa<strong>in</strong>WndClass="W<strong>in</strong>TestW<strong>in</strong>"; //Name of ma<strong>in</strong> w<strong>in</strong>dow class<br />
}<br />
if (hPrevInstance == NULL)<br />
{<br />
memset(&wndClass, 0, sizeof(wndClass));<br />
wndClass.style = CS_HREDRAW | CS_VREDRAW;<br />
wndClass.lpfnWndProc = WndProc;<br />
wndClass.hInstance = hInstance;<br />
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);<br />
wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);<br />
wndClass.lpszClassName = szMa<strong>in</strong>WndClass;<br />
wndClass.lpszMenuName = NULL;<br />
if (!RegisterClass(&wndClass)) return FALSE;<br />
}<br />
hwnd = CreateW<strong>in</strong>dow(szMa<strong>in</strong>WndClass, "HELLO",<br />
WS_OVERLAPPEDWINDOW,<br />
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,<br />
NULL, NULL, hInstance, NULL);<br />
ShowW<strong>in</strong>dow(hwnd, nCmdShow);<br />
UpdateW<strong>in</strong>dow(hwnd);<br />
while (GetMessage(&msg, NULL, 0, 0))<br />
DispatchMessage(&msg);<br />
return msg.wParam;<br />
E<strong>in</strong>e Vorbemerkung: Vielleicht ist Ihnen aufgefallen, dass alle Variablennamen mit e<strong>in</strong>em Präfix<br />
beg<strong>in</strong>nen? Ich er<strong>in</strong>nere an <strong>die</strong> ungarische Notation – <strong>die</strong>se wird <strong>in</strong> W<strong>in</strong>dows-Programmen <strong>in</strong>tensiv<br />
genutzt!<br />
Alle Variablennamen, <strong>die</strong> nach der ungarischen Notation geschrieben s<strong>in</strong>d, beg<strong>in</strong>nen mit e<strong>in</strong>em Präfix<br />
<strong>in</strong> Kle<strong>in</strong>buchstaben, <strong>die</strong> den Typ der Variable wiedergeben.<br />
Präfix<br />
Datentyp<br />
c<br />
char<br />
by<br />
BYTE (unsigned char)<br />
n<br />
short (<strong>in</strong>t)<br />
i<br />
<strong>in</strong>t (signed <strong>in</strong>t)<br />
x, y <strong>in</strong>t (x- und y-Koorrd<strong>in</strong>aten)<br />
cx, cy<br />
<strong>in</strong>t (Längenangaben <strong>in</strong> e<strong>in</strong>em Koord<strong>in</strong>atensystem, das c steht für 'count')<br />
b, f bool; das f steht für 'Flag'<br />
w<br />
WORD (unsigned short)<br />
l<br />
long<br />
dw<br />
DWORD (unsigned long)<br />
fn<br />
Funktion
E<strong>in</strong>leitung 8<br />
s<br />
sz<br />
h<br />
p<br />
Pp<br />
Str<strong>in</strong>g<br />
ASCIIZ-Str<strong>in</strong>g (genauer: char* abgeschlossen mit '\0')<br />
Handle<br />
Zeiger (po<strong>in</strong>ter)<br />
Doppel Zeiger (**pp)<br />
Nun zurück zu unserem Programm. Se<strong>in</strong>e Ausführung beg<strong>in</strong>nt wie immer mit W<strong>in</strong>Ma<strong>in</strong>.<br />
<strong>in</strong>t WINAPI W<strong>in</strong>Ma<strong>in</strong>(HINSTANCE hInstance, HINSTANCE hPrevInstance,<br />
LPSTR d3, <strong>in</strong>t nCmdShow)<br />
Zu Beg<strong>in</strong>n werden e<strong>in</strong>ige Variablen def<strong>in</strong>iert, deren Datentypen Sie Tabelle 1 entnehmen können:<br />
{<br />
MSG msg;<br />
HWND hwnd;<br />
WNDCLASS wndClass;<br />
wndClass werden wir gleich nutzen. S<strong>in</strong>d Instanzen der Applikation vorhanden, braucht <strong>die</strong>se<br />
Fensterklasse nicht neu <strong>in</strong>itialisiert werden, andernfalls def<strong>in</strong>ieren wir ihre Eigenschaften:.<br />
if (hPrevInstance == NULL)<br />
{<br />
memset(&wndClass, 0, sizeof(wndClass));<br />
wndClass.style = CS_HREDRAW | CS_VREDRAW;<br />
wndClass.lpfnWndProc = WndProc;<br />
wndClass.hInstance = hInstance;<br />
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);<br />
wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);<br />
wndClass.lpszClassName = "HELLO";<br />
wndClass.lpszMenuName = NULL;<br />
if (!RegisterClass(&wndClass)) return FALSE;<br />
Diese Fensterklasse def<strong>in</strong>iert das grundlegende Verhalten e<strong>in</strong>es Fensters (Aufbau, Fenstersymbol,<br />
Menü, ...). Außerdem wird <strong>die</strong> Adresse e<strong>in</strong>er Funktion <strong>in</strong> der Klasse gespeichert, <strong>die</strong> als<br />
Fensterprozedur bezeichnet wird. Wir haben es hierbei mit dem für uns seltenen Fall zu tun, dass wir<br />
bei<br />
wndClass.lpfnWndProc = WndProc;<br />
e<strong>in</strong>en Zeiger auf e<strong>in</strong>e Funktion verwenden!<br />
W<strong>in</strong>dows bietet e<strong>in</strong>e Reihe vordef<strong>in</strong>ierter Fensterklassen (vgl. BUTTON, Kap. 3), es ist aber genauso<br />
möglich, eigene über <strong>die</strong> Funktion RegisterClass zu def<strong>in</strong>ieren (wie <strong>in</strong> <strong>die</strong>sem Fall).<br />
? Der Eigenschaft hInstance müssen wir den Handle zu unserer Programm<strong>in</strong>stanz übergeben,<br />
damit nur unser Programm <strong>die</strong>se Fensterklasse benutzen kann.<br />
? Mit hCursor kann man den Typ des Cursors bestimmen. Wenn man hier Null angibt,<br />
ersche<strong>in</strong>t auf unserem Fenster ke<strong>in</strong> Cursor. Wir laden jedoch mit der Funktion LoadCursor<br />
den Standard-Pfeilcursor.
E<strong>in</strong>leitung 9<br />
? Dann müssen wir noch über <strong>die</strong> Variable hbrBackground 4 <strong>die</strong> H<strong>in</strong>tergrundfarbe des<br />
Fensters festlegen.<br />
Über GetStockObject (BLACK_BRUSH); können Sie alternativ vordef<strong>in</strong>ierte Füllungen<br />
nutzen. Es stehen folgende zur Verfügung:<br />
? WHITE_BRUSH,<br />
? BLACK_BRUSH,<br />
? LTGRAY_BRUSH,<br />
? GRAY_BRUSH,<br />
? DKGRAY_BRUSH,<br />
? HOLLOW_BRUSH ( identisch mit NULL_BRUSH).<br />
Versuchen Sie NULL_BRUSH – damit wird das Fenster durchsichtig!<br />
Sie können auch e<strong>in</strong>e eigene Füllfarbe erzeugen:<br />
HBRUSH MyBrush = CreateSolidBrush( RGB( 0, 150, 255 ) );<br />
Damit stehen Ihnen mittels RGB( rot, grün, blau ) <strong>in</strong>sgesamt 256*256*256 = 16.777.216 Farbwerte<br />
(24 bit) zur Verfügung. Die Werte für <strong>die</strong> E<strong>in</strong>zelfarben s<strong>in</strong>d jeweils von 0 bis 255 e<strong>in</strong>stellbar.<br />
Experimentieren Sie e<strong>in</strong> wenig mit den Farben, damit Sie <strong>die</strong> Wirkung des RGB-Makros verfolgen<br />
können! 5<br />
LoadCursor bietet folgende Standard-Cursor:<br />
IDC_APPSTARTING<br />
IDC_ARROW<br />
IDC_CROSS<br />
IDC_HAND<br />
IDC_HELP<br />
IDC_IBEAM<br />
IDC_NO<br />
IDC_SIZEALL<br />
IDC_SIZENESW<br />
IDC_SIZENS<br />
IDC_SIZENWSE<br />
IDC_SIZEWE<br />
IDC_UPARROW<br />
IDC_WAIT<br />
Standard arrow and small hourglass<br />
Standard arrow<br />
Crosshair<br />
W<strong>in</strong>dows NT 5.0 and later: Hand<br />
Arrow and question mark<br />
I-beam<br />
Slashed circle<br />
Four-po<strong>in</strong>ted arrow po<strong>in</strong>t<strong>in</strong>g north, south, east, and west<br />
Double-po<strong>in</strong>ted arrow po<strong>in</strong>t<strong>in</strong>g northeast and southwest<br />
Double-po<strong>in</strong>ted arrow po<strong>in</strong>t<strong>in</strong>g north and south<br />
Double-po<strong>in</strong>ted arrow po<strong>in</strong>t<strong>in</strong>g northwest and southeast<br />
Double-po<strong>in</strong>ted arrow po<strong>in</strong>t<strong>in</strong>g west and east<br />
Vertical arrow<br />
Hourglass<br />
4 hbr ist <strong>die</strong> Abkürzung für Handle [to a] Brush<br />
5 Quelle: http://www.henkessoft.de/api2.htm
E<strong>in</strong>leitung 10<br />
Übung<br />
Experimentieren Sie mit verschiedenen Cursor-Varianten<br />
Die Membervariable lpszClassName soll den Fensterklassennamen speichern. Über ihn können<br />
Sie später Fenster <strong>die</strong>ser Klasse erzeugen. Die Hauptfensterklasse bekommt üblicherweise den Namen<br />
des Programms.<br />
Da unser Fenster (noch) ke<strong>in</strong> Menü haben soll, geben wir der Membervariable<br />
lpszMenuName den Wert NULL.<br />
Mit der CreateW<strong>in</strong>dow Funktion erstellen wir nun e<strong>in</strong> Fenster gemäß unserer registrierten<br />
Fensterklasse.<br />
hwnd = CreateW<strong>in</strong>dow("HELLO", "HELLO",<br />
WS_OVERLAPPEDWINDOW,<br />
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,<br />
NULL, NULL, hInstance, NULL);<br />
Dem ersten Parameter, lpClassName, wird der Name der Fensterklasse als Zeichenkette übergeben.<br />
Über den zweiten Parameter können wir den Text <strong>in</strong> der Titelleiste bee<strong>in</strong>flussen. Im dritten Parameter<br />
speichern wir den Stil des Fensters. Die nächsten vier Parameter s<strong>in</strong>d für <strong>die</strong> räumliche Position des<br />
Fensters verantwortlich. Der vorletzte Parameter muss wieder der Handle auf unsere Programm<strong>in</strong>stanz<br />
se<strong>in</strong>, damit man das Fenster unserem Programm zuordnen kann. Die anderen drei Parameter s<strong>in</strong>d im<br />
Moment unwichtig, daher belassen wir sie bei Null. Die CreateW<strong>in</strong>dow Funktion liefert als<br />
Rückgabewert den Handle auf das Fenster zurück.<br />
Unser Fenster ist nun erstellt, es ersche<strong>in</strong>t jedoch noch nicht auf dem Bildschirm. Dazu müssen wir <strong>die</strong><br />
ShowW<strong>in</strong>dow Funktion aufrufen. Die UpdateW<strong>in</strong>dow Funktion lässt den Anwendungsbereich, also<br />
den freien Fensterbereich, sofort nach dem Start neu zeichnen.<br />
ShowW<strong>in</strong>dow(hwnd, nCmdShow);<br />
UpdateW<strong>in</strong>dow(hwnd);<br />
Das Fenster ist nun erstellt und wird angezeigt. Der Aufruf von UpdateW<strong>in</strong>dow sendet e<strong>in</strong>e<br />
WM_PAINT-Nachricht, damit der Client-Bereich des Fensters hWnd neu gezeichnet wird.<br />
Nun warten wir auf <strong>die</strong> E<strong>in</strong>gabe bzw. Aktivitäten des Benutzers. Wenn etwas passiert, das unser<br />
Fenster betrifft, <strong>in</strong>formiert uns W<strong>in</strong>dows über <strong>die</strong> entsprechende Nachricht. Jedoch werden uns <strong>die</strong><br />
Nachrichten nicht aufgezwängt, sondern sie werden <strong>in</strong> e<strong>in</strong>er Warteschleife abgelegt, aus der wir sie<br />
erst mittels der GetMessage Funktion abholen müssen.<br />
while (GetMessage(&msg, NULL, 0, 0))<br />
DispatchMessage(&msg);<br />
Wenn ke<strong>in</strong>e Nachricht vorhanden ist, dann bleibt unser Programm <strong>in</strong> der Funktion stehen und<br />
wartet auf <strong>die</strong> nächste Nachricht.
E<strong>in</strong>leitung 11<br />
Die GetMessage Funktion hat folgende Parameter:<br />
BOOL GetMessage(<br />
LPMSG lpMsg, // address of structure with message<br />
HWND hWnd, // handle of w<strong>in</strong>dow<br />
UINT wMsgFilterM<strong>in</strong>, // first message<br />
UINT wMsgFilterMax // last message<br />
);<br />
In dem ersten Parameter wird <strong>die</strong> Nachricht gespeichert. Im zweiten Parameter können wir uns auf das<br />
Abholen von Nachrichten für nur e<strong>in</strong> Fenster beschränken. Wenn wir <strong>die</strong>s wollten, müssten wir hier<br />
den Handle des Fensters e<strong>in</strong>tragen. Jedoch würde dann unser Programm nicht korrekt beenden, da zum<br />
Beispiel <strong>die</strong> Nachricht, <strong>die</strong> PostQuitMessage sendet, nicht für unser Fenster bestimmt ist, da <strong>die</strong>s ja<br />
schon zerstört wurde. Mit dem dritten und vierten Parameter kann man <strong>die</strong> Art der Nachrichten<br />
beschränken. Es werden nur Nachrichten abgeholt, <strong>die</strong> zwischen <strong>die</strong>sen beiden Werten liegen. E<strong>in</strong>e<br />
Ausnahme ist, wenn beide Parameter Null s<strong>in</strong>d, dann werden alle Nachrichten abgeholt.<br />
Bei E<strong>in</strong>tritt e<strong>in</strong>es Ereignisses ruft W<strong>in</strong>dows e<strong>in</strong>e Funktion des Programms auf, wenn e<strong>in</strong>es der im<br />
Aufruf von GetMessage e<strong>in</strong>getreten ist. Diese Funktion (auch Fensterfunktion, W<strong>in</strong>dow Procedure<br />
oder kurz WndProc genannt) ist jedoch nicht an e<strong>in</strong> Programm gebunden, sondern an e<strong>in</strong> Fenster. Es<br />
kommt daher häufig vor, dass e<strong>in</strong> Programm mehrere solcher Funktionen enthält. Über <strong>die</strong> Parameter<br />
der WndProc-Funktion bekommt man <strong>die</strong> Nachricht übergeben.<br />
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg,<br />
WPARAM wParam, LPARAM lParam)<br />
{<br />
switch(uMsg)<br />
{<br />
case WM_PAINT:<br />
DrawHello(hwnd);<br />
break;<br />
case WM_DESTROY:<br />
PostQuitMessage(0);<br />
break;<br />
default:<br />
return DefW<strong>in</strong>dowProc(hwnd, uMsg, wParam, lParam);<br />
}<br />
return 0;<br />
}<br />
Die WndProc Funktion gibt e<strong>in</strong>en LRESULT Wert zurück. LRESULT ist e<strong>in</strong> typedef 6 auf e<strong>in</strong>en<br />
long Typ. CALLBACK sagt aus, dass <strong>die</strong>se Funktion von W<strong>in</strong>dows aufgerufen wird. Der Name der<br />
WndProc Funktion ist frei wählbar. Sie verfügt über folgende Parameter:<br />
Der erste Parameter ist der Handle zu dem Fenster, für das <strong>die</strong> Nachricht bestimmt ist.<br />
Der zweite Parameter message enthält <strong>die</strong> Kennziffer der Nachricht. Für alle Nachrichten s<strong>in</strong>d <strong>in</strong><br />
6 typedef erzeugt ke<strong>in</strong>en neuen Datentyp, sondern führt lediglich mnemonische Synonyme (e<strong>in</strong>fachere Namen)<br />
oder Alias-Bezeichnungen für e<strong>in</strong>en existierenden Typ e<strong>in</strong>. typedef eignet sich daher besonders zur<br />
Vere<strong>in</strong>fachung komplexer Deklarationen:
E<strong>in</strong>leitung 12<br />
w<strong>in</strong>user.h Konstanten deklariert.<br />
Die nächsten beiden Parameter enthalten je nach Nachricht verschiedene Daten. WPARAM ist vom Typ<br />
unsigned <strong>in</strong>t. Das W stammt noch aus alten Tagen, als e<strong>in</strong> <strong>in</strong>t noch 16 Bit breit war, damals<br />
war WPARAM also noch e<strong>in</strong> Speicherwort.<br />
case WM_PAINT:<br />
DrawHello(hwnd);<br />
break;<br />
Die WM_PAINT-Nachricht <strong>in</strong>formiert darüber, dass e<strong>in</strong> Teil des Fensters oder das gesamte Fensters<br />
der Applikation neu gezeichnet werden muss. Die Applikation ruft, immer wenn sie e<strong>in</strong>e Nachricht<br />
WM_PAINT erhält, <strong>die</strong> Funktion DrawHello auf.<br />
Im Falle e<strong>in</strong>er WM_DESTROY-Nachricht wird <strong>die</strong> Meldung zur Beendigung des Programms an<br />
W<strong>in</strong>Ma<strong>in</strong> weitergeleitet, das e<strong>in</strong>en geordneten Abschluss des Programms vornehmen kann.<br />
Alle übrigen Meldungen werden an <strong>die</strong> Standard-Fensterprozedur DefW<strong>in</strong>dowProc weitergeleitet.<br />
Diese stellt sicher, dass alle Meldungen abgearbeitet werden.<br />
case WM_DESTROY:<br />
PostQuitMessage(0);<br />
break;<br />
default:<br />
return DefW<strong>in</strong>dowProc(hwnd, uMsg, wParam, lParam);<br />
Nun zur Funktion DrawHello:<br />
void DrawHello(HWND hwnd)<br />
{<br />
HDC hDC;<br />
PAINTSTRUCT pa<strong>in</strong>tStruct;<br />
RECT clientRect;<br />
}<br />
hDC = Beg<strong>in</strong>Pa<strong>in</strong>t(hwnd, &pa<strong>in</strong>tStruct);<br />
if (hDC != NULL)<br />
{<br />
GetClientRect(hwnd, &clientRect);<br />
DPtoLP(hDC, (LPPOINT)&clientRect, 2);<br />
DrawText(hDC, "Hello, World!", -1, &clientRect,<br />
DT_CENTER | DT_VCENTER | DT_SINGLELINE);<br />
EndPa<strong>in</strong>t(hwnd, &pa<strong>in</strong>tStruct);<br />
}<br />
Mit der Beg<strong>in</strong>Pa<strong>in</strong>t-Funktion teilen wir W<strong>in</strong>dows mit, dass wir <strong>in</strong> unseren Anwendungsbereich<br />
zeichnen möchten. Der erste Parameter der Beg<strong>in</strong>Pa<strong>in</strong>t Funktion legt das Fenster fest, <strong>in</strong> dem wir<br />
zeichnen wollen. Der zweite Parameter ist e<strong>in</strong> Zeiger auf unsere PAINTSTRUCT Funktion, <strong>in</strong> der<br />
W<strong>in</strong>dows <strong>die</strong> Daten bezüglich des Zeichenbereiches speichern wird. Der Rückgabewert hDC (Device<br />
Context) ist der Handle auf den Zeichenbereich.<br />
Die Beg<strong>in</strong>Pa<strong>in</strong>t Funktion übermalt, wenn es so gewollt war, den zu erneuernden (ungültigen)<br />
Zeichenbereich mit dem H<strong>in</strong>tergrund Füllmuster. Der Zeichenbereich wird danach wieder als gültig<br />
erklärt. Dieses als gültig markieren ist wichtig, denn solange noch e<strong>in</strong> Bereich als ungültig markiert
E<strong>in</strong>leitung 13<br />
ist, wird uns W<strong>in</strong>dows immer wieder e<strong>in</strong>e WM_PAINT Nachricht schicken.<br />
Die DPtoLP-Function konvertiert Koord<strong>in</strong>aten des Geräts (device) <strong>in</strong> logische Koord<strong>in</strong>aten. Dabei<br />
werden verschiedene Gerätee<strong>in</strong>stellungen (mapp<strong>in</strong>g mode, Koord<strong>in</strong>aten des Bezugspunkts, viewport,<br />
...) berücksichtig. Bei e<strong>in</strong>em Bildschirm entsprechen <strong>die</strong> Gerätekoord<strong>in</strong>aten den Pixelkoord<strong>in</strong>aten am<br />
Bildschirm. Logische Koord<strong>in</strong>aten werden applikationsspezifisch durch den Entwickler festgelegt.<br />
GetClientRect(hwnd, &clientRect);<br />
Die GetClientRect-Funktion liefert <strong>die</strong> Koord<strong>in</strong>aten der Clien-Area e<strong>in</strong>es Fensters. Die Client-<br />
Koord<strong>in</strong>aten spezifizieren <strong>die</strong> l<strong>in</strong>ke obere und untere rechte Ecke <strong>die</strong>ser Client-Fläche. Da Client<br />
Koord<strong>in</strong>aten relative zur l<strong>in</strong>ken, oberen Ecke def<strong>in</strong>iert s<strong>in</strong>d, ist l<strong>in</strong>ks oben immer (0,0).<br />
Mit der DrawText Funktion kann man Text formatiert ausgeben. Der erste Parameter ist, wie immer<br />
bei GDI Funktionen, der Handle zu unserem Device Context. Bei der W<strong>in</strong>dows-Programmierung<br />
sprechen wir e<strong>in</strong> Ausgabegerät <strong>in</strong> der Regel nicht direkt an, sondern über e<strong>in</strong>en Gerätekontext. Dies<br />
hat für Sie den Vorteil, dass Sie sich um <strong>die</strong> spezifischen Eigenschaften e<strong>in</strong>es Geräts (Bildschirm,<br />
Drucker, Plotter, Zwischenablage) nicht selbst kümmern müssen, sondern W<strong>in</strong>dows <strong>die</strong> Ausgabe<br />
überlassen. Beachten Sie dabei jedoch, dass <strong>die</strong>se Ausgaben nicht von W<strong>in</strong>dows gespeichert werden.<br />
Bei bestimmten Fensteroperationen (wie Verschieben, M<strong>in</strong>imieren, Vergrößern, <strong>in</strong> den Vordergrund<br />
holen) muss also Ihr Programm dafür sorgen, angeregt durch <strong>die</strong> vom Betriebssystem gesendeten<br />
Botschaften, den Fenster<strong>in</strong>halt neu zu zeichnen!<br />
Der zweite Parameter ist e<strong>in</strong> Zeiger auf den auszudruckenden Text (<strong>in</strong> <strong>die</strong>sem Fall e<strong>in</strong>e<br />
Zeichenkettenkonstante). Im dritten Parameter muss man <strong>die</strong> Länge des Textes angeben; <strong>die</strong>s<br />
geschieht hier durch Übergabe des zuvor bestimmten Client-Rects. Mit dem vierten Parameter kann<br />
man festlegen, wie der Text formatiert werden soll. Wir benutzen <strong>die</strong> Konstanten DT_SINGLELINE,<br />
DT_CENTER und DT_VCENTER, <strong>die</strong> den Text e<strong>in</strong>zeilig und zentriert ausgeben lassen.<br />
Am Ende jeder Zeichenoperation müssen wir mit der EndPa<strong>in</strong>t Funktion W<strong>in</strong>dows mitteilen, dass wir<br />
nichts mehr zeichnen möchten. Die Funktion gibt den Device Context wieder frei.<br />
5 Nachrichtenschleifen und Fensterprozeduren<br />
In e<strong>in</strong>er Fensterprozedur kann ebenfalls e<strong>in</strong>e Nachrichtenschleife enthalten se<strong>in</strong>. Im folgenden<br />
Programm implementieren wir e<strong>in</strong>e grundlegende Zeichnungsfunktionalität.<br />
#<strong>in</strong>clude <br />
void AddSegmentAtMessagePos(HDC hDC, HWND hwnd, BOOL bDraw)<br />
{<br />
DWORD dwPos;<br />
POINTS po<strong>in</strong>ts;<br />
POINT po<strong>in</strong>t;<br />
}<br />
dwPos = GetMessagePos();<br />
po<strong>in</strong>ts = MAKEPOINTS(dwPos);<br />
po<strong>in</strong>t.x = po<strong>in</strong>ts.x;<br />
po<strong>in</strong>t.y = po<strong>in</strong>ts.y;<br />
ScreenToClient(hwnd, &po<strong>in</strong>t);<br />
DPtoLP(hDC, &po<strong>in</strong>t, 1);<br />
if (bDraw) L<strong>in</strong>eTo(hDC, po<strong>in</strong>t.x, po<strong>in</strong>t.y);<br />
else MoveToEx(hDC, po<strong>in</strong>t.x, po<strong>in</strong>t.y, NULL);
E<strong>in</strong>leitung 14<br />
void DrawHello(HWND hwnd)<br />
{<br />
HDC hDC;<br />
MSG msg;<br />
if (GetCapture() != NULL) return;<br />
hDC = GetDC(hwnd);<br />
if (hDC != NULL)<br />
{<br />
SetCapture(hwnd);<br />
AddSegmentAtMessagePos(hDC, hwnd, FALSE);<br />
while(GetMessage(&msg, NULL, 0, 0))<br />
{<br />
if (GetCapture() != hwnd) break;<br />
switch (msg.message)<br />
{<br />
case WM_MOUSEMOVE:<br />
AddSegmentAtMessagePos(hDC, hwnd, TRUE);<br />
break;<br />
case WM_LBUTTONUP:<br />
goto ExitLoop;<br />
default:<br />
DispatchMessage(&msg);<br />
}<br />
}<br />
ExitLoop:<br />
ReleaseCapture();<br />
ReleaseDC(hwnd, hDC);<br />
}<br />
}<br />
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg,<br />
WPARAM wParam, LPARAM lParam)<br />
{<br />
switch(uMsg)<br />
{<br />
case WM_LBUTTONDOWN:<br />
DrawHello(hwnd);<br />
break;<br />
case WM_DESTROY:<br />
PostQuitMessage(0);<br />
break;<br />
default:<br />
return DefW<strong>in</strong>dowProc(hwnd, uMsg, wParam, lParam);<br />
}<br />
return 0;<br />
}<br />
<strong>in</strong>t WINAPI W<strong>in</strong>Ma<strong>in</strong>(HINSTANCE hInstance, HINSTANCE hPrevInstance,<br />
LPSTR d3, <strong>in</strong>t nCmdShow)<br />
{<br />
MSG msg;<br />
HWND hwnd;<br />
WNDCLASS wndClass;<br />
if (hPrevInstance == NULL)<br />
{<br />
memset(&wndClass, 0, sizeof(wndClass));<br />
wndClass.style = CS_HREDRAW | CS_VREDRAW;<br />
wndClass.lpfnWndProc = WndProc;
E<strong>in</strong>leitung 15<br />
wndClass.hInstance = hInstance;<br />
}<br />
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);<br />
wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);<br />
wndClass.lpszClassName = "HELLO";<br />
if (!RegisterClass(&wndClass)) return FALSE;<br />
}<br />
hwnd = CreateW<strong>in</strong>dow("HELLO", "HELLO",<br />
WS_OVERLAPPEDWINDOW,<br />
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,<br />
NULL, NULL, hInstance, NULL);<br />
ShowW<strong>in</strong>dow(hwnd, nCmdShow);<br />
UpdateW<strong>in</strong>dow(hwnd);<br />
while (GetMessage(&msg, NULL, 0, 0))<br />
DispatchMessage(&msg);<br />
return msg.wParam;<br />
Machen Sie sich mit dem Programm vertraut! Was hat es mit den beiden Nachrichtenschleifen auf<br />
sich?<br />
Was geschieht, wenn<br />
? Sie das Programm m<strong>in</strong>imieren?<br />
? Sie e<strong>in</strong> anderes Programmfenster über das Anwendungsfenster schieben?