Windows

 

In dit artikel ga ik uitleggen hoe je een window programmeert. Met deze window heb je een mooi begin voor je game. Al enthousiast? Vast wel, dus laten we maar beginnen.

 

Zoals jullie waarschijnlijk wel weten is Windows bericht gestuurd. Het reageert dus op berichten van de gebruiker. Deze mogelijkheid zorgt ervoor dat Windows multitasked is. Je kan dus meerdere programma’s tegelijk gebruiken, hetgeen wat DOS niet kan. Ik kan je nog wel meer hierover vertellen, maar dat is niet van belang. Je moet alleen niet vergeten dat Windows event gestuurd is.

 

Voordat we kunnen beginnen, moet je iets weten van de Hungarian Notation. In een bedrijf waar honderden of zelfs duizenden programmeurs aan hetzelfde programma werken, moet je een standaard voor code verzinnen. Microsoft heeft toen het volgende verzonnen. De Hungarian Notation.

 

Voorvoegsel

Data type

c

Char

by

BYTE(unsigned char)

n

short of int

i

int

x, y

short (zijn de coordinaten van bijv. een hoek)

cx, cy

short ( voor x en y lengtes.)

b

BOOL (waar of onwaar)

w

UINT(= unsigned int) of WORD( unsigned)

l

LONG

dw

DWORD (= unsigned long)

fn

Functie pointer

s

String

sz, str

String

lp

32-bit Long pointer

h

Handle

msg

Bericht

 

Een handle refereert aan een Windows object bijvoorbeeld een window.

 

Als je meer wilt weten over de Hungarian Notation, raad ik je aan om in bijvoorbeeld de SDK handleiding te kijken.

 

Goed, nu kunnen we echt beginnen. Allereerst de headers.

 

// Eerste window – window.cpp

 

// INCLUDES ///////////////////////////////////////////////////////////////

#define WIN32_LEAN_AND_MEAN // geen MFC

#include <windows.h>

#include <windowsx.h>

 

 

// DEFINES /////////////////////////////////////////////////////////////////

 

// Zet WINDOW_CLASS_NAME op WINCLASS1

#define WINDOW_CLASS_NAME  “WINCLASS1”

 

// Globals //////////////////////////////////////////////////////////////////////

 

// Functions //////////////////////////////////////////////////////////////////

 

Goed dat waren de headers. Ik neem aan dat het meeste wel duidelijk is. #define WIN32_LEAN_AND_MEAN betekent dat er geen MFC in onze code gebruikt wordt. MFC is erg makkelijk voor zakelijke programma’s maar niet van belang voor spelontwikkeling, waar ik met deze tutorial wel op doel.

 

Goed, ik ga nu eerst even wat dingen door elkaar husselen voor de duidelijkheid. Op het eind zie je de code in goede volgorde, dus zoals ik het nu vertel loopt het niet helemaal op volgorde.

 

De window class

 

Voordat we echt windows kunnen programmeren moeten we eerst de window class instellen. Dat doe je met de functie WNDCLASSEX. Je hebt ook nog WNDCLASS, maar deze zal al gauw verdwijnen. Dit is het prototype van _WNDCLASSEX

 

Typedef struct  _WNDCLASSEX

{

                UINT                      cbSize;

                UINT                      style;

                WNDPROC                lpfnWndProc;

                Int                          cbClsExtra;

Int                          cbWndExtra;

HANDLE                hinstance;

HICON                  hIcon;

HCURSOR                  hCursor;

HBRUSH                hbrBackground;

LPCTSTR                lpszMenuName;

LPCTSTR                lpszClassName;

HICON                  hIconSm;

} WNDCLASSEX;

 

Dit is het prototype. Om nu de class te kunnen creëren hoeven we hem alleen maar in te vullen, makkie toch?

 

Allereerst zeggen we tegen de compiler dat we een nieuwe class gaan maken. Dat doen we zo.

 

WNDCLASSEX                winclass;  // een blanke klasse

 

Het eerste veld is een van de belangrijkste. Het is de grootte van de klasse. Daar zijn 2 redenen voor. De ontvanger kan nu kijken of hij werkelijk alle informatie heeft ontvangen en niks mist. Daarnaast hoeft de compiler het nu ook niet meer te berekenen, dus een kortere compileer tijd. Het zetten van de grootte doen we zo:

 

winclass.cbSize = sizeof(WNDCLASSEX);

 

