API-Windows-Programmierung (ohne MFC)
Inhalt
1.
WinMain(...) und windows.h
2.
Eigene Fenster erzeugen
3.
Zwei Fenster auf Basis einer WNDCLASS-Struktur erzeugen
4.
Nachrichtenverarbeitung
5.
Interaktionen mit Kindfenstern
6.
DLL-Programmierung
5. Interaktionen mit Kindfenstern
Die Darstellung von Fenstern und die Verarbeitung von Nachrichten ist die Grundlage von Windows. Im nächsten Schritt betrachten wir die Zusammenarbeit mehrerer Fenster zur Interaktion mit dem Benutzer. Hierbei ist es wichtig, daß man versteht, daß Buttons, Edit-Felder usw. auch nur Fenster sind, die über den Austausch von Nachrichten mit einem Elternfenster Aktionen einleiten. Beginnen wir mit einem Button.
Als Ausgangspunkt verwenden wir folgendes Programm, das einen Button als Kindfenster darstellt. Die entsprechenden notwendigen Ergänzungen im Programmcode sind rot hervorgehoben:
int WINAPI
WinMain (HINSTANCE hI, HINSTANCE hPrI, PSTR szCmdLine, int iCmdShow)
{
char
szName[] = "Fensterklasse";
HBRUSH
MyBrush = CreateSolidBrush( RGB( 0, 150, 255 ) );
WNDCLASS
wc;
wc.style
= CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc
= WndProc;
wc.cbClsExtra
= 0;
wc.cbWndExtra
= 0;
wc.hInstance
= hI;
wc.hIcon
= LoadIcon (NULL, IDI_WINLOGO);
wc.hCursor
= 0;
wc.hbrBackground
= MyBrush;
wc.lpszMenuName
= NULL;
wc.lpszClassName
= szName;
RegisterClass (&wc);
HWND
hwnd = CreateWindow (szName, "Interaktives Fenster",
WS_OVERLAPPEDWINDOW,
0, 0, 400, 100, NULL, NULL, hI, NULL);
ShowWindow
(hwnd, iCmdShow);
UpdateWindow
(hwnd);
//-----------------------------------------------------------------------------------
MSG msg;
while
(GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
return
msg.wParam;
}
//-----------------------------------------------------------------------------------
LRESULT
CALLBACK
WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC
hdc;
PAINTSTRUCT
ps;
HWND
hwndButton;
switch
(message)
{
case
WM_PAINT:
hdc = BeginPaint (hwnd, &ps);
SetTextColor(hdc, RGB(255,0,0) );
SetBkColor(hdc, RGB(255,255,0) );
TextOut (hdc, 20, 20, "Ich bin ein Fenster.", 20);
EndPaint (hwnd, &ps);
return
0;
case WM_CREATE :
hwndButton
= CreateWindow ( "button", "Knopf",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
200, 20, 100, 40,
hwnd, NULL,((LPCREATESTRUCT) lParam)->hInstance, NULL);
return
0 ;
case
WM_DESTROY:
PostQuitMessage (0);
return
0;
}
return
DefWindowProc (hwnd, message, wParam, lParam);
}
Nach dem Kompilieren erhält man folgendes Ergebnis:
An der Stelle x=200 / y=20 (linke obere Ecke des Buttons) findet man jetzt ein mit CreateWindow(...) erzeugtes Kindfenster vom Typ "button" / BS_PUSHBUTTON mit der Höhe=40 und der Breite=100.
Etwas kompliziert wirkt zunächst der Ausdruck für den Parameter "instance handle":
((LPCREATESTRUCT)lParam)->hInstance.
Die WM_CREATE Nachricht liefert als
lParam
einen Zeiger auf eine Struktur vom Typ CREATESTRUCT.
Diese Struktur enthält folgende
Elemente,
die denen der Funktion CreateWindow(...) entsprechen:
LPVOID
lpCreateParams
HINSTANCE
hInstance
HMENU
hMenu
HWND
hwndParent
int
cy
int
cx
int
y
int
x
LONG
style
LPCTSTR
lpszName
LPCTSTR
lpszClass
DWORD
dwExStyle
Ein Element dieser Struktur ist hInstance. Daher wird lParam in einen Zeiger auf diese CREATESTRUCT-Struktur umgewandelt. Mit dem Pfeiloperator erhalten wir daraus das Element hInstance.
Eine weitere Methode besteht im Einsatz der Funktion GetWindowLong(...) und cast von LONG nach HINSTANCE wie folgt:
(HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE)
Als weitere Alternative kann man außerhalb der Funktionen WinMain(...) und WndProc(...) eine globale Variable hInst deklarieren, die man in WinMain(...) vor Erstellen des Hauptfensters einfach mit hInst = hI initialisiert. Dann kann man wie folgt vorgehen:
LRESULT
CALLBACK
WndProc (...)
{
...
case
WM_CREATE
:
hwndButton = CreateWindow ( "button", "Knopf",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
200, 20, 100, 40,
hwnd, NULL, hInst,
NULL);
return 0
;
...
}
Diese Variante sieht zumindest weniger kompliziert aus als die ersten beiden. Man erkauft diesen Vorteil durch eine zusätzliche globale Variable.
Probieren Sie einfach alle drei Varianten aus, um an hInstance zu gelangen.
Mir persönlich gefällt die
Methode
mit GetWindowLong(...) am besten.
Daher ändern Sie den Code für
die weiteren Betrachtungen bitte auf diese Methode ab.
Das Fenster mit seinem Button sieht
zwar
hübsch aus, aber eine Funktion ist noch nicht zu erkennen.
Was passiert, wenn man mit der Maus auf
den Knopf klickt? Hierbei erzeugt man eine Nachricht WM_COMMAND.
Diese Nachricht kann man abfangen und
auswerten. Der Aufbau der Nachricht ist wie folgt:
LOWORD (wParam) | Kindfenster ID |
HIWORD (wParam) | Notification code |
HWND (lParam) | Kindfenster handle |
Diese Parameter sind im Moment nicht wesentlich. Wir probieren zunächst das Abfangen der Nachricht. Ergänzen Sie die switch-Kontrollstruktur zur Auswertung der Nachrichten bitte wie folgt:
Beim Mausklick auf den Button erhält man folgende Antwort:
Sie haben es nun geschafft, ein
Kindfenster
als Button in unser Elternfenster zu integrieren und diesem Button eine
Funktion zuzuordnen.
Im nächsten Schritt werden wir zwei
Buttons einbauen. Zur Sicherheit hier noch einmal der komplette Code:
int WINAPI
WinMain (HINSTANCE hI, HINSTANCE hPrI, PSTR szCmdLine, int iCmdShow)
{
char
szName[] = "Fensterklasse";
HBRUSH
MyBrush = CreateSolidBrush( RGB( 0, 150, 255 ) );
WNDCLASS
wc;
wc.style
= CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc
= WndProc;
wc.cbClsExtra
= 0;
wc.cbWndExtra
= 0;
wc.hInstance
= hI;
wc.hIcon
= LoadIcon (NULL, IDI_WINLOGO);
wc.hCursor
= 0;
wc.hbrBackground
= MyBrush;
wc.lpszMenuName
= NULL;
wc.lpszClassName
= szName;
RegisterClass (&wc);
HWND
hwnd = CreateWindow (szName, "Interaktives Fenster",
WS_OVERLAPPEDWINDOW,
0, 0, 400, 100, NULL, NULL, hI, NULL);
ShowWindow
(hwnd, iCmdShow);
UpdateWindow
(hwnd);
//-----------------------------------------------------------------------------------
MSG msg;
while
(GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
return
msg.wParam;
}
//-----------------------------------------------------------------------------------
LRESULT
CALLBACK
WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC
hdc;
PAINTSTRUCT
ps;
HWND
hwndButton1, hwndButton2;
switch
(message)
{
case
WM_PAINT:
hdc = BeginPaint (hwnd, &ps);
SetTextColor(hdc, RGB(255,0,0) );
SetBkColor(hdc, RGB(255,255,0) );
TextOut (hdc, 20, 20, "Ich bin ein Fenster.", 20);
EndPaint (hwnd, &ps);
return
0;
case
WM_CREATE :
hwndButton1 = CreateWindow ( "button", "Knopf 1",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
150, 20, 100, 40, hwnd, NULL,
(HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE), NULL);
hwndButton2 = CreateWindow ( "button", "Knopf 2",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
251, 20, 100, 40, hwnd, NULL,
(HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE), NULL);
return
0;
case
WM_COMMAND:
MessageBox(hwnd,"Click", "Die Nachricht WM_COMMAND wurde erzeugt",
MB_OK);
return
0;
case
WM_DESTROY:
PostQuitMessage (0);
return
0;
}
return
DefWindowProc (hwnd, message, wParam, lParam);
}
Das Ergebnis zeigt ein Fenster mit zwei Kindfenstern vom Typ Button.
Jetzt gibt es aber ein Problem. Egal,
ob
ich auf Knopf 1 oder Knopf 2 drücke, immer erscheint die gleiche
Messagebox.
Wie kann man hier unterschiedliche
Reaktionen
erzeugen?
Verändern/Ergänzen Sie den
Code
bitte wie folgt:
hwndButton2 = CreateWindow ( "button", "Knopf 2",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
251, 20, 100, 40, hwnd, (HMENU)2,
(HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE), NULL);
return
0;
case
WM_COMMAND:
if(LOWORD(wParam)
== 1)
{
MessageBox(hwnd,"Click auf Knopf 1", "Die Nachricht WM_COMMAND wurde
erzeugt",
MB_OK);
}
if(LOWORD(wParam) == 2)
{
MessageBox(hwnd,"Click auf Knopf 2", "Die Nachricht WM_COMMAND wurde
erzeugt",
MB_OK);
}
return
0;
Jetzt funktioniert die Unterscheidung
der
beiden Buttons. Was haben wir genau unternommen? Ganz einfach:
Wir
haben den beiden Kindfenstern verschiedene Namen gegeben: 1
und 2. Die
ID
(ID=Identifikation; das ist der Name - in Windows
natürlich
eine Zahl - des Kindfensters) wird im switch-Zweig mit der Nachricht
WM_COMMAND
abgefragt. Auf diese Weise - also unter Verwendung der ID - kann man
die
Kindfenster in einem Elternfenster sicher unterscheiden. Nachfolgend
finden
Sie zur Übung die entsprechende switch-Variante, falls Ihnen diese
Kontrollstruktur hier besser gefällt:
Hier können Sie übrigens auch
wParam anstelle LOWORD(wParam) verwenden, da dies hier identisch ist.
Zur
Erinnerung der Aufbau der Message WM_COMMAND:
LOWORD (wParam) | Kindfenster ID |
HIWORD (wParam) | Aktions-Code |
HWND (lParam) | Kindfenster-Handle |
Übrigens: Wenn Sie den "Kindern" gleiche Namen geben, also z.B. beide (HMENU) 1, dann kommen eben beide, wenn Sie rufen. Probieren Sie es aus.
Den Code in WndProc(...) ändern
wir
nun wie folgt ab, um zusätzlich zur Button-Schaltfläche das
Edit-Feld
kennenzulernen:
switch
(message)
{
case
WM_PAINT:
hdc = BeginPaint (hwnd, &ps);
SetTextColor(hdc, RGB(255,0,0) );
SetBkColor(hdc, RGB(255,255,0) );
TextOut (hdc, 20, 20, "Ich bin ein Fenster.", 20);
EndPaint (hwnd, &ps);
return
0;
case
WM_CREATE :
hwndButton1 = CreateWindow ( "button", "Knopf 1",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
150, 20, 100, 40, hwnd, (HMENU)1,
(HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE), NULL);
hwndEdit2
= CreateWindow ( "edit", "Edit-Feld",
WS_CHILD | WS_VISIBLE,
251, 20, 100, 40, hwnd, (HMENU)2,
(HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE), NULL);
return
0;
case
WM_COMMAND:
switch(LOWORD(wParam))
{
case
1:
SetWindowText(hwndEdit2,"");
SendMessage(hwndEdit2, EM_SETREADONLY, TRUE, 0);
break;
case 2:
SendMessage(hwndEdit2, EM_SETREADONLY, FALSE, 0);
break;
}
return
0;
case
WM_DESTROY:
PostQuitMessage (0);
return
0;
}
return
DefWindowProc (hwnd, message, wParam, lParam);
}
Wichtig ist, daß wir die
HWND-Variablen
für unsere Kindfenster auf static setzen, damit der Inhalt dieser
Variablen nicht verloren geht.
Für das Edit-Feld setzt man in
CreateWindow(...)
den Parameter lpClassName einfach auf das vorgefertigte "edit".
Nach dem Kompilieren findet man nun folgendes Bild:
Mit den ersten beiden Parametern der Funktion CreateWindow ( "edit", "Edit-Feld", ...) haben wir also den Fenster-Typ Edit-Feld und den Text im Feld festgelegt.
Die Reaktionen auf das Klicken mit der Maus in das Kindfenster wird über die Nachricht WM_COMMAND festgelegt. Daher haben wir dort einige Funktionen eingebaut.
Ein Klick auf den Button bewirkt folgende Funktionen:
SetWindowText(hwndEdit2,"");
SendMessage(hwndEdit2,
EM_SETREADONLY, TRUE, 0);
Die erste Funktion verändert einfach den Text im Fenster. Ersetzen Sie hwndEdit2 versuchsweise durch hwnd und hwndButton1. Damit können Sie also die Texte "Interaktives Fenster", "Knopf1" und "Edit-Feld" beeinflussen.
Eine sehr breit einsetzbare Funktion ist SendMessage(...). Die allgemeine Syntax ist:
LRESULT SendMessage ( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam );
Der erste Parameter legt das Fenster
fest,
an das die Nachricht gesendet werden soll. Dann folgt die Nachricht und
ihre beiden Parameter.
Wir senden also die Nachricht EM_SETREADONLY
mit den Parametern wParam = TRUE und lParam = 0. In MSDN finden Sie die
betreffenden Informationen:
EM_SETREADONLY
wParam = (WPARAM) (BOOL) fReadOnly;
//
read-only flag
lParam =
0;
// not used; must be zero
Dort erfahren Sie, daß das read-only flag auf TRUE gesetzt werden muß, wenn das Edit-Feld schreibgeschützt sein soll. Dann wird das Eingabefeld zum Ausgabefeld, gekennzeichnet durch die graue Farbe. Mit FALSE kann man das Flag zurücksetzen.
Auf Knopfdruck haben wir jetzt das Edit-Feld gelöscht (string "") und auf READ-ONLY gesetzt.
Ein Klick in das Edit-Feld aktiviert das Feld durch folgende Funktion:
SendMessage(hwndEdit2, EM_SETREADONLY, FALSE, 0);
Die Bedeutung kennen Sie ja bereits.
Schauen
Sie sich einmal in MSDN alle Nachrichten an, die mit EM_SET... beginnen.
Dort finden Sie 27 Nachrichten, die Ihnen
eine Manipulation des Edit-Feldes erlauben. Testen Sie nach Belieben.
In der Funktion CreateWindow haben wir bisher nur die Parameter WS_CHILD | WS_VISIBLE gesetzt. Damit haben wir ein Standard-Edit-Feld als sichtbares Kindfenster installiert. Weitere Eigenschaften kann man über Styles ES_... setzen.
Ändern Sie z.B. auf WS_CHILD | WS_VISIBLE | ES_MULTILINE ab. Dann können Sie z.B. mehr als eine Zeile im Edit-Feld nutzen:
Unser Edit-Feld ist natürlich zu
klein,
um größere Eingaben zuzulassen. Zwei Zeilen sind momentan
das
Maximum.
Mittels WS_CHILD | WS_VISIBLE |
ES_MULTILINE
| ES_AUTOVSCROLL kann man mehr als zwei Zeilen eingeben, da das Feld
automatisch
weiterscrollt. Mit den Pfeiltasten können Sie den Inhalt
entsprechend
scrollen.
Informieren Sie sich in MSDN unter edit
styles.
Bisher haben Sie die vordefinierten
Fenster-Typen
"button" und "edit" kennengelernt.
Nachfolgend noch einige andere
vordefinierte
Fenster-Typen:
Fenster-Typ | Nachricht (message) | Stil (style) |
"button" | BM_ | BS_ |
"edit" | EM_ | ES_ |
"listbox" | LB_ | LBS_ |
"combobox" | CB_ | CBS_ |
"static" | STM_ | SS_ |
Dieses Handwerkzeug müssen Sie nun
vertiefen. Nachfolgend finden Sie ein einfaches Übungsbeispiel mit
einer ListBox.
Konzentrieren Sie sich vor allem auf das
Zusammenspiel zwischen Eltern- und Kindfenster:
#define
WIN32_LEAN_AND_MEAN
#include
<windows.h>
const
UINT
ID_LISTBOX = 1;
static
char g_szClassName[]
= "MyWindowClass";
UINT
limit
= 20; //Notwendiger freier Speicherplatz
in
MB
void
GetLaufwerkInfo();
LRESULT
CALLBACK
WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
switch(Message)
case
WM_COMMAND:
case WM_CLOSE:
case WM_DESTROY:
int
WINAPI WinMain(HINSTANCE
hInstance, HINSTANCE hPrevInstance,
WndClass.cbSize =
sizeof(WNDCLASSEX);
RegisterClassEx(&WndClass);
GetLaufwerkInfo();
ShowWindow(hwnd, nCmdShow);
while ( GetMessage( &msg,NULL,0,0 ) )
void
GetLaufwerkInfo()
int
Drive
= 1; //
Laufwerktyp
(1-6)
for(
int
zaehler = 0; zaehler<26; zaehler++ )
Drive = GetDriveType( str ); //
Laufwerk-Typ abfragen
if( FreeBytes.QuadPart /(1024*1024) >= limit )
if( LaufwerkIsTrue[zaehler] )
|