Hogyan írjunk Windows programot?

Előre szólok, hogy eddig csak C/C++ nyelven próbáltam Windows programot írni. Pascalban nem tudom, hogy kell.

Ahhoz, hogy Windows alá programot tudjunk írni, ahhoz ismernünk kell magát a Windows-t is, mert még egy egyszerű Windows ablak kirajzolása sem olyan egyszerű, mint gondolnánk.

Többféleképpen is programozhatjuk a Windows-t. Van az ObjectWindows, meg a sima változat. Én a simát mutatom be, mert az egyszerűbb, mint az objektumos. (Persze ez relatív, hogy ki melyiket szereti.)

Windows program írásához két header-t kell beszúrni:

#include "windows.h"
#include "windowsx.h"

Valamit a fordítóprogramot Windows programok írására kell átállítani. A Windows programok írásához a legjobb a Borland C++ 5.0 (Mivel nekem nincs más).

Windows programok belépési pontja

A Windows programnak a belépési pontja nem a main() függvény, hanem a WinMain. Ez a windows.h-ban van deklarálva, de nincs kifejtve (egy prototípus), tehát nekünk kell. A deklarálása a következő:

int PASCAL WinMain(HINSTANCE cinst,HINSTANCE pinst,LPSTR Cmd,int Show);

Ezek a paraméterek automatikusan értéket kapnak mikor a programunk elindul. A cinst a programunk éppen futó példányának azonosítója; a pinst a program előző példányának az azonosítója, ha van ilyen; a Cmd a parancssori paramétereket tartalmazza; a Show pedig a megjelenítésről tartalmaz információt, amit később a ShowWindow eljárás hívásakor használhatunk fel.

Néhány adattípusnak új nevet adtak a typedef használatával. Pl. az LPSTR megegyezik a char *-gal.

Ne törődjünk vele, a lényeg az, hogy a Windows programnak ez a belépési pontja, és kész.

Most lássunk egy nagyon rövidke Windows programot:

#include "windows.h"
#include "windowsx.h"

int PASCAL WinMain(HINSTANCE cinst,HINSTANCE pinst,LPSTR Cmd,int Show)
{
    MessageBox(NULL,"Üzenetpanel","INFO",MB_OK);
}

Ez a nyúlfarknyi program annyit tesz, hogy kirak egy leokézható párbeszédablakot, majd kilép.

Saját ablak létrehozása

Ha már ott tartunk, hogy Windows program, akkor jó lenne, ha a programunk kapna egy ablakot is.

A Windows-ban lényegében minden egy ablak: az összes gomb, menük, csúszkák, ikonok még a fehérhátterű súgó gyorstipp is az. Ezek az elemek regisztrálva vannak egy globális hogyishívjákban. Minden ilyen elemnek van egy irányító eljárása, ami kezeli az egérkattintásokat, billentyűlenyomásokat stb.

Ha saját ablakot akarunk, először nekünk is regisztrálnunk kell az ablakunkat, ezt úgy tehetjük meg, hogy kitöltünk egy WNDCLASS struktúrát:

WNDCLASS FoAblakO;
...
FoAblakO.style=CS_HREDRAW | CS_VREDRAW;
FoAblakO.lpfnWndProc=FoAblakE;
FoAblakO.cbWndExtra=0;
FoAblakO.cbClsExtra=0;
FoAblakO.hIcon=LoadIcon(NULL,IDI_APPLICATION);
FoAblakO.hCursor=LoadCursor(NULL,IDC_ARROW);
FoAblakO.lpszMenuName=NULL;
FoAblakO.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
FoAblakO.lpszClassName="FoAblakO";
FoAblakO.hInstance=cinst;
RegisterClass(&FoAblakO);
...

WNDCLASS-nak 10 mezője van. Nézzük őket sorba:

style: az ablakosztály stílusa, példánkban ide csak az van beállítva, hogy rajzolja újra az ablakot, ha átméretezzük.

lpfnWndProc: A kezelőeljárásra mutató pointer. A példánkban ezt FoAblakE-nek hívják, később lesz szó róla.

cbWndExtra,cbClsExtra: Valami extrabájtokról szól, én mindig nullára állítom be.

hIcon: Az ikon handle-ja. A LoadIcon eljrással én az előredefiniált hagyományos ablak ikonra állítottam be.

hCursor: A kurzor handle-ja. A LoadCursor eljárással én a szabványos nyílra állítottam be. Itt lehet tulajdonképpen azt beállítani, hogy milyen legyen az egérkurzor, ha az ablak területére visszük.

