10. Windowsprogrammering

 

 

·     WinMain().

 

·     Programfönstret.

 

·     Muspekaren.

 

·     Ikonen.

 

·     Menyn.

 

·     Koppla menykommando.

 

·     Tangentbord.

 

·     Mus.

 

 

 

 

 

 

 

 

 

 

 


WinMain().

 

Än så länge har vi endast programmerat för konsol. Då såg vi att man alltid måste ha en huvudfunktion som heter main(). Detta beror av att Dos alltid startar med den funktionen. När vi nu byter operativsystem, finner vi att Windows har brutit den gamla traditionen, och startar i stället med funktionen WinMain().

 

Till skillnad från main(), som kan ta emot två parametrar, argc och argv, vilka innehåller de parametrar som eventuellt angavs vid programstart, och som kan returnera ett heltal, har WinMain() ett antal fasta argument, och bör returnera en avslutningskod, ett heltal.

 

Slår vi upp WinMain() i hjälpen finner vi att funktionen tar fyra argument:

 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance

                   LPSTR lpCmdLine, int nShowCmd);

 

Tittar vi först på ‘WINAPI’ så står det att det ersätter ‘FAR PASCAL’, vilket i sin tur förklarar för kompilator och länkare att denna funktion kommer att anropas i enighet med programmeringsspråket PASCALs standard för långa anrop. ‘WINAPI’ lovas förbättra möjligheterna när man skriver DLL:er.

 

Typen HINSTANCE är ett s.k. handtag, d.v.s. en i Windows numrerad pekare, som håller reda på ett program när det är inläst i minnet. Vi har två argument av denna typ, en som ger oss adressen till var detta program vi just kör ligger i minnet, och en som talar om var det finns en annan kopia av samma program i minnet, när det vi kör startas (NULL om denna är den enda).

 

Detta öppnar möjlighet för programmet att hantera multippel start på olika sätt. Man kan hoppa till redan existerande kopia, vilket man ibland gör med program som kan ha flera dokument öppna samtidigt. Man kan också starta flera kopior av samma program, vilket är det vanligaste.

 

Även Windows använder kommandorad, även om vi inte ser den så ofta. Den ser ut precis som i Dos. I Dos main() kan man få in antalet argument, argc, och en pekare, argv, till en lista som innehåller en textsträng för varje ord skrivet på kommandoraden separerade med blanktecken. I Windows får man adressen till en sträng med hela kommandoraden i stället.

 

Sista argumentet är nShowCmd, vilket är ett heltal som anger hur programmets huvudfönster, programfönstret, ska visas. Dessa kan ha heltalsvärden definie­rade som SW_HIDE, SW_MINIMIZE, SW_RESTORE, SW_SHOW, etc.

Slå upp dessa i hjälpen och läs om dem!

 

En annan viktig sak är: var finns dessa heltalskonstanter definierade? Det är en sak med hjälpen, slår man upp en funktion som kräver att en viss fil finns med (#include <fil>) så står det också i hjälpen. Klicka på ‘QuickInfo’ i rubriken.

 

Därmed är vi klara att testa. Vi gör en övning där vi använder oss av en annan Windows-funktion, nämligen MessageBox(). Slå upp den också i hjälpen, och verifiera att nedanstående övning är korrekt skriven.

 

Observera att när vi skapar ett projekt för Windows i C, utan MFC, ska vi välja ‘Application’ i Type-rutan i dialogrutan ‘New Project Workspace’.

 

 

 

Övningsuppgift:

 

·      Skapa ett nytt windowsprojekt c:\CCpp\generic.

·      Skapa en källkod c:\CCpp\generic\generic.c och inkludera i projekt som vanligt. Källkoden ska se ut så här:

 

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance,

                   HINSTANCE hPrevInstance,

                   LPSTR lpCmdLine, int nShowCmd)

{

    MessageBox(NULL, "Hello Windows!",

               "Hello Message", MB_APPLMODAL);

    return 0;

}

·      När du kör programmet ska det se ut så här:

 

                                  

 

 

 

 

? WinMain(), MessageBox().


Programfönstret.

 

Det första vi ser när vi startat ett program brukar vara dess huvudfönster, pro­gramfönstret. I Windows är alla fönster skapade på samma sätt. De kan se olika ut och ha olika syften, men de är i grund och botten samma sak. Man kan få dem att se olika ut och göra olika saker, men de är fortfarande fönster.

 

