Inleiding

Welkom bij de Windows handleiding van Vampire studios. Het eerste dat je hier zult leren is hoe een window werkt en hoe je een window maakt. Ik neem aan dat je C++ al kent, of in ieder geval een groot deel. Ben je toch niet helemaal meer op de hoogte dan kun je altijd nog even terug kijken in de C++ handleiding.

Het eerste en belangrijkste ding dat je moet weten van windows is hoe het op gebruikers invoer reageert. Zodra een gebruiker iets invoert verstuurt windows een message naar een actief window. Deze message kun je vervolgens in jou programma gaan verwerken, bijv. als de gebruiker op de linker muis knop klikt je een tekst op het scherm ziet. Je kunt dus d.m.v. deze messages kijken welke knop er wordt ingedrukt en vervolgens actie ondernemen. Het deel dat voor de verwerking van de messages zorgt is de window procedure.

Je wilt natuurlijk ook niet alleen de message's verwerken, maar ook weer iets terug sturen naar het scherm. In de main functie (in windows WinMain) wordt dan ook een window gemaakt. Een window bestaat uit 2 delen, een window class, en een createwindow(ex) functie. In de window class worden (basis)gegevens van een window opgeslagen, je kunt met 1 window class dan ook meerdere windows maken. Met de createwindow(ex) functie geef je de specifieke gegevens van een bepaald window aan zoals de grootte en de titel. Ook geef je in deze functie aan welke window class je wilt gebruiken om het window te maken.

Als dit verhaal nog niet helemaal duidelijk is dan komt dat nog wel. We zullen eerst even naar een voorbeeld van een compleet window kijken waarna ik alle onderdelen appart uit zal leggen.


#define WIN32_LEAN_AND_MEAN	//Windows opties verkleinen voor snelheidswinst 

#include <windows.h>		//Windows header file

////////////////////////////////////////////////////////////////////////////////
//Window Procedure
////////////////////////////////////////////////////////////////////////////////

LRESULT CALLBACK WndProc(HWND hwnd, 
			 UINT message, 
			 WPARAM wParam, 
 			 LPARAM	lParam)	
{
  HDC         hdc;	//device context
  PAINTSTRUCT ps;	//paint stucture
  RECT        rect;     //rectangle sturcture

  switch (message)
  {
    case WM_CREATE:		//Wordt ontvangen als het window wordt gemaakt		
	break;

    case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;
          
          GetClientRect (hwnd, &rect) ;
          
          DrawText (hdc, TEXT ("hello world!"), -1, &rect,
                    DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
          EndPaint (hwnd, &ps) ;
          break;

    case WM_CLOSE:		//Wordt ontvangen als het window wordt gesloten		
	PostQuitMessage(0);			
	break;
  }
  //Niet verwerkte messages worden verwerkt door de default window procedure
  return DefWindowProc(hwnd,message,wParam,lParam);  
}

////////////////////////////////////////////////////////////////////////////////
//Main Functie
////////////////////////////////////////////////////////////////////////////////

int WINAPI WinMain(HINSTANCE	hInstance,			
		   HINSTANCE	hPrevInstance,		
		   LPSTR	lpCmdLine,			
		   int		nShowCmd)			
{
  WNDCLASSEX	wc;		//Window class	
  HWND 		hwnd;		//Window handle
  MSG		msg;		//Message
  bool		done=FALSE;	//Controle om te zien of de 

//Window class 

  wc.cbSize		= sizeof(WNDCLASSEX);		
  wc.style		= CS_HREDRAW | CS_VREDRAW;		
  wc.lpfnWndProc	= WndProc;					
  wc.cbClsExtra		= 0;				
  wc.cbWndExtra		= 0;				
  wc.hInstance		= hInstance;			
  wc.hIcon		= LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor		= LoadCursor(NULL, IDC_ARROW);			
  wc.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);	
  wc.lpszMenuName	= NULL;					
  wc.lpszClassName	= "window";				
  wc.hIconSm		= LoadIcon(NULL,IDI_WINLOGO);

  RegisterClassEx(&wc);	  //Registratie van de window class

//createwindowex functie

  hwnd=CreateWindowEx(NULL,							
       		      "window",				
		      "Mijn Window",				
		      WS_OVERLAPPEDWINDOW | 
                      WS_VISIBLE | 
                      WS_SYSMENU, 			
		      100,100,
		      400,400,
		      NULL,							
		      NULL,							
		      hInstance,							
		      NULL);
	
  while(!done)		//loop zolang done is false			
  {
    PeekMessage(&msg,NULL,0,0,PM_REMOVE);  //Controleer of er messages zijn	
			
    if (msg.message==WM_QUIT)	//Bij een quit message, maak done true			
    {
      done=TRUE;							
    }
    else									
    {
      TranslateMessage(&msg);	//verwerk messages				
      DispatchMessage(&msg);	//verstuur messages
    }
  }
  return (msg.wParam);						
}
Dit is dus een standaard window zonder extra's wat we voor windows programmeren gaan gebruiken. Het eerste wat je ziet is de window procedure die dus voor de verwerking van messages zorgt, en zorgt dat er iets op het scherm komt in de client window (zwarte deel) van het window.

