Det første vindue
- skrev denne artikel Onsdag, 19 Juli 2006
Forudsætninger: En grundig forståelse af variable, funktioner og pointere. Desuden er det smart at forstå hvordan punktum kan hive felter ud af en klasse.
Tiden er endelig kommet, hvor du skal træde ind i en ny og forunderlig verden, en verden af vinduer. Indtil nu har vi af og til set nogle MessageBoxe, men til tider er det ikke nok til at lave et program, vi har brug for rigtige vinduer med knapper, tekstfelter og sjove figurer, i denne tutorial vil jeg gennemgå det simpleste vindue du kan forestille dig. Det tomme vindue.
001   #include <windows.h>
002  
003   LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
004   #define szClassName "Min klasses navn"
005  
006   int WINAPI WinMain (HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszArgument,int nCmdShow){
007    HWND hWnd;
008    MSG msg;
009    WNDCLASS wc;
010   
011    wc.style = 0;
012    wc.lpfnWndProc = WndProc;
013    wc.cbClsExtra = 0;
014    wc.cbWndExtra = 0;
015    wc.hInstance = hInstance;
016    wc.hIcon = LoadIcon(0,IDI_WINLOGO);
017    wc.hCursor = LoadCursor(0,IDC_ARROW);
018    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
019    wc.lpszMenuName = 0;
020    wc.lpszClassName = szClassName;
021    if(!RegisterClass(&wc)){
022      MessageBox(0,"Dette burde ALDRIG ske, medmindre nogen piller ved noget.\n"
023                    "Dette går jeg ikke ud fra at de gør, men lad os checke alligevel.",0,MB_OK);
024      return 0;
025    }
026    hWnd = CreateWindow(szClassName, "Det tomme vindue", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,400, 300, 0, 0, hInstance, 0);
027   
028    ShowWindow(hWnd,nCmdShow);
029  
030    while (GetMessage(&msg,0,0,0) == TRUE){
031      TranslateMessage(&msg);
032      DispatchMessage(&msg);
033    }
034    return msg.wParam;
035   }
036  
037   LRESULT CALLBACK WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){
038    switch (msg){
039      case WM_DESTROY:
040        PostQuitMessage(0);
041        break;
042      default:
043        return DefWindowProc (hWnd, msg, wParam, lParam);
044    }
045    return 0;
046   }