Fönstret har i huvudsak två logiska delar:

 

1.   Det grafiska gränssnittet. Visar olika saker på skärmen.

2.   Fönsterproceduren. Tar emot meddelanden från Windows för vidare befodran till olika funktioner i programmet.

 

För att åstadkomma detta måste man göra tre saker:

 

1.   Registrera fönsterklassen, så att Windows vet hur det ska se ut, hur muspekaren ska se ut när den är över fönstert, titel etc, men även adressen till fönsterproceduren, så att Windows vet vart meddelanden ska skickas.

2.   Skapa fönstret, d.v.s. be Windows lägga upp sina dataareor enligt den beskrivning som erhölls vid registreringen ovan.

3.   Visa fönstret, d.v.s. rita upp det på skärmen. Detta får inte göras förrän alla variabler står rätt.

 

Vi börjar med att skapa fönsterproceduren. Den kan heta vad som helst, eftersom vi kommer att registrera dess namn när vi registrerar fönsterklassen, men i hjälpen kallas den ‘WindowProc’. För att den ska bli känd för WinMain() är det lämpligt att placera den först i filen, direkt efter #include <windows.h>. Vi slår upp den i hjälpen för att se hur den ska deklareras.

 

Du kan alltid kopiera ur hjälpen, det går snabbare än att skriva av, men kom ihåg att alla funktioner redovisas som prototyper, så du måste ta bort semi­kolonet efter högerparantesen. Du bör också redigera koden med avseende på det estetiska utseendet på skärmen.

 

Därefter deklarerar du koden. Här nedan finner du funktionen i sin helhet, vi ska strax slå upp de olika delarna i hjälpen för att se vad de gör. Jag ska också förklara hur jag tänkt att den ska fungera.

 

För att undvika problem med namnkollisioner bör du genast ändra funktionsnamnet. Eftersom detta är programmets huvudfönster kallar jag funktionen ‘GenericWindowProc()’:

 

LRESULT CALLBACK GenericWindowProc(

                    HWND hwnd,      // handle of window

                    UINT uMsg,      // message identifier

                    WPARAM  wParam, // first message parameter

                    LPARAM  lParam) // second message parameter

{

    LRESULT Result = 0L;

 

    switch(uMsg)

    {

        case WM_DESTROY:

            PostQuitMessage(0);

            break;

        default:

            Result = (DefWindowProc(hwnd, uMsg,

                                    wParam, lParam));

    }

    return Result;

}

 

Vi slår upp LRESULT i hjälpen (sätt markören i ordet och tryck F1!). Här står det att denna deklaration står för ett 32-bitars obetecknat heltal avsett att returneras från fönsterprocedur eller svarsprocedur. Naturligtvis kan det användas till vad som helst där en unsigned long int behövs, men namnet är avsett att göra programmet mer lättläst, och för dig att komma ihåg typen.

 

CALLBACK är definierat som FAR PASCAL. Meningen med att använda sådana här definitioner är att man genom villkorlig kompilering kan ändra förutsättningarna när man t.ex. byter operativsystem. Den villkorliga kompiler­ingen är förberett i headerfilerna och kan styras med diverse fördefinierade ord. Vi kommer inte att gå igenom det, men är du nyfiken så tittar du i headerfilerna, börja i windows.h. När vi skapar projekt får vi i vissa fall detta förberett åt oss.

 

Argumenten:

·     hwnd        -      informerar oss om var i minnet fönstret finns. An-
                        vändbart när vi t.ex. vill rita i fönstret, lägga till
                        menyer etc.

·     uMsg        -      Det numeriska meddelandet. Olika värden finns
                        fördefinierade. Vi kan slå upp dem i hjälpen. De
                        börjar alltid med WM_.

·     wParam     -      Meddelandeberoende tilläggsinformation till
                        meddelandet. Slå upp respektive meddelande i
                        hjälpen för information. Detta är ett word, 16
                        bits.

·     lParam      -      Samma som wParam, men ett longword, 32
                        bits.

 

Funktionens kodavsnitt: Vi börjar med att deklarera en variabel ‘Result’ för returvärdet. Om vi själva hanterar meddelandet (se nedan) ska vi returnera värdet 0. ‘L’ står för lång och instruerar kompilatorn att denna konstant ska ta upp fyra bytes. Med denna syntax slipper man långa variabler som bara är ifyllda i ena halvan.

 