lpszMenuName: A menü neve. A Resource Workshop-pal hozhatunk létre menüt, és egyszerűen annak a nevét kell ide beírni. Én ezt NULL-ra állítottam, jelezvén, hogy nincs menü.

hbrBackground: Az ablak hátterét lehet itt beállítani. Én a GetStockObject segítségével feketére állítottam ezt be.

lpszClassName: Az ablakosztály neve. Akármi lehet, de lehetőleg legyen egyedi. Én FoAblakO-ra állítottam ezt be.

hInstance: Az az instance handle, amiben az ablakosztályt létrehoztuk. Ezt az instance handle-t vehetjük a WinMain cinst paraméteréből. Ugyanis az automatikusan értéket, mikor a programunk elindul.

Végezetül ezt a kész ablakosztályt regisztráljuk, ezután már létre is hozhatjuk az ablakunkat.
(Megjegyzés a RegisterClass visszatérési értéke nulla, ha ha nem lehetett regisztrálni az ablakosztályt, pl. nem tudjuk regisztrálni, ha már van egy ilyen osztály regisztrálva.)

A WNDCLASS-t feltöltöttük, már csak a kezelő eljárásról nem esett szó. Ennek az eljárásnak vannak formai követelményei. Valahogy így kell kinéznie:

LRESULT CALLBACK WndProc(HWND ablak,UINT parancs,WPARAM wParam,LPARAM lParam);

Egyedül a név és a paraméterek nevei azok, amiket mi választhatunk meg, a többi kötött. Ezt az eljárást a Windows indítja el és adja át neki a paramétereket. Az ablak lesz az ablak azonosítója; a parancs a kezelendő esemény; a másik két paraméter pedig további adatot tárol. Általában egy switch utasítást szoktunk használni a különböző parancsok kezelésére.

Ennek az eljárásnak kell végeznie az események kezelését. Mostani példánkban egyenlőre csak annyit, hogy ki kell lépnie a programból, ha bezárjuk az ablakot:

LRESULT CALLBACK FoAblakE(HWND ablak,UINT parancs,WPARAM wParam,LPARAM lParam)
{
	switch (parancs)
	{
		case WM_DESTROY:
		{
			PostQuitMessage(0);
			return 0;
		}
		default:
			return DefWindowProc(ablak,parancs,wParam,lParam);
	}
}

Ez csak a WM_DESTROY eseményt kezeli és jelet küld a programnak, hogy lépjen ki. A return 0 utasítás azért kell, mert a WM_DESTROY végrehajtásáról tudatni kell Windows-t és ezt így lehet megtenni. Minden más eseményt a DefWindowProc kezel, jelezvén, hogy mi nem foglalkozunk azzal az eseménnyel.

Na most már végre minden meg van egy ablak létrehozásához. Már csak létre kell hozni magát az ablakot. Erre van egy utasítás, a CreateWindow:

HWND CreateWindow(
    LPCTSTR lpClassName,	// a regisztrált ablakosztály nevérere mutató pointer.
    LPCTSTR lpWindowName,	// az ablak nevére mutató pointer
    DWORD dwStyle,		// ablak stílusa
    int x,			// X koordináta
    int y,			// Y koordináta
    int nWidth,		// szélesség
    int nHeight,		// magasság
    HWND hWndParent,	// szülőablakra mutató pointer
    HMENU hMenu,		// menüre mutató pointer
    HANDLE hInstance,	// az alkalmazás instance handle-ja
    LPVOID lpParam		// ablak létrehozási adatoka mutat
);

Példa:

FoAblak=CreateWindow
	(
		"FoAblakO",
		"Saját ablak",
		WS_OVERLAPPEDWINDOW | WS_VISIBLE,
		0,0,640,480,
		NULL,NULL,cinst,NULL
	);

A példa az általunk létrehozott FoAblakO ablakosztályból hoz létre egy ablakot. Az ablak címe "Saját ablak" lesz. A WS_OVERLAPPEDWINDOW ablakstílus jelenti, hogy egy közönséges ablakot szeretnénk létrehozni, a WS_VISIBLE pedig azt, hogy a létrehozás után azonnal meg szeretnénk jeleníteni az ablakot. Láthatjuk, hogy ezek stílusjelzők kombinálhatók a | operátorral. Az ablakunk bal felső sarka a képernyő bal felső sarkában lesz: (0;0) pozíció. Az ablakunk mérete 640×480 lesz. A következő négy paraméterből, ami nem kell egyszerűen kinullozhatjuk, egyedül a hInstance paramétert állítjuk be a porogramunk instance handle-jére.