Het volgende veld bevat alle stijlen. Er zijn veel van deze flags, je kunt het hele overzicht in de SDK* help files vinden. Ik laat hier een korte selectie zien van de meeste gebruikten.

 

CS_HREDRAW

Hertekent de window als er horizontaal beweging of verandering is opgetreden.

CS_VREDRAW

Hertekent de window als er verticaal beweging of verandering is opgetreden

CS_OWNDC

Kent een uniek device context toe. Dit ga ik een later artikel uitleggen

CS_DBLCLKS

Stuurt een dubbel klik bericht naar de WinProc wanneer er dubbel geklikt word.

 

Dit zijn de meest gebruikte flags. Zo zet je het style field.

 

winclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;

 

Het volgende veld is lpfnWndProc. Dit is een pointer naar een functie. Deze functie behandelt alle berichten die het programma toegestuurd krijgt. Deze functie is een zogeheten CALLBACK functie. Windows roept deze functie aan wanneer er wat gebeurt of dat als je hem zelf aanroept. Op deze manier werkt de berichten loop, maar hier later meer over. Dit is hoe we hem zetten.

 

winclass.lpfnWndProc = WinProc;                  // Dit is de functie pointer

 

De volgende 2 velden worden bijna nooit gebruikt, gewoon simpelweg op 0 zetten.

 

winclass.cbClsExtra = 0;

winclass.cbWndExtra = 0;

 

Dan komt het hInstance veld. Hier vul je gewoon in wat ook in de WinMain staat.

 

winclass.hInstance = hinstance;

 

Dit is iets wat je heel veel te zien krijgt. hInstance is een zogenoemde handle. Handles zijn identifiers op een interne Windows type. Het zijn eigenlijk een soort van pointers.

 

Nu krijgen we alle grafische objecten. Hier werken we veel met functies. Als je ze niet kent, kan je hun prototypes opzoeken in de help van bijvoorbeeld de SDK*.

 

Het eerste veld is de icon die je window bevat. Je kan hier je eigen icoon gebruiken, maar nu zullen we een standaard icoon gebruiken. Hier gebruiken we Load_Icon() voor.

 

winclass.hIcon  = LoadIcon(NULL, IDI_APPLICATION);

 

Dit laad de standaard icoon, niet leuk, maar effectief.

 

Op de helft!

 

 

SDK = Software Development Kit.  simpelweg je compiler.

Nu wordt het tijd om de cursor erbij te halen. Hier gebruiken we Load_Cursor() voor en wederom de standaard cursor.

 

winclass.hCursor = LoadCursor(NULL, IDC_ARROW);

 

Al het verschil opgevallen bij de ICON en de CURSOR. ICON gebruikt IDI en CURSOR IDC, op deze manier kun je ze onderscheiden van elkaar.

 

Het wordt tijd dat we de achtergrond een kleurtje gaan geven. Hiervoor maak je gebruik van de GDI. Hier leg ik in een later artikel alles over uit.  Zo stellen we de achtergrond in.

 

winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);

 

Ik gebruik (HBRUSH) om de return van GetStockObject te converteren naar het gewenste type. Zonder dat werkt het niet.

 

Als je gebruikt maakt van een menu, moet je dat ook in de class aangeven. Hoe je menu’s maakt leg ik nog later uit. Voor nu gebruik je dit.

 

winclass.lpszMenuName = NULL;

 

We gaan de class structuur nu een naamegeven. Hierbij moet je niet vergeten dat je de naam pas kan gebruiken, wanneer de class geregristreerd is. Tot die tijd gebruik je WNDCLASSEX.

 

winclass.lpszClassName = WINDOW_CLASS_NAME;  // LET OP! WINDOW_CLASS_NAME is een define, weet     je nog ( zie headers ) . Hij refereert aan winclass1.

 

Bijna klaar, gelukkig. Nu krijgen we het kleine icoonthe wat je in de titelbalk ziet, dit is bijna gelijk aan de regel voor het grote icoontje.

 

winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

 

We zijn klaar, met de window class dan. Nu moeten we nog alle velden voor de window zelf invullen. Maar goed, hieronder zie je een volledige window class definitie.

 

WNDCLASSEX                winclass

 

// vul de window class

winclass.cbSize                        = sizeof(WNDCLASSEX);

winclass.style                      = CS_DBLCLKS | CS_OWNDC |

                                               CS_HREDRAW | CS_VREDRAW;

winclass.lpfnWndProc       = WindowProc;

winclass.cbClsExtra         = 0;