Därefter kontrollerar vi vilket meddelande som kommit in. Vi börjar med ett enkelt program som endast går att stänga av. Därför hanterar vi endast med­delandet ‘WM_DESTROY’. Slå upp det i hjälpen!

 

När fönstret förstörs (destroy, du läste väl i hjälpen), är det meningen att vi ska terminera vårt program. Detta sker genom att man skickar meddelande till Windows för att tala om att programmet ska tas bort ur minnet. Om du har hjälpavsnittet för ‘WM_DESTROY’ på skärmen så har du en koppling till ‘PostQuitMessage’. Läs om den funktionen.

 

Om man konsulterar ‘QuickInfo’ i dessa avsnitt ser man att de kräver att vi tar med filen ‘winuser.h’. Detta behöver vi inte göra själva, eftersom satsen ‘#include <winuser.h>’ står med i filen windows.h

 

Åter till vår switch-sats. Ska man lägga in mer funktionalitet i programmet så gör man det här. Många meddelanden föranleder att man tittar på wParam och/eller lParam, vilket man kan hantera med ytterligare switchar.

 

Det finns en hel del som Windows kan sköta åt oss, t.ex. ändring av fönster­storlek, funktioner i systemmenyn etc. För detta finns en funktion som heter ‘DefWindowProc()’. Denna tar samma argument som vår funktion Generic­WindowProc() varför vi bara kan passera dem vidare. Returvärdet ska sparas för att returneras av GenericWindowProc().

 

Nu, äntligen, är vi redo att registrera fönsterklassen (steg 1 enligt ovan). Först tar vi bort anropet till MessageBox() i WinMain(), det behöver vi inte längre, men vill du spara på skrivarbetet sätter du kommentartecken före, så kan du kopiera den senare när vi lägger in något för att testa menyval.

 

Man registrerar en fönsterklass m.h.a. funktionen ‘RegisterClass()’ och vid det här laget slår du väl automatiskt upp den i hjälpen. Returvärdet (av typen ATOM) intresserar oss inte i detta lilla exempel, varför vi struntar i det. Argumentet är en struktur som är fördefinierad under namnet ‘WNDCLASS’.

 

Vad vi ska göra är alltså att deklarera en sådan variabel, fylla i dess medlemmar med lämpliga värden (slå upp WNDCLASS!), och till sist anropa funktionen. Detta gör vi dock inte om programmet redan finns i minnet från en tidigare programstart. Windows återanvänder klasser i sådana fall! WinMain() borde se ut så här nu:

 

int WINAPI WinMain(HINSTANCE hInstance,

                   HINSTANCE hPrevInstance,

                   LPSTR lpCmdLine, int nShowCmd)

{

    WNDCLASS WndClass;

    HWND hWnd;

 

    if(!hPrevInstance)

    {

        WndClass.lpszClassName = ”GENWINAPP”;

        WndClass.hInstance = hInstance;

        WndClass.lpfnWndProc = GenericWindowProc;

        WndClass.hCursor = LoadCursor(NULL, IDC_UPARROW);

        WndClass.hIcon = LoadIcon(hInstance, NULL);

        WndClass.lpszMenuName = NULL;

        WndClass.hbrBackground = GetStockObject(WHITE_BRUSH);

        WndClass.style = 0L;

        WndClass.cbClsExtra = 0;

        WndClass.cbWndExtra = 0;

 

        RegisterClass(&WndClass);

    }

//  MessageBox(NULL, "Hello Windows!", "Hello Message",

//             MB_APPLMODAL);

    return 0;

}

 

Parametrarna skickas över via strukturen, vi fyller i enligt nedanstående:

 

·     Vi registrerar klassen under namnet ‘GENWINAPP’, ett namn vi senare refer­erar till när vi skapar fönstret.

·     Klassen associeras med vårt program genom att vi anger vårt programs hInstance.

·     Vi registrerar vår fönsterprocedur, GenericWindowProc.

·     Vi skaffar oss en standard muspekare m.h.a. LoadCursor, och registrerar adressen.

·     Samma med ikonen. Observera att Developer Studio bakar in en standardikon i vår programfil, den får man om man anger NULL. Man kan ange även andra inlästa programs ikoner.

·     Meny har vi ingen ännu, ange NULL.

·     Bakgrunden fyller vi i m.h.a. GetStockObject(), som hämtar en standardfärg. Microsoft förordar starkt att vi använder en vit arbetsyta (...paper is white eh...).