De window procedure

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

De functie retourneerd een Long Result, en is van het type CALLBACK, een speciaal type die de oproepen tussen Windows en de applicatie regelt. Verder ontvangt WndProc vier parameters, hwnd, message, wParam, en lParam. De hwnd parameter is de handle wat niks anders dan de identificatie van een window waardoor windows weet op welk window bepaalde acties betrekking hebben. De handle die WndProc ontvangt is dezelfde handle als die wie geretourneerd wordt door de CreateWindow functie.

De tweede parameter is gelijk aan het message veld in de MSG structure, aan de hand hiervan kun je kijken wat voor message er verstuurt is (WM_CLOSE, WM_CREATE, enz.). De laatste 2 parameters bevatten extra informatie afhankelijk van het soort message, bijvoorbeeld de x en y positie van de muis.

Messages verwerken


{
  switch (message)
  {
    case WM_CREATE:		//Wordt ontvangen als het window wordt gemaakt		
	break;

    case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;
          
          GetClientRect (hwnd, &rect) ;
          
          DrawText (hdc, TEXT ("hello world!"), -1, &rect,
                    DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
          EndPaint (hwnd, &ps) ;
 	  break;

    case WM_CLOSE:		//Wordt ontvangen als het window wordt gesloten		
	PostQuitMessage(0);			
	break;
  }
  //Niet verwerkte messages worden verwerkt door de default window procedure
  return DefWindowProc(hwnd,message,wParam,lParam);  
}
Binnen de window procedure worden de messages verwerkt door een switch statement. Hier wordt gecontroleerd welke waarde message bevat en kun je zelf aangeven wat je met deze message wilt doen. De WM_CREATE en WM_CLOSE message worden verstuurd als het window wordt gemaakt of afgesloten. Bij de WM_CLOSE message wordt hier de PostQuitMessage() functie gebruikt zodat er een WM_QUIT message wordt verstuurd. De window procedure bevat bovendien WM_PAINT die ervoor zorgt dat er tekst op het scherm komt net zo als in de hello world handleiding van C++. Het eerste wat hier wordt gedaan is ervoor zorgen dat de device context wordt ingesteld door functie BeginPaint();. Deze functie zorgt ervoor dat het client area (zwarte gedeelte in het window) wordt gereset, en dat de DC (device context) zo wordt ingesteld dat er in de client area getekend kan worden. Met de device context kun je instellingen maken over hoe er naar het scherm wordt geschreven en is afhankelijk van een bepaald apparaat (monitor, printer), en afhankelijk van het window waar naar geschreven moet worden. Voor meer informatie kun je de eerste OpenGL handleiding bekijken waar ik een rendering context bespreek die "overeenkomt" met een device context. Om een DC te kunnen retourneren ontvangt de BeginPaint(); functie de handle van het window en het adres van een paintstructure die beginpaint instelt. De paintstructure bevat gegevens over of de background is gereset, de grote van ongeldige client area, en nog een aantal gegevens. Als we een DC hebben kunnen we het client gedeelte bepalen door GetClientRect(); op te roepen voor het window (hwnd) en stelt rect in op de grootte van het client gedeelte. Nu kunnen we dan eindelijk beginnen met de tekst door Drawtext op te roepen met als eerste parameter de device context van het window gevolgd door de tekst. De -1 geeft vervolgens aan dat de tekst door een zero teken wordt afgesloten. De laatste parameters geven aan dat de tekst 1 lijn is en horizontaal en verticaal wordt gecentreerd ten opzichte van de rect parameter.

En als laatste sturen we de messages die wij niet af willen handelen door naar DefWindowProc, en de window procedure is klaar.

Het window

Nu de window procedure af is hebben we nog een window nodig die we maken in de WinMain functie. WinMain ontvangt net zoals de Window procedure vier parameters, hInstance, hPrevInstance, lpCmdLine, en nShowCmd. De return waarde van WinMain is een int en heeft WINAPI als speciaal type. WINAPI is een calling convention die alle windows programma's gebruiken (normaal gebruik je de C calling convention).

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)