Az ablakunk ezzel készen van.

Ez fog majd megjelenni:

Főciklus

Biztosítanunk kell, hogy a programunk addig biztos fusson, amíg az ablakot ki nem ikszeljük. Ehhez létre kell hoznunk a programunk főciklusát, aminek az a feladata, hogy a Windows parancsait és az eseményeket továbbítsa az ablakok kezelő eljárásainak. Egy egyszerű főciklus így néz ki:

MSG msg;
...
while(GetMessage(&msg,NULL,0,0))
{
	TranslateMessage(&msg);
	DispatchMessage(&msg);
}

A GetMessage függvény addig vár, amíg esemény nem történik, ez idő alatt hagyja a többi programot futni, és nem terheli le a processzort. Amikor esemény történik, az MSG struktúrát feltölti, és visszatér igaz vagy hamis értékkel. Általában igaz értékkel tér vissza, csak akkor tér vissza hamis értékkel, amikor a program bezárására kap parancsot, amit a korábbi példában a PostQuitMessage utasítással értünk el.

A while ciklus ciklusmagjában két utasítás foglal helyet. Ezek közül az elsőnek, a TranslateMessage-nek, a feladata az, hogy a WM_KEYDOWN és WM_KEYUP parancsokból WM_CHAR-t csináljon, ez mindig kell, ha billentyűzeten kell valamit beírni egy szövegmezőbe. A DispatchMessage feladata az, hogy a parancsokat, eseményeket továbbitsa az ablakkezelő eljárásoknak, ennek mindig benne kell lennie a főciklusban különben az ablakkezelő eljárások egyszerűen nem fognak működni.

Lényegében ilyen a főciklus.

Egy működő Windows program

Ha Windows alá írsz programot, akkor következő 71 soros programocskát használhatod sablonként, hogy ne kelljen az egészet minden egyes programnál újra meg újra beirkálni, csak be kopipeszteled és máris van egy ablakod.

#include "windows.h"
#include "windowsx.h"
  //---------------------//
 // Rendszerdeklarációk //
//---------------------//
WNDCLASS
	FoAblakO;
HWND
	FoAblak;
  //-----------------//
 // Főablak eljárás //
//-----------------//
LRESULT CALLBACK FoAblakE(HWND ablak,UINT parancs,WPARAM wParam,LPARAM lParam)
{
	switch (parancs)
	{
		case WM_DESTROY:
		{
			PostQuitMessage(0);
			return 0;
		}
		default:
			return DefWindowProc(ablak,parancs,wParam,lParam);
	}
}
  //------------------------------//
 // Ablakosztályok regisztrálása //
//------------------------------//
void OsztalyRegisztralas(HINSTANCE cinst)
{
	FoAblakO.style=CS_HREDRAW | CS_VREDRAW;
	FoAblakO.lpfnWndProc=FoAblakE;
	FoAblakO.cbWndExtra=0;
	FoAblakO.cbClsExtra=0;
	FoAblakO.hIcon=LoadIcon(NULL,IDI_APPLICATION);
	FoAblakO.hCursor=LoadCursor(NULL,IDC_ARROW);
	FoAblakO.lpszMenuName=NULL;
	FoAblakO.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
	FoAblakO.lpszClassName="FoAblakO";
	FoAblakO.hInstance=cinst;
	RegisterClass(&FoAblakO);
}
  //---------------------//
 // Ablakok létrehozása //
//---------------------//
void AblakLetrehozas(HINSTANCE cinst)
{
	FoAblak=CreateWindow
	(
		"FoAblakO",
		"Saját ablak",
		WS_OVERLAPPEDWINDOW | WS_VISIBLE,
		0,0,640,480,
		NULL,NULL,cinst,NULL
	);
}
  //---------------//
 // Belépési pont //
//---------------//
int PASCAL WinMain(HINSTANCE cinst,HINSTANCE pinst,LPSTR Cmd,int Show)
{
	MSG msg;
	if (!pinst)
		OsztalyRegisztralas(cinst);
	AblakLetrehozas(cinst);
	while(GetMessage(&msg,NULL,0,0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

További témakörök:

(Majd lesznek. Még nem volt időm, hogy megírjam őket)