·     Style använder vi inte. Eftersom alla bitar sätts m.h.a. ‘|’, (se hjälpen) nollställer vi alla bitar genom att ange 0L.

·     Extraminne använder vi inte.

Till sist anropar vi funktionen och skickar med adressen till strukturen som argument. Vi sparar handtaget till fönstret i en variabel av typen HWND. Vi deklarerar den i början av WinMain(). Som också synes har jag kommenterat bort MessageBox().

 

Nu ska vi skapa fönstret. Kan funktionen heta ‘CreateWindow()’? Viiist! Hjälpen upplyser oss om att man inte använder en struktur denna gång. CreateWindow() vi ha ett större antal argument, och returnerar ett handtag till fönstret. Detta handtag måste vi ta hand om. Alltså deklarerar vi ett HWND som vi kallar, fantasifullt nog, hWnd.

 

Hjälpen förklarar ganska utförligt, så vi knappar friskt in följande:

 

hWnd = CreateWindow("GENWINAPP",

                    "Generic Windows Application",

                    WS_OVERLAPPEDWINDOW,

                    CW_USEDEFAULT,

                    CW_USEDEFAULT,

                    CW_USEDEFAULT,

                    CW_USEDEFAULT,

                    NULL,

                    NULL,

                    hInstance,

                    NULL);

 

Vilket gjorde susen. Läs nu noga om alla argumenten i hjälpen, så slipper jag förklara dem här (och visa mina egna kunskapsbrister). Du behöver ändå kunna hitta i hjälpen senare, läsa, förstå och tillämpa på egen hand.

 

Ok, nu är målet nära. Vi kan visa fönstret m.h.a. funktionen ShowWindow(). Denna funktion vill veta två saker, vilket fönster och hur ska det visas. Vilket fönster har vi lagrat i hWnd. Kommandot fick vi från Windows, så det är väl bäst att använda det. Windows har ju möjlighet att t.ex. alltid starta ett visst program med minimerat programfönster etc. Om vi anger något annat här tar vi ju bort den möjligheten.

 

Hur skulle jag nu kunna veta detta om det inte stod i kurslitteraturen? Ja det är inte helt lätt att hitta i hjälpen. I detta fallet får man fullträff genom att gissa. Vi vill ju visa ett fönster, och visa fönster heter ju show window på engelska. Om man söker på ‘showwindow’ får man förslag på två avsnitt, ett med MFC, ett med SDK. Eftersom vi sysslar med SDK slår vi upp det avsnittet, et voila, som fransosen säger när han... ja, vad nu fransoser gör.

 

Skulle vi testa programmet nu, så skulle vi ha ett program som öppnar ett fönster och sedan avslutar. Vi har nämligen inte skrivit någon s.k. meddelande­loop än. Den funkar ungefär så här, om jag får lov att uttrycka mig i pseudokod:

 

Så länge vi får in meddelanden:

                 Hämta meddelande till meddelandearean.

                 Översätt meddelandet.

                 Skicka det till vederbörande instans.

 

Det ser ut så här i C, under SDK:

 

while(GetMessage(&msg, 0, 0, 0))

{

    TranslateMessage(&msg);

    DispatchMessage(&msg);

}

 

Vilket till att börja med föranleder att vi deklarerar en meddelandearea. Den kan heta t.ex. ‘msg’ och ska vara av typen MSG. Skriv in den direkt under ‘HWND hWnd’. Slå upp den i hjälpen, känns strukturen för meddelandet igen?

 

Vi slår upp GetMessage(), och finner att den returnerar TRUE i alla fall utom ett: när meddelandet är WM_QUIT. Det var ju det vi skickade själva m.h.a. PostQuitMessage()!

 

Programmet kommer att ‘vila’ på denna instruktion. Den fungerar så att den överlämnar kontrollen till Windows medan vårt program pausar.

 

Läs sedan om TranslateMessage(), en funktion som översätter tangentbordshän­delser till tecken. Windows genererar bara tangent ned och tangent upp. Här skapas också information om vilket tecken som det resulterar i. Följ gärna de händelser som anges i hjälpavsnittet.

 

DispatchMessage() ber Windows skicka meddelandet till den fönsterprocedur som ska ha den, i vårt fall GenericWindowProc().

 

 

Övningsuppgift:

 

·      Kompilera!

·      Rätta alla stavfel.