De eerste parameter die WinMain ontvangt is hInstance, wat een handle naar een instance is. Of makkelijk gezegd de identificatie van je programma. De tweede parameter, hPrevInstance wordt tegenwoordig niet meer gebruikt en is altijd NULL. De derde parameter, lpCmdLine is een pointer naar een string die je bij de uitvoer van je programma kunt invoeren. Als je bijvoorbeeld intypt "mijnprogramma.exe parameter" dan heeft lpCmdLine de waarde parameter in zich. De laatste parameter, nShowCmd bepaalt hoe je window wordt weergegeven.

Window class

Een window class bevat alle gegevens over wat voor window het wordt(een button, een text box, een edit box, enz. zijn allemaal windows). Bovendien wordt de window procedure aan de window class gekoppeld waardoor alle windows die dezelfde window class gebruiken ook dezelfde window procedure gebruiken.Een aantal instellingen van de window class zal ik hier bespreken, voor meer informatie kun je altijd bij msdn terecht.

  wc.cbSize		= sizeof(WNDCLASSEX);		
  wc.style		= CS_HREDRAW | CS_VREDRAW;		
  wc.lpfnWndProc	= WndProc;					
  wc.cbClsExtra		= 0;				
  wc.cbWndExtra	 	= 0;				
  wc.hInstance		= hInstance;			
  wc.hIcon		= LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor		= LoadCursor(NULL, IDC_ARROW);			
  wc.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);	
  wc.lpszMenuName	= NULL;					
  wc.lpszClassName	= "window";				
  wc.hIconSm		= LoadIcon(NULL,IDI_WINLOGO);
wc.cbSize: De grootte van de wndclassex structuur.
wc.style: Hier kun je de eigenschappen van het window instellen d.m.v. flags zoals CS_HREDRAW en CS_VREDRAW. Deze 2 flags zorgen ervoor dat de client area opnieuw wordt getekend als de grootte van het window verandert. Er zijn nog een aantal van deze flags, maar voor nu heb je hier genoeg aan.
wc.lpfnWndProc: Hier geef je aan welke window procedure je wilt gebruiken.
wc.cbClsExtra: Extra class informatie, voor ons is dit altijd nul.
wc.cbWndExtra: Extra window informatie, voor ons is dit altijd nul.
wc.hInstance: Dit is de instance handle (identificatie) van de applicatie afkomstig van WinMain.
wc.hIcon: Laad een groot icoon voor files en folders. NULL geeft aan dat je standaard windows icons gebruikt.
wc.hCursor: Laad een standaard windows cursor.
wc.hbrBackground: Stelt de achtergrond kleur in van het window.
wc.lpszMenuName: Geeft aan of het window een menu heeft, hier null dus geen menu.
wc.lpszClassName: De naam van de class. Deze is nodig voor de createwindow functie voor identificatie.
wc.hIconSm: Laad een klein icoon voor bijv. geminimaliseerde windows.

