Tutorial
Lutz Ifers WinAPI Tutorial
von MOD | Seite 3 von 16 |
Kapitel 1.2: Erzeugen eines ersten Fensters
In der Programmiersprache C ist es nicht erlaubt, Code "einfach so" in den Hauptteil des Programmes zu schreiben, C fordert eine "main()"-Funktion. Die komplette Deklaration dieser Funktion lautet "int main (int argc, char **argv)", in FreeBasisch gesprochen also
declare function main(argc as integer, argv() as string) as integer
Auf diese Weise werden in C die Aufrufparameter übergeben, argc beinhaltet die Anzahl der Parameter, argv die Zeichenketten. FreeBasic ist in dieser Hinsicht deutlich einfacher. Wir benötigen keine main()-Funktion, und die Parameter sind bei uns in der Variable "Command$" bzw. mit Leerzeichen als Trennzeichen in "Command$()" gespeichert.
Windowsprogramme in C haben eine andere main()-Funktion. Ihre Deklaration lautet "int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)". In FreeBasisch:
declare function WinMain(hInstance as HINSTANCE, hPrevInstance as HINSTANCE,_
szCmdLine as PSTR, iCmdShow as integer) as integer
"Winapi" ist eine sogenannte Aufrufkonvention, die uns nicht interessieren braucht, sie bedeutet letztlich nur, dass die WinMain()-Funktion anstatt der main()-Funktion als Hauptfunktion des Programmes dienen soll. Was sind jetzt das allerdings für Datentypen, "HINSTANCE" und "PSTR"? Das sind von Windows eingeführte Datentypen, in FreeBasic kennen wir das als "UDT" bzw. "User defined type". Die Art, die Namen für diese Datentypen auszuwählen nennt man "Ungarische Notation", das heißt: Die ersten Zeichen geben die Art des Datentyps an, der Rest ist Name. "p" steht für "Pointer", "Str" für "String" - "PSTR" ist also ein String Pointer. "h" steht für "Handle", wie erwähnt, eine Windowsinterne Identifikationsnummer (defacto: ein Pointer auf die Adresse im RAM, an der alle nötigen Informationen stehen - aber vorerst halte ich die einfache Erklärung für besser), eine Art Etikett, und werden von FreeBasic in der Regel als Integer umgesetzt.
Nun zur Bedeutung dieser Parameter: hInstance ist ein Handle auf die eigene Instanz, daher die von Windows vergebene Nummer für dieses Programm. Logischerweise enthält hPrevInstance ein Handle auf das aufrufende Programm. Die Aufrufparameter stehen in dem String, auf den szCmdLine verweist. iCmdShow steht für die Art, wie das Programm seine Fenster erzeugen soll: Maximiert, Minimiert oder Normal (In den Dateieigenschaften einer Verknüpfung zu einem Programmes kann man das im zweiten Reiter unter "Ausführen" einstellen).
Wie ist das jetzt in FB? FreeBasic benötigt wie erwähnt keine solche [win]main-Funktion, ob dies ein Vorteil ist, darüber könnte man sich streiten. Ein Nachteil davon allerdings ist, dass wir uns einige der Informationen selber besorgen müssen: hInstance bekommt man durch einen Aufruf von "GetModuleHandle(NULL)", und iCmdShow ersetzen wir ungefragt durch "SW_NORMAL" ("Callback", da wir die Funktion nur angeben, sie aber von Windows aufgerufen bzw. "zurückgerufen" wird).
''' Lutz Ifers WinAPI-Tutorial
''' Lizenz: WTFPL
'''
''' Kapitel 1.2 - "Erzeugen eines ersten Fensters"
#include "windows.bi"
const ProgrammName = "Erzeugen eines Fensters"
declare function Fenster(byval hWnd as HWND, byval message as UINTEGER,_
byval wParam as WPARAM, byval lParam as LPARAM) as LRESULT
Als erstes benötigen wir die Deklaration der Callback-Funktion unseres Fensters. Da Windows seine Fenster ereignisgesteuert organisiert, bekommt unser Fenster ständig Nachrichten zugeschickt, die es über Aktivitäten, zum Beispiel Bewegungen der Maus oder Eingaben auf der Tastatur informiert. Dies ist die Funktion, die diese Nachrichten verarbeiten wird.
dim as WNDCLASS windowclass
with windowclass
.style = CS_HREDRAW or CS_VREDRAW
.lpfnWndProc = ProcPtr(Fenster)
.cbClsExtra = 0
.cbWndExtra = 0
.hInstance = GetModuleHandle(NULL)
.hCursor = LoadCursor(NULL, IDC_ARROW)
.hIcon = LoadIcon(NULL, IDI_APPLICATION)
.hbrBackground = GetStockObject(WHITE_BRUSH)
.lpszClassName = StrPtr(ProgrammName)
.lpszMenuName = NULL
end with
RegisterClass @windowclass
Für unser Fenster legen wir als erstes die sogenannte Fensterklasse fest. Sie enthält alle wichtigen Informationen über die Beschaffenheit des Fensters. lpfnWndProc verweist auf die Callback-Funktion, die wir vorher deklariert haben. Mit den beiden "Extra"-Einträge könnte man für alle gemeinsam oder jedes einzelne Fenster der Klasse zusätzlichen Speicher reservieren. Was es damit auf sich hat, werden wir in Kapitel 4.3, "Speicher reservieren" näher beleuchten. hInstance sagt Windows, zu welchem Programm die Fensterklasse gehört. hIcon und hCursor legen das Fenstericon (das links oben in der Titelzeile) und den Cursor fest. Hier legen wir im Moment das Standardicon und den Standardcursor fest. hbrBackground enthält einen sogenannten Brush (siehe Kapitel 2.2, "Filled Shapes"), grob gesprochen: Die Hintergrundfarbe des Fensters, bei der wir uns für weiß entscheiden. Bei lpszClassName legen wir den Namen unserer neuen Fensterklasse fest, in der Regel wird dies der Programmname sein. Da wir noch keine Menüleiste im Fenster haben wollen, wählen wir für lpszMenuName NULL. Der Style der Fensterklasse (nicht zu verwechseln mit dem Fensterstyle, mit dem wir uns später beschäftigen werden) legt fest, dass das Fenster bei horizontaler oder vertikaler Größenänderung neu gezeichnet werden soll. Weitere interessante Flags wären "CS_NOCLOSE" (deaktiviert den "x"-Button) oder "CS_DROPSHADOW" (Ab WinXP: Das Fenster wirft einen Schatten). Mit RegisterClass wird Windows von der neuen Klasse unterrichtet, der Rückgabewert, der hier ignoriert wird, enthält die Information, ob die Registrierung der Klasse erfolgreich war, oder nicht.
dim as HWND MeinFenster = CreateWindow(_
ProgrammName, "Titelzeile", WS_OVERLAPPEDWINDOW,_
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,_
NULL, NULL, GetModuleHandle(NULL), NULL)
ShowWindow MeinFenster, SW_NORMAL
UpdateWindow MeinFenster
CreateWindow ist eine Funktion, die uns noch sehr häufig begegnen wird. Ihr Rückgabewert ist ein Handle auf das erstelle Fenster. Die Deklaration von CreateWindow lautet:
declare function CreateWindow(lpClassName as STRING, lpWindowName as STRING,_
dwStyle as DWORD, x as INTEGER, y as INTEGER, nWidth as INTEGER,_
nHeight as INTEGER, hWndParent as HWND, hMenu as HMENU,_
hInstance as HINSTANCE, lpParam as LPVOID) as HWND
Klassenname und Fenstername sind soweit selbsterklärend (Der Vollständigkeit halber soll erwähnt sein, dass statt des Klassennamens auch ein Pointer auf die Klasse selbst übergeben werden könnte). Wenn wir für die Position (x, y) und die Größe (nWidth, nHeight) keine speziellen Wünsche haben, verwenden wir CW_USEDEFAULT. Da unser Fenster kein Parentfenster hat, übergeben wir NULL für hWndParent, sowie für hMenu, da wir keine Menüleiste wünschen. Das zum Fenster gehörige Programm ist unser eigenes, und wir wollen dem Fenster keine weiteren Informationen mitgeben, daher auch lpParam Null. Der Aufruf von CreateWindow erzeugt das Fenster nur, setzt seinen Sichtbarkeitsstatus aber auf "unsichtbar", wir müssen es erst durch einen Aufruf von "ShowWindow" anzeigen lassen (SW_NORMAL). Wir könnten es natürlich auch minimiert (SW_SHOWMINIMIZED) oder maximiert(SW_SHOWMAXIMIZED) anzeigen lassen. Durch UpdateWindow wird direkt nach Erzeugen des Fenster der Inhalt zum ersten Mal gezeichnet.
Als nächstes kümmern wir uns um die Von Windows verschickten Nachrichten:
dim as MSG Nachricht
do while GetMessage(@Nachricht, NULL, 0, 0) <> 0
DispatchMessage @Nachricht
loop
end Nachricht.wParam
Die Nachrichten werden in dem UDT Nachricht gespeichert. GetMessage ist ein sogenannter "blocking Call", das heißt: Das Programm verbleibt solange in der Funktion, bis eine Nachricht vorliegt. Tritt dabei ein Fehler auf (zum Beispiel, weil das Fenster geschlossen wurde), gibt GetMessage TRUE zurück, die Schleife wird verlassen, und ein eventueller Fehlercode wird ausgegeben. Andernfalls wird die Nachricht an die zur Nachricht gehörende Callback-Funktion weitergegeben:
function Fenster(byval hWnd as HWND, byval message as UINTEGER,_
byval wParam as WPARAM, byval lParam as LPARAM) as LRESULT
if message = WM_DESTROY then
PostQuitMessage 0
return 0
else
return DefWindowProc(hWnd, message, wParam, lParam)
end if
end function
Diese Funktion überprüft, ob die Nachricht WM_DESTROY geschickt wurde, also das Fenster geschlossen wurde, und beendet den Nachrichtenverkehr des Fensters mit Windows (und verlässt dadurch die do-loop schleife im Hauptprogramm). Anderfalls soll die sogenannte DefaultWindowProcedure die Behandlung übernehmen. Diese Funktion ist eine Sammlung von Reaktionen auf Nachrichten, die meißtens gleich bleiben, die man aber nicht immer neuschreiben möchte, zum Beispiel die Größe des Fensters ändern, wenn jemand mit der Maus an den Rändern des Fensters zieht.
Fertig, unser erstes "richtiges" Fenster in FreeBasic.
Links:
In der MSDN: WNDCLASS, RegisterClass, CreateWindow, ShowWindow, GetMessage, DefWindowProc
In der FreeBasic-Referenz: END, WITH, WHILE
Zusätzliche Informationen und Funktionen | |||||||
---|---|---|---|---|---|---|---|
|