·      Kompilera, länka och testa!

 

 

 

 

 

Har du gjort likadant som jag, så borde programmet se ut så här på din bild­skärm också:

 

 

          

 

 

Observera att du har alla de vanliga systemfunktionerna tillgängliga. Prova dem! Muspekaren blir en ‘up-arrow’ när vi placerar den i clientarean, vilket vi ju också beställde när vi registrerade fönsterklassen.

 

 

 

 

 

 

 

? WindowProc(), LRESULT, WM_DESTROY, PostQuitMessage(), WM_QUIT, DefWindowProc(), RegisterClass(), WNDCLASS, LoadCursor(), LoadIcon(), GetStockObject(), CreateWindow(), HWND, ShowWindow(), MSG, GetMessage(), TranslateMessage(), WM_KEYDOWN, WM_KEYUP, WM_CHAR, WM_DEADCHAR, DispatchMessage().
Muspekaren.

                       [Insert - Resource...] Ctrl + R, Ctrl + 3

 

Muspekaren är en resurs bestående av en variant på bitmap. Vi kan inte skapa hela resursen m.h.a. paint, eftersom paint inte kan hantera denna speciella variant, vilken innehåller även information om s.k. ‘hotspot’, den aktiva punkten. Den talar om vilken av bitmappens pixels som ska anses vara muspekarens position, vilken varierar från muspekare till muspekare.

 

Därför använder vi ett speciellt verktyg i Developer Studio, resurseditorn. Denna kan skapa ett flertal olika resurser, och vi börjar med muspekaren.

 

Du kan antingen välja [Isert - Resource], eller trycka på Ctrl + R, så får du upp en dialogruta där man kan välja vilken typ av resurs man vill skapa, och vi väljer naturligtvis ‘Cursor’:

 

 

 

 

                    

 

 

 

 

Lägg märke till att vissa resurstyper har ett ‘+’ framför sig. Detta kan man klicka på för att se ett antal standardresurser att välja emellan, om man vill göra en variant på en sådan. Vi börjar dock från scratch.

 

Allt detta kunde vi gjort genom att klicka på , eller trycka Ctrl + 3 i stället.

När vi klickar på ‘OK’ drar resurseditorn igång i läge för redigering av muspekare:

 

            

 

Lägg märke till följande saker:

 

·     Storleken går inte att ändra (alla ‘resizing-block’ är ofyllda).

·     Det finns en knapp för hotspot (prova den).

·     Du har en förstorad bild och en bild i verklig storlek (det går att ändra med kanten mellan bilderna, s.k. ‘split screen’).

·     Det finns två verktygsfält till höger, det nedersta syns dåligt eller inte alls beroende av hur stort du gör det nedersta fönstret. Du kan flytta det till en bättre position.

 

Flytta det nedre verktygs­fältet, vilket innehåller en palett, så att du kan arbeta bekvämt.

 

Rita någon snygg muspekare, och sätt ‘hotspot’ på dess spets, t.ex. så här:

 

                                  

Öppna egenskapsrutan m.h.a. menyvalet [Edit - Properties] eller snabbtangen­ten Alt + Enter. Fyll i ID med ‘IDC_GENERICCURSOR’, vilket blir det resurs­ID vi kommer att referera till när vi använder muspekaren.

 

 

                

 

 

Du kan nu stänga resursen. I redigeringsfönstret dyker det nu upp ett litet hierarkiträd:

 

                                  

 

 

Här visas vårt nya ‘resource script’, vilket temporärt döpts till ‘Scipt1’. Vi har dock inte sparat det än, varken skriptet eller bitmappen. Därför väljer vi [File - Save As...]. Kontrollera att rätt katalog är förvald. Kalla skriptet ‘generic.rc’. Du behöver inte själv skriva filnamnstillägget, om du inte gör det så läggs det till automatiskt.

 

Nu har följande filer skapats:

 

·     genericc.cur          -      Bitmappen.

·     resource.h            -      En fil med numeriska definitioner,
                                    gemensam för alla resurser i projektet.

·     generic.rc             -      Resursbeskrivningsfilen.

 

Den sistnämnda är egentligen den som representeras av trädet som visas ovan. Nu är det faktiskt en textfil, och vi kan titta på den m.h.a. programmet anteck­ningar. Där finner vi bl.a. en rad som kopplar samman det logiska ID:t IDC_GENERICCURSOR med filen genericc.cur.

 