Als alle instellingen zijn ingevuld moet je de class nog registreren met de registerclass functie, en je bent klaar met de window class.

RegisterClassEx(&wc);

Create window

Nu je de window class klaar hebt kun je windows gaan maken door de CreateWindowEx functie.

hwnd=CreateWindowEx(NULL,							
       		      "window",				
		      "Mijn Window",				
		      WS_OVERLAPPEDWINDOW | 
                      WS_VISIBLE | 
                      WS_SYSMENU, 			
		      100,100,
		      400,400,
		      NULL,							
		      NULL,							
		      hInstance,							
		      NULL);
De eerste parameter die van belang is is "window" die aangeeft welke class je voor het window gebruikt. Na de class naam komt de titel van het window die je boven in de (blauwe balk) ziet.

De volgende parameter is de dwStyle en geeft d.m.v. flags de steil van het window aan.

WS_OVERLAPPEDWINDOW: Maakt een overlappend window met titel en rand.
WS_VISIBLE:Maakt een window dat zichtbaar is.
WS_SYSMENU:Maakt een window met een menu in de titelbar.

Vervolgens geef je de x en y positie van je window aan en de grootte van je window, in dit geval een window van 400 X 400 pixels met de linker bovenhoek 100 pixels naar rechts en 100 pixels naar beneden. Er is geen parent window en ook geen menu dus deze 2 parameters zijn "null". En als laatste moet je nog de programma instance en eventuele extra parameters aangeven.

Message loop

Nu je het window kunt laten zien wil je de gebruiker natuurlijk ook de kans geven om hier wat te doen, dit gebeurt door de message loop. De message loop kijkt of er een bericht is verwerkt de message en gooit deze vervolgens weg. De loop ziet er als volgt uit.

while(!done)		//loop zolang done is false			
  {
    PeekMessage(&msg,NULL,0,0,PM_REMOVE);  //Controleer of er messages zijn	
			
    if (msg.message==WM_QUIT)	//Bij een quit message, maak done true			
    {
      done=TRUE;							
    }
    else									
    {
      TranslateMessage(&msg);	//verwerk messages				
      DispatchMessage(&msg);	//verstuur messages
    }
  }
  return (msg.wParam);
Het is een while loop die loopt zolang done false is (maar dat was al wel duidelijk denk ik). De PeekMessage functie wordt gebruikt om te kijken of er messages in de message queue zijn.

PeekMessage(&msg,NULL,0,0,PM_REMOVE);

Als een message uit de message queue komt wordt deze in de msg parameter opgeslagen. De tweede parameter is de instance van de applicatie en is in dit geval "null" zodat de message queue van deze applicatie wordt gecontroleert op messages. Als laatste worden wMsgFilterMin en wMsgFilterMax nul gemaakt en zorgt PM_REMOVE er voor dat afgehandelde messages worden verwijdert.

Nu gaan we eerst kijken of er een quit message wordt ontvangen, gebeurt dit niet dan worden de TranslateMessage en DispatchMessage functies opgeroepen. De TranslateMessage functie vertaalt eerst een message, bijv. als de escape toets ingedrukt wordt vertaalt TranslateMessage dit naar VK_ESCAPE. De DispatchMessage functie stuurt de message vervolgens door naar windows waarna deze bij de window procedure van je applicatie komt.

Als het goed is moet je window nu werken, het was veel werk maar je kunt dit window nu voor de rest van de handleidingen gebruiken.

Succes

Vampire,