winclass.cbWndExtra      = 0;

winclass.hInstance           = hinstance;

winclass.hIcon                    = LoadIcon(NULL, IDI_APPLICATION);

winclass.hCursor                                = LoadCursor(NULL, IDC_ARROW);

winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);

winclass.lpszMenuName                = NULL;

winclass.lpszClassName                = WINDOW_CLASS_NAME;

winclass.hIconSm            = LoadIcon(NULL, IDI_APPLICATION);

 

Nu hoeven we de class alleen nog maar te registreren. Dat doen we met de volgende regel. Vergeet trouwens niet dat je nog niet de class name gebruiken, maar dat je de class structuur (winclass)  moet gebruiken.

 

RegisterClassEx(&winclass);

 

Vanaf nu mag je wel de class name gebruiken. In dit geval  WINDOW_CLASS_NAME, de define die staat voor winclass1.

 

Eindelijk, de window zelf!

 

Ja, heb je al van alles ingevuld blijk je nog geen window te hebben. De class is maar de basis voor de window! We gaan nu echt de window samenstellen. Dit is zijn prototype en die nemen we daarna even gauw door.

 

 CreateWindowEx(dwExStyle,

lpClassName,

lpWindowName,

dwStyle,

x, y,

nWidth, nHeight,

hWndParent,

hMenu,

hInstance,

lpParam);

 

Hier een korte uitleg van iedere parameter.

 

-          dwExStyle, dit is heel erg vergevorderd, zelfs zo erg dat het bijna nooit gebruikt wordt. Stel het dus in op NULL.

-          lpClassName, hier vul je de naam van de zojuist gecreërde class in. Gebruik gewoon de global WINDOW_CLASS_NAME in ons voorbeeld.

-          lpWindowName, hier stel je de window naam in. Dit is ook gelijk hetgeen wat in de bovenbalk komt te staan.

-          dwStyle, dit zijn weer style flags. zie de volgende tabel voor zijn meest gebruikte waardes.

-          x, y, Hiermee geef je de coordinaten waar je wilt dat de window begint. Voor standaard gebruik je 0,0.

-          nWidth, nHeight. Hier kan je de breedte en hoogte van je window instellen.

-          hWndParent, stel deze standaard in op NULL

-          hMenu. Als je gebruik maakt van een menu, zet je hier de handle neer. Voorlopig kan je dat nog niet, dus gebruik je NULL.

-          hInstance, dit kopieer je weer van WinMain, vul dus hinstance in.

-          lpParam.Vergevorderd, stel in op NULL.

 

WS_POPUP

Voor een popup window

WS_OVERLAPPED

Voor een window met een titelbalk en een border.

WS_OVERLAPPEDWINDOW

Voor een window met een titelbalk, border, menu en minimaliseer en maximaliseer knoppen.

WS_VISIBLE

Zegt tegen Windows dat het scherm bij het opstarten zichtbaar is.

 

En op deze manier maak je een window van 400 bij 400 pixels:

 

if(!(hwnd = CreateWindowEx(NULL,

                                               WINDOW_CLASS_NAME,

                                               “Mijn Eerste Window!”,

WS_OVERLAPPEDWINDOW | WS_VISIBLE,

0,0,

400,400,

NULL,

NULL,

hinstance,

NULL)))

return(0);

 

Goed. We zijn al heel ver, maar toch niet klaar. Weten jullie nog dat we bij de klasse definitie een functie moesten opgeven. We gaan nu die functie samenstellen.

 

De Event Handler

 

Zoals eerder gezegd is Windows gebaseerd op berichten. Je hebt dus een functie nodig die de berichten verwerkt. Deze functie schrijf je helemaal zelf. Je hebt bij het opgeven van de window class al zijn naam opgegeven. Dat deed je met deze regel.

 

winclass.lpfnWndProc = WinProc;

 

Onze functie gaat dus WinProc heten. Deze functie moet je zelf schrijven, je kan hem dus naar je eigen wensen maken. De volgende prototype is een goed voorbeeld van een Window Procedure.

 

LRESULT CALLBACK WinProc(

                                               HWND                hwnd,

                                               UINT                msg,

                                               WPARAM wparam,

                                               LPARAM lparam);

 

HWND is hier de window handle, maar dat wist je al. Toch? Msg is het bericht. Zie de tabel voor enkele berichten. Deze worden door Windows automatisch verstuurd, maar je kan ze ook zelf sturen. WPARAM en LPARAM zijn verdere info van het bericht, maar daar heb je nu niks mee te maken.

 