I filen resource.h finner vi bl.a. en definition av den numeriska betydelsen av samma ID.

Vi har fortfarande inte talat om att denna resurs hör till vårt projekt. Just det, ‘Insert Files into Project’ igen. Välj generic.rc och spara. Som vanligt hade vi kunnat välja att stänga filen i stället, och blivit föreslagna att spara. Det är nu ingen egentlig mening att ha generic.rc öppet längre, vilket vi strax ska se, så stäng det nu.

 

Om du tittar i InfoView-fönstret ser du att vi fått en ny flik, vilken avser resurser. Vi kan välja den fliken, och om vi har stängt generic.rc går det att expandera trädet vi ser:

 

 

 

 

                                  

 

 

 

Nu ska vi bara ändra i programmet, så att den nya resursen kommer med. Börja med att lägga till filen resource.h, skriv in includesatsen direkt efter den första, den som tar med windows.h. Observera att resource.h ligger i projektets katalog, så du ska använda dubbla citattecken, och inte <>-tecken:

 

#include ”resource.h”

 

Vidare måste vi ändra den rad där vi bestämde vilken markör som användes. Det var när vi fyllde i WndClass. Leta upp raden och ändra den till:

 

    WndClass.hCursor = LoadCursor(hInstance,

                           MAKEINTRESOURCE(IDC_GENERICCURSOR));

 

MAKEINTRESOURCE är ett macro som översätter numeriska ID till resursID. Slå upp det i hjälpen. Jag vet inte varför vi måste använda det här, vi kommer att finna när vi skapar en ikon i nästa avsnitt, att man då kan ange resursnamnet som textsträng i stället.

Nu kan vi i alla fall testa programmet. Kör Build och starta, och världens häftigase muspekare dyker upp när du placerar den i generics klientarea:

 

 

       

 

 

Prova att flytta muspekaren till kanten av fönstret, så att de pixels som du målat med vitt kommer över kanten. Kantens gråa färg skyms av muspekarens vita. Men om du placerar muspekaren så att den yta som hade bakgrundsfärg (mossgrön i resurseditorn) så ser du att kantens gråa färg syns igenom.

 

Systemets vanliga muspekare är inte genomskinlig. Men den genomskinliga färgen har uppenbart använts runt omkring den.

 

 

Övningsuppgift:

 

·      Enligt ovanstående text.

 

 

 

 

? LoadCursor(), MAKEINTRESOURCE().


Ikonen.

                       [Insert - Resource...] Ctrl + R, Ctrl + 4

 

Man skapar en programikon på samma sätt. Man arbetar med samma resurs­skript, det är bara att lägga till flera resurser. När man redigerar en ikon kan man inte heller ändra storleken, men det finns fler färger tillgängliga på palet­ten.

 

Skapa en ny ikon och kalla den IDI_GENERICICON. Den kan se ut t.ex. så här:

 

                                  

 

Lägg märke till att InfoView-fönstret innehåller den nya resursen. Observera även att man kan dubbelklicka på informationen i InfoView-fönstret, så öppnas det för redigering. Höger mustangent leder till snabbmenyer. Testa!

 

Nu är det dags att göra motsvarande ändringar i generic.c igen. #include-satsen avser hela resursskriptet, och är gemensam för alla resurser i skriptet. Den behöver du inte göra något mer åt. Ändra bara motsvarande rad där du anger ikon i WndClass:

 

WndClass.hIcon = LoadIcon(hInstance, ”IDI_GENERICICON”);

 

När du sedan kör Build kanske du lägger märke till att resurserna kompileras för sig. Detta sker m.h.a. en speciell resurskompilator. Därför skiljer sig språket i resursskriptet från C, det liknar faktiskt mer PASCAL, om något.

 

Övningsuppgift:

 

·      Utför ovanstående steg.

·      Installera programmet på skrivbordet (kopiera eller skapa genväg).

·      Fint va?

 

? LoadIcon().


Menyn.

                       [Insert - Resource...] Ctrl + R, Ctrl + 2

 

Här gör vi på samma sätt. Redigering av menyer ser litet olika ut, men det övergripande arbetet är samma. När du ser editorn så finns det en tom menyplats markerad. Dubbelklicka i den, så dyker egenskapsrutan upp. Om man arbetar med många menyval är det lämpligt att nåla fast den. Fyll i Caption: Meny. När vi trycker ‘Enter’ ser vi att menytexten fylldes i där den markerade rutan stod. Samtidigt fick vi en ny ruta för menyval. Denna döper vi till: MenyVal. Lägg också till menyvalet Avsluta. Observera ID som skapas!

 