Linie 3: Er en erklæring som siger at vi senere vil lave en funktion med dette navn, og disse argumenter, denne funktion returnerer i vores tilfælde en LRESULT og det er ydermere en CALLBACK funktion (hvilket gør det nemmere når vi skal bruge den som pointer).
Linie 4: Når man laver et vindue skal det tilhøre en klasse, en klasse har navn som man kan kende den på, Windows har nogle indbyggede klasser som vi vil se i senere tutorials. Grunden til at jeg definerer navnet som en konstant er så både klassen selv wc og CreateWindow funktionen kan bruge den uden jeg behøver bekymre mig for stavefejl.
Linie 7: HWND er en indbygget variabeltype i Windows, det er en window handle. Handle er bare et smart ord for pointer, vi vil se mange handles senere hen, og generelt bruger vi dem bare sammen med de indbyggede funktioner i Windows. Da det er en window handle er det altså en pointer til et vindue.
Linie 8: En MSG er en struct, den indeholder en besked og informationer om denne besked, beskeder sendes til vinduer som så tager sig af at reagere ud fra de beskeder de får. Et eksempel på en besked er at oprette vinduet eller at brugeren har trykket på krydset oppe i hjørnet så vi skal lukke.
Linie 9: WNDCLASS er det før omtalte vindues klasse, dette er også en struct med informationer om hvordan netop dette vindue skal være, det indeholde f.eks. info om hvilken funktion vinduet bruger til at fortolke de beskeder det får og om der skal tages hensyn til dobbeltklik.
Linie 11 til 20; her bliver felterne i WNDCLASS så sat et af gangen, det første er stilen, den kan have mange forskellige og de kan f.eks. gøre så vinduet selv kan tegne sig igen (det vil vi se på senere) eller vi kan gøre så vi kan modtage dobbeltklik, hvis man vil vide mere om hvad der kan bruges her kan man med fordel slå op på www.msdn.com.
lpfnWndProc er en pointer til den funktion der skal tage sig af beskederne der sendes til vinduet. Bemærk at det IKKE er et funktionskald da der ikke er ’(’ og ’)’ bagpå.
cbClsExtra og cbWndExtra er til ekstra ting et vindue kan få brug for, vi vil foreløbig bare sætte dem til 0 (og det vil vi nok blive ved med et LAAAAANGT stykke tid).
hInstance er en instance handle, den er nærmest en slags pointer til programmet selv, grunden til at vi skal bruge den her er at det kun er programmet selv der kan bruge den vinduesklasse vi laver.
hIcon er den ikon som vinduet skal bruge både når vi hopper rundt mellem vinduer med alt+tab men også når man kigger i proceslinjen eller i vinduet venstre hjørne. Man burde kunne vælge de indbyggede ikoner på denne måde, alligevel ser det ikke ud til at virke her. Det er som sådan ligegyldigt da vi normalt vil benytte vores egne ikoner i stedet for de indbyggede.
hCursor lader vi blive som den er lige nu, da den normale cursor er fin nok. IDC_CROSS hvis man gerne vil have et kryds som cursor, man kan også lave sin egen cursor, eller man kan sætte cursoren til 0 og så selv lave den om når den går ind i vinduet. Men lige nu lader jeg den være på standard.
lpszClassName er klassens navn, dette skal bare være en streng som man så bruger igen i CreateWindow.
Linie 21: Når vi har udfyldt alle disse ting registrerer vi klassen, dette gør så CreateWindow kan bruge den, dette kan gå galt hvis en klasse med det navn allerede findes eller hvis der er noget meget galt. Det er normalt ikke noget man behøver at tage højde for, men det er måske ok at gøre det og fortælle brugeren hvis det ikke gik godt.
Linie 26: Vi skal så lave et vindue og tildele det til vores vindue pointer holder ting. Der er mange argumenter til at lave et vindue, og med tiden lærer man dem udenad. Jeg vil lige nævne dem her:
Navn på klasse: Det har jeg skrevet nok ok!
Navn på vindue: et vindue har et lille navn i titelbaren, ellers var der jo ikke meget titelbar over den.
Stil: her skriver jeg WS_OVERLAPPEDWINDOW og det indikerer at jeg vil have et ’almindeligt’ vindue, jeg vil demonstrere andre en anden gang og ellers kan man slå op på www.msdn.com.
Start koordinater x og y: jeg har valgt bare at oprette vinduet der hvor windows synes det er smart, men man kunne skrive 0,0 eller 10,10. Dette er så afstanden i pixel fra øverste venstre hjørne et lidt anderledes koordinatsystem, det følger almindelig læseretning så man vender sig hurtigt til det. CW_USEDEFAULT er altså bare hvad windows synes, og er afhængigt af hvir den sidst var, jeg har nogen gange benyttet andre koordinater, f.eks. hvis man vil have et centreret vindue som f.eks. en SplashScreen.
De 400,300 er vinduets størrelse til at starte med, bemærk at det er hele vinduet med titelbar osv. så man er nødt til at lave tricks hvis man vil være sikker på at have 400x300 pixel man kan tegne i så brugeren kan se det.
0 er vinduets forældrer, man kan have flere vinduer inde i hinanden hvis man bruger WS_CHILD på de efterfølgende og sætter dette til det første vindue, jeg vil demonstrere dette i en senere tutorial.
Nullet efter er for menuen, vi vil ikke have sådan en filer menu ting endnu da vi gerne vil have et tomt vindue, men man kan sætte den her (man kan også sætte den senere med magiske tricks :-))
hInstance vinduet skal vide hvilket program det hører til, på den måde ved det hvilke klasser det har adgang til.
Den sidste er til ekstra information man synes vinduet skal have med når det oprettes, det er ikke noget jeg bruger så tit, men det kommer nok med i en senere tutorial som et lille bipunkt.
Linie 28: Når nu vi har lavet sådan et fint vindue ville det være smart at vise det til brugeren, bemærk at jeg gør brug af nCmdShow som vi fik med fra windows da programmet startede, hvis du nogen sinde har tænkt over hvorfor kontrollerne til at minimere et program fra starten i en genvej ikke virkede, er årsagen nok at programmet ikke benytter nCmdShow som argument her. Dette er også tilfældet for enkelte af AODASofts spil, da de benytter sig af et trick og da jeg ikke var helt så dygtig og smart som jeg er nu. Bemærk at hvis man helt undlader denne linie kommer der ikke noget til syne på skærmen, vinduet eksisterer stadig men er skjult derfor afslutter programmet ikke og man er nødt til at dræbe processen manuelt med ctrl+alt+delete eller bare job listen i windows NT og senere.
Linie 30 til 33: denne while løkke sørger for at programmet modtager beskeder og sender det til de forskellige vinduers meddelelses håndterings funktioner. Der skal være sådan en i programmet, der skal som regel kun være 1 og den ser ofte lidt anderledes ud da der nogle gange skal ændres lidt på nogle ting. Man bør undgå at slette ==TRUE da den benytter sig af windows BOOL som kan tage andet end 0 og 1, i dette tilfælde returnerer den -1 hvis vinduet laver en forfærdelig fejl og så er det jo rart at programmet ikke bare ligger og suger ressourcer fortsat.
Return msg.wParam siger at programmet skal returnere beskeden fra PostQuitMessage til windows, og det bør man altid gøre. Dette gør det muligt at skrive programmer der kalder dette program og ser om det dør på forfærdeligste vis og som muligvis endda kan give indsigt i hvorfor.

Resten af programmet er en besked håndterings funktion, den modtager et tal som indikerer hvad type besked det er og så benytter jeg en switch til at kigge på forskellige mulligheder, i dette tilfælde er der kun en mulighed, nemlig WM_DESTROY, denne bliver sendt til et vindue lige før det slettes helt fra hukomelsen, hvis man ikke laver en PostQuitMessage her lukker programmet aldrig. Man bør altid returnere 0 på beskeder man håndterer og lade DefWindowProc håndtere dem man ikke selv kigger på, i dette tilfælde kigger vi ikke på WM_CLOSE men man kan alligevel lukke vinduet ved at klikke i højre hjørne fordi det er en standardbesked, vælger man at sætte en MessageBox ind ved WM_CLOSE for at spørge brugeren om de er sikre skal man bruge DestroyWindow hvis de klikker ja, så bliver WM_DESTROY kaldt og programmet lukker. Smart ik’?
Vi vil se eksempler på mange flere beskeder senere… Som altid kan man skrive til mig på admin@AODASoft.net