WM_CREATE

Word verstuurd wanneer het venster voor het eerst wordt gemaakt

WM_DESTROY

Gestuurd wanneer het venster wordt afgesloten

WM_QUIT

Wanneer het venster echt is afgesloten. Dus ook de window class is verwijderd.

WM_PAINT

Wordt gestuurd wanneer het venster hertekent moet worden.

WM_SIZE

Wordt gestuurd wanneer de venster van grootte veranderd.

 

Vergeet LRESULT en CALLBACK niet. Dit is van belang voor een goede werking van de functie.

 

Dit is een WinProc die de belangrijkste berichten verwerkt.

 

LRESULT CALLBACK WindowProc(HWND hwnd,

                                                                                                 UINT msg,

                            WPARAM wparam,

                            LPARAM lparam)

{

// this is the main message handler of the system

PAINTSTRUCT                  ps;                          // used in WM_PAINT

HDC                                                      hdc;                // handle to a device context

 

// what is the message

switch(msg)

                {             

                case WM_CREATE:

        {

                                // do initialization stuff here

 

        // return success

                                return(0);

                                } break;

 

                case WM_PAINT:

                                {

                                // simply validate the window

                                hdc = BeginPaint(hwnd,&ps);    

                                // you would do all your painting here

        EndPaint(hwnd,&ps);

 

        // return success

                                return(0);

                                } break;

 

                case WM_DESTROY:

                                {

                                // kill the application, this sends a WM_QUIT message

                                PostQuitMessage(0);

 

        // return success

                                return(0);

                                } break;

 

                default:break;

 

    } // end switch

 

// process any messages that we didn't take care of

return (DefWindowProc(hwnd, msg, wparam, lparam));

 

} // end WinProc

 

Als er andere berichten zijn naast WM_CREATE, WM_DESTROY en WM_PAINT dan sturen we die overige berichten naar de standaard WinProc, ook wel DefWindowProc genoemd. Het is heel simpel gewoon return( ) en dan de aanroep naar DefWindowProc( ) met alle gegevens die je ook in je eigen WinProc( ) gebruikt.

 

Let op dat WM_DESTROY niet de werkelijke afsluitingsfase is. Hier kan je alle geheugen en resources vrijgeven. Als je dat hebt gedaan kan je de werkelijke WM_QUIT sturen. Hier bestaat zelfs een functie voor. PostQuitMessage( ). Met een willekeurige cijfer, vaak 0.

 

De code bij WM_PAINT leg ik in mijn volgend artikel uit. Het tekent nu simpelweg niks, maar zorgt er voor dat de window wel geldig blijft.

 

De main loop

 

Je kan hier kiezen uit 2 mogelijkheden. Ik maak er maar van 1 gebruik, want deze is wat realistischer. Ik zet hem hier neer en leg hem dan uit.

 

 

 

while(TRUE)

{

// test of er een bericht is

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

{

 // test if it’s a quit

if (msg.message == WM_QUIT)

break;

 

// translate any accelerator keys

TranslateMessage(&msg);

 

 // send the message to the window proc

                   DispatchMessage(&msg);

} // end if

 

} // end while

 

// return to Windows like this

return(msg.wParam);

 

Allereerst stellen we dat de loop altijd geldig blijft. Vervolgens kijken we of er wel een bericht is. Zo ja, test dan of het geen quit is. Dan vertalen we de berichten en sturen ze naar WinProc. Mocht het een quit zijn, dan wordt dat bericht naar WinMain gereturned.

 

Zal ik je eens wat vertellen? We zijn klaar! Je weet nu genoeg om je eigen class en window te maken. Gefeliciteerd! Als opdracht moet je het maar eens proberen! Als het niet lukt staat hieronder de source, om je code na te kijken. Veel succes!

 

Ten slotte nog een laatste opmerking. Maak gebruik van commentaar en veel ook! Ik gebruik het liefst Engels, zodat ik het ook makkelijk op Engelse forums kan plaatsen, maar je moet zelf kijken wat je het lekkerst vind werken. Gebruik ook zinvol commentaar! Zeg niet wat het moet doen, maar wat er moet gebeuren. Dus niet

 

// Stel A & B in op 2, C wordt 4.

int A, B  = 2;

int C = A + B;

 

Maar doe het zo.

 

// Stel int A & B in op een getal. C wordt de som van A & B.

int A, B = 2;