Nu är vi klara, och stänger dokumentet. Observera vår nya resurs i InfoView-fönstret. Dock vill vi inte att den ska heta MENU1. Klicka med höger mustan­gent på namnet. Välj ‘Properties’ i menyn som dyker upp, och ändra ID till IDR_GENERICMENU.

 

Nu är det bara att göra motsvarande ändring i generic.c. Använd denna gång MAKEINTRESOURCE().

 

WndClass.lpszMenuName = MAKEINTRESOURCE(IDR_GENERICMENU);

 

Så här ser vår nya meny ut:

 

         

 

Observera att menyn sköts av Windows. När vi för muspekaren över menyerna förvandlas den till systemets vanliga (tråkiga) muspekare igen.

 

Koppla menykommando.

 

Men det händer ju ingenting när vi klickar på menyvalen! Nej vi måste naturligtvis ha kod som reagerar på våra kommandon, vi måste koppla kommandona till funktioner.

 

När vi klickar på menyvalet skickar Windows ett meddelande WM_COM­MAND. wParam innehåller då information om vilket kommando som skickats. Vi kan göra följande tillägg i vår GenericWindowProc:

 

    case WM_COMMAND:

        switch(wParam)

        {

            case ID_MENY_MENYVAL:

                OnMenyMenyval();

                break;

            case ID_MENY_AVSLUTA:

                PostQuitMessage(0);

                break;

        }

 

Observera att jag väver in en switch i den befintliga. Naturligtvis bör man i större program bryta ut den till en speciell funktion för kommandohantering, men detta är ju ett minimalt exempel.

 

Det som händer när vi klickar på Avsluta ska vara att programmet avslutas. Det vet vi redan hur man gör, anropa PostQuitMessage(). Eftersom det är ett norm­alt avslut anger vi argumentet 0. Jag har ingenstans sett hur Windows hanterar andra koder, men ingen vet ju allt. I Dos kan man ju t.ex. använda returvärdet i en batch m.h.a. errorlevel, men här?

 

Om vi klickar på Menyval anropar vi en funktion som helst bör heta samma som meddelandet, föregått av ‘On’. Versaler och gemener har andra regler för funktioner, och vi tar bort alla ‘underscore’. Resultatet blir ‘OnMenyMenyval’. Den funktionen återstår att deklarera. Den ska vara känd när den anropas, så vill du inte skriva en prototyp är det bästa att deklarera funktionen före Generic­WindowProc(). Vi kan ju testa med MessageBoxen vi hade från början:

 

void OnMenyMenyval()

{

    MessageBox(NULL, "Hello Windows!", "Hello Message",

               MB_APPLMODAL);

}

 

Nu är det bara att köra Build igen.

 

 

Övningsuppgift:

 

·      Utför ovanstående.

·      Testa.

 

Så här ska det se ut när man valt ‘MenyVal’:

 

 

 

 

             

 

 

 

 

Om man väljer avsluta, så avslutas programmet. Riktigt tjusigt.

 

 

 

 

 

 

 

 

 

 

 

? WM_COMMAND, Tryck på knappen ‘Sync Contents’ när du läst färdigt, så kan du hitta avsnittet bland böckerna. Där kan du läsa om andra meddelanden.


Tangentbord.

 

 

På samma sätt kan vi hantera tangenttryckningar. Meddelandet heter WM_CHAR. Vi lägger till den i samma switch som ovan, och skriver en funktion för den:

 

void OnChar(HWND hwnd, WPARAM wParam)

{

    static int x = 10, y = 10;

    char cText[2] = " ";

    HDC hDC = GetDC(hwnd);

    cText[0] = (char)wParam;

    TextOut(hDC, x, y, cText, 1);

    x = x + 8;

    if(x > 200)

    {

        x = 10;

        y = y + 16;

    }

}

 

Observera att vi behöver hwnd och wParam. Skicka dessa som argument till funktionen, vilken förresten bör heta ‘OnChar’.

 

wParam innehåller nämligen det tecken som skrivits, och hwnd behöver vi för att hitta klientarean. Den sköts via ett s.k. Device Context, en sorts drivrutin i Windows som hanterar apparatur åt oss. Vi behöver inte veta vilken typ av bildskär detta är, ej heller om det ens är en bildskärm, det kan lika gärna vara en skrivare.

 