int C = A + B;

 

Ik hoop dat je het snapt. Succes!

 

Paul de Raaij

Voor meer info of heb je foutjes contact me @ pderaaij@kabelfoon.nl.

 

Een geldige source

// NO MFC/////////////////////////////////////

 

#define WIN32_LEAN_AND_MEAN

 

#define WINDOW_CLASS_NAME "winclass1"

 

///////////////////////////////////////////////////////

 

// INLUDES ////////////////////////////////////////////

 

#include <windows.h>

#include <windowsx.h>

#include <mmsystem.h>

 

//////////////////////////////////////////////////////

 

// GLOBALS //////////////////////////////////////////////////////////

 

HWND                main_window_handle                                      = NULL;

HINSTANCE main_instance                    = NULL;

 

//////////////////////////////////////////////////////////////////////

 

// FUNCTIONS ////////////////////////////////////////////////////////

 

LRESULT CALLBACK WindowProc(HWND hwnd,

                                                                UINT msg,

                                                                WPARAM wparam,

                                                                LPARAM lparam)

{

// this is the main message handler of the system

PAINTSTRUCT                  ps;                          // used in WM_PAINT

HDC                                      hdc;                // handle to a device context

 

// what is the message

switch(msg)

                {             

                case WM_CREATE:

        {

                                // do initialization stuff here

 

        // return success

                                return(0);

                                } break;

 

                case WM_PAINT:

                                {

                                // simply validate the window

                                hdc = BeginPaint(hwnd,&ps);    

                                // you would do all your painting here

        EndPaint(hwnd,&ps);

 

        // return success

                                return(0);

                                } break;

 

                case WM_DESTROY:

                                {

                                // kill the application, this sends a WM_QUIT message

                                PostQuitMessage(0);

 

        // return success

                                return(0);

                                } break;

 

                default:break;

 

    } // end switch

 

// process any messages that we didn't take care of

return (DefWindowProc(hwnd, msg, wparam, lparam));

 

} // end WinProc

 

 

////////////////////////////////////////////////////////////

 

// WINMAIN /////////////////////////////////////////////////

 

int WINAPI WinMain(                HINSTANCE hinstance,

                                                                              HINSTANCE hprevinstance,

                                                                              LPSTR lpcmdline,

                                                                              int ncmdshow)

{

 

WNDCLASSEX winclass; // this will hold the class we create

HWND      hwnd;  // generic window handle

MSG                         msg;                      // generic message

 

// first fill in the window class stucture

winclass.cbSize         = sizeof(WNDCLASSEX);

winclass.style                                      = CS_DBLCLKS | CS_OWNDC |

                          CS_HREDRAW | CS_VREDRAW;

winclass.lpfnWndProc       = WindowProc;

winclass.cbClsExtra                         = 0;

winclass.cbWndExtra                      = 0;

winclass.hInstance                           = hinstance;

winclass.hIcon                                    = LoadIcon(NULL, IDI_APPLICATION);

winclass.hCursor                                = LoadCursor(NULL, IDC_ARROW);

winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);

winclass.lpszMenuName                = NULL;

winclass.lpszClassName                = WINDOW_CLASS_NAME;

winclass.hIconSm        = LoadIcon(NULL, IDI_APPLICATION);

 

// register the window class

if (!RegisterClassEx(&winclass))

                return(0);

 

// create the window

if (!(hwnd = CreateWindowEx(NULL,                  // extended style

                            WINDOW_CLASS_NAME,     // class

                                                                                                  "Basic Window", // title

                                                                                                 WS_OVERLAPPEDWINDOW | WS_VISIBLE,

                                                                                                  0,0,             // initial x,y

                                                                                                 400,400,  // initial width, height

                                                                                                 NULL,        // handle to parent

                                                                                                 NULL,        // handle to menu

                                                                                                 hinstance,// instance of this application

                                                                                                 NULL))) // extra creation parms

return(0);

 

// save the handles

main_window_handle     = hwnd;

main_instance                    = hinstance;

 

// begin main event loop

 

while(TRUE)

                {

    // test if there is a message in queue, if so get it

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

                   {

                   // test if this is a quit

       if (msg.message == WM_QUIT)

           break;

                              

                   // translate any accelerator keys

                   TranslateMessage(&msg);

 

                   // send the message to the window proc

                   DispatchMessage(&msg);

                   } // end if

 

                } // end while

 

// return to Windows like this

return(msg.wParam);

 

} // end WinMain