Vi deklarerar ett handtag till detta Device Context, och frågar efter vad vi ska fylla i detta handtag genom att anropa funktionen GetDC(). Denna funktion returnerar handtaget till klientarean i ett angivet fönster, och behöver därför handtaget till vårt fönster som argument.

 

Enligt hjälpen måste man sedan återlämna kontrollen över Device Context till Windows, men att man inte behöver göra det för privata DC:s. Vi har ju deklarerat ett HDC i vår funktion, och det är inte statiskt, alltså kommer det att upphöra automatiskt när funktionen går ur sitt sammanhang, scope (tar slut, på ren svenska!).

 

Detta hDC kan man använda till att skriva, rita och klistra bilder. Vi skriver m.h.a. funktionen TextOut(). Två statiska variabler, x och y, får hålla reda på var vi ska skriva nästa bokstav.

 

Övningsuppgift:

 

·      Testa nu ovanstående!

 

 

 

 

Så här blir det när vi skriver på tangentbordet:

 

 

 

 

           

 

 

 

 

 

Njaa... Nog kunde man hantera koordinaterna bättre. Detta är en proportionerlig font, och då måste man fråga efter bokstävernas bredd. Det är ingen större mening med att dyka på djupet med detta, eftersom vi senare kommer att använda textkontroller, vilka är små fönster som sköter allt detta åt oss.

 

 

 

 

 

 

? WM_CHAR, GetDC(),TextOut().


Mus.

 

Det sista vi tar och tittar på i detta exempel är hur vi fångar upp mushändelser. Vi inskränker det till ett meddelande, WM_LBUTTONDOWN. Som du ser är namnsättningen ganska logiskt, vilket underlättar arbetet med att hitta saker och ting i hjälpen.

 

Detta blir, precis som allt annat i detta överförenklade exempel, synnerligen rudimentärt. Det finns många möjligheter i Windows, och vi har inte ens skrap­at på ytan. Anledningen är att det finns så bra möjligheter under MFC, där allt detta bakats in i klasser, och vi kommer att fördjupa oss litet mer i det sista häftet i stället.

 

Detta exempel är bara till för att belysa hur ett Windowsprogram är uppbyggt. När vi så småningom ber Developer Studio generera ett enklast möjliga program­skelett åt oss kommer bara det att bli c:a 10 ggr mer källkod, och inte ens då finns allt i källkoden. Många saker ligger färdigkompilerade som objektkod, t.ex. WinMain, och vi ser inte ens halva programmet.

 

Vi kommer alltså inte att hinna lära oss särskilt många av Windows c:a 2000 funktioner, men huvudsaken är att vi förstår principen, och att vi kan leta oss fram till en lämplig metod genom att söka i hjälpen.

 

Nu ska vi bara skriva en funktion som ritar ett streck från den position mus­pekaren stod på förra gången vi klickade, till den position muspekaren finns på vid aktuellt klick. Vi behöver därför en startpunkt för det första klicket, och det sätter vi till 0,0.

 

Så här skrev jag funktionen:

 

void OnLButtonDown(HWND hwnd, LPARAM lParam)

{

    static int franx = 0, frany = 0;

    POINTS till = MAKEPOINTS(lParam);

    HDC hDC = GetDC(hwnd);

 

    MoveToEx(hDC, franx, frany, NULL);

    LineTo(hDC, (int)till.x, (int)till.y);

    franx = (int)till.x;

    frany = (int)till.y;

}

 

Observera att aktuella koordinater vid musklick finns i lParam. Det finns ett färdigt makro MAKEPOINTS som delar upp lParam och ger oss koordinaterna i en POINTS-struktur. Dessa är unsigned int, och måste typomvandlas. Föregående klicks koordinater är deklarerade statiskt.

Så här kan det bli om man klickar på fem ställen på skärmen:

 

 

 

 

 

 

            

 

 

 

 

 

Övningsuppgift:

 

·      Prova själv!

 

Övningsuppgift:

 

·      Försök göra om hela uppgiften en gång till, utan att titta i häftet.

·      När du får problem, söker du i hjälpen.

·      Det viktigaste du har att lära är hur du hittar i hjälpen!

 

 

 

 

 

 

? WM_LBUTTONDOWN, POINTS, MAKEPOINTS(), MoveToEx(), LineTo().