4.
Kalkylatorn
3. Dialogen.
5. Siffrorna.
6. Beräkningen.
I detta kapitel ska vi göra ett riktigt program. Vi ska göra en kalkylator. I Exemplet 'Kalkylatorn' kommer vi i kontakt med följande nya moment:
· Att arbeta med dialogbaserat program.
· Att ändra font för en dialogresurs.
· Att göra en textruta som det inte går att skriva in text i, och ändra även andra egenskaper.
· Att ange vilka knappar som ska synas i programfönstrets titelrad.
· Att avsluta ett program på 'kommando'.
· Att skriva i fönstrets titelrad.
Så här är det tänkt att den färdiga kalkylatorn ska se ut:
Detta är den enklaste formen av kalkylator, men vi kommer att finna att det är nog så klurigt att få till alla funktioner. Jag kommer att diskutera dess teori efterhand, men du får själv realisera koden.
Om vi nu jämför bilden och punktlistan ovan finner vi att alla punkter på något sätt ger sig till känna i bilden. Ett dialogbaserat program ser ut som ett mellanting mellan ett programfönster och en dialogruta. Titelraden hör till ett programfönster, om än något modifierad. Fönstret kan dock inte ändra storlek, vilket är normalt för en dialogruta. Uppenbart är även att knappar normalt hör hemma i en dialogruta. Alla dessa skillnader kan naturligtvis ha andra orsaker, så de är inga absoluta bevis på att programmet är av typen 'dialogbaserat', och det kan även göras på annat sätt.
Andra punkten säger att vi ska ändra font, och det syns ju uppenbarligen att jag inte bara valt en annan font, utan även ändrat storlek på den text som syns på kontrollerna.
När man gör en passiv kontroll, det vill säga en kontroll som användaren inte kan påverka, blir den i regel försedd med grå text i stället för svart. Detta gäller dock inte en textruta, vilken inte alltid har en text. Den får i stället grå bakgrund. Jag har dessutom tagit bort ramen, så det enda som syns är siffrorna. Textkontrollen är dessutom knuten till en variabel av typen 'float', vilket ger oss speciell formatering i sifferfönstret. Talformatet visar 5 siffror eftersom det är den precision man får med variabler av typen 'float', och skulle man mata in mycket stora eller mycket små tal kommer utskriften att ske i exponentiell form. Egentligen har float en precision av 5 - 6 siffror, men vi är bara intresserade av vilken precision textrutan kommer att visa oss.
Fjärde punkten talar om de knappar som finns i programfönstret. När man skapar ett program som är dialogbaserat så får det inga knappar för minimering och maximering. Man kan lägga till dessa, och jag har valt att man endast ska kunna minimera programfönstret. Maximeringsknappen har som synes följt med 'på köpet', men den är passiv.
Kalkylatorn har en 'Off'-knapp, och i hanteringsfunktionen för den stängs programmet, så att man inte behöver klicka på den lilla kryssknappen uppe i högra hörnet.
Till sist kan man se att det står '-M-' i gult i titelraden. Vi kommer att testa att använda de funktioner vi känner igen från Windowsprogrammering utan MFC, för att se att de i många falla går att använda där metoder för det man vill göra saknas i MFC.
Vi ska nu se hur man använder AppWizard till att skapa ett dialogbaserat projekt. Vi börjar som vanligt, men redan i första sidan gör vi första ändringen. I stället för att välja Single document som vi gjort tidigare väljer vi nu Dialog based:
Lägg märke till att exemplet på vänster sida ändras till något som mer liknar en dialogruta än ett programfönster, och det är just det som är meningen. Programmet ska baseras på en dialogruta i stället för ett komplett programfönster. Skillnaderna är stora:
· Ett dialogbaserat program har normalt ingen menyrad. Den har dock en systemmeny som vanligt, och vi kommer att se i ett senare steg att menyvalet 'About...' hamnar här i stället för under den i detta exempel obefintliga 'Help'-menyn.
· Projektet innehåller inget dokument, så 'File'-menyn och 'Edit'-menyn behövs heller inte. Spara på disk och sådant finns inte alls med i projektet. Skulle man vilja spara något måste man själv lägga till den kod som behövs, vilket faktiskt inte blir så mycket, eftersom man använder klasser i MFC för att göra det mesta jobbet.
· Utan dokument behövs ingen vy, eftersom den ju bara är till för att visa dokumentet. Det är därför vi kan använda en dialog i stället.
När vi går till nästa sida ser vi dessutom att AppWizard inte tänker ta oss igenom sex steg som vanligt, utan bara fyra:
Om man har 'About box' förbockad ser man på exemplet till vänster att menyvalet 'About app...' ligger under systemmenyn.
Man kan välja till sammanhangskänslig hjälp, men det tar vi inte upp på den här kursen. Vill du testa det så rekommenderar jag övningsexemplet 'Scribble' som finns i hjälpen.
3D-controls gör snyggare kontroller. Testa själv och se hur exemplet visar skillnaden. OLE tar vi inte heller upp här, och samma sak gäller Windows sockets.
Normalt sköter dokumentet om programfönstrets titelrad. Där är vi vana att se programmets namn, ett bindestreck och aktuellt dokuments namn, vilket kan vara 'Namnlös' eller 'Untitled' när man just skapat ett nytt dokument och ännu inte sparat det under ett nytt namn. Här har vi inget dokument, så vi har chansen att ändra namnet i titelraden om vi vill, men även det är förifyllt med det enda vettiga, nämligen programmets namn. I detta exempel ändrar vi alltså ingenting på denna sida. Den tredje sidan känner vi igen, och ändrar som vanligt till att MFC ska länkas statiskt:
Den fjärde och sista sidan är som vanlig en summering, och den ser naturligtvis litet magrare ut än vanligt, utan dokument och vy:
Övningsuppgift 4.2.1:
· Skapa projektet Kalkylatorn enligt ovanstående.
· Kompilera, länka och testa.
· Så här ska det se ut:
· Och under systemmenyn finns bland annat 'About Kalkylatorn...':
· Du kan lämna projektet öppet, vi ska fortsätta i nästa avsnitt.
Vi såg under programkörningen att AppWizard redan skapat en dialogruta att visa upp, och det är naturligtvis meningen att vi ska redigera den. Vi såg även att den redan var försedd med två standardknappar, 'OK' och 'Cancel'. Vi hade ju även begärt kommentarer, vilka vägleder oss när vi ska skriva in egen kod, och här har man varit lustiga nog att lägga in ett rubrikobjekt, en 'static'-kontroll, som upplyser oss om att vi ska lägga till våra egna kontroller i denna dialog.
Om vi tittar under Project Workspace's ResourceView-flik finner vi dialogrutan lätt nog:
Vi öppnar dialogen för redigering, och börjar med att städa bort det vi inte vill ha, i detta fall alla tre kontrollerna. Knapparna OK och Cancel duger alldeles utmärkt till att avsluta programmet med, men jag lovade att visa hur man kan stoppa ett program från vilken punkt som helst, och därför tar vi bort dessa tillsammans med rubrikobjektet, så att vi har en helt tom dialog att arbeta med.
Innan vi skapar kontrollerna ska vi ändra dialogrutans font. När man gör detta anpassas såväl kontrollerna som hela dialogrutan i proportion till hur mycket texten ändrar storlek. Väljer man då en mycket stor font, kommer man att få en mycket stor dialogruta. För att undvika detta är det bra att ställa in font och fontstorlek så tidigt som möjligt, med högst ett par kontroller i dialogrutan, och dessa ska vara så små som möjligt. Även dialogrutan ska vara så liten som möjligt. Vi kan skapa en knapp som ska ha längsta tänkbara text, vilket antagligen blir 'Off'-knappen, göra den så liten att texten lagom får plats, minska dialogrutans storlek, och sedan ändra fonten:
Dubbelklickar man sedan på dialogrutan (inte på någon kontroll) så får man fram egenskaperna för själva dialogrutan:
Här kan vi klicka på 'Font'-knappen, så får vi möjlighet att välja såväl font som storlek efter behag:
När vi klickat på 'OK' har dialogen och knappen blivit större i proportion till texten. Om Egenskapsrutan ligger i vägen kan du flytta den eller stänga den också, så att du kan beundra resultatet:
Lägg märke till att de prickar som används som rutnät för att hjälpa oss skapa kontroller som är lika stora och placera dessa i snygga rader har hamnat glesare i dialogrutan. De har uppenbarligen anpassats till textens storlek i punkter. Vi behöver ett mer finmaskigt nät, så vi väljer [Layout - Guide settings...]:
Den första gruppen avser bara vilka guider vi använder, och de kan vi aktivera i verktygsfältet 'Dialog'. Intressant här är däremot gruppen 'Grid spacing', vilken normalt är inställd på 5 * 5. Vi ändrar detta till 2 * 2 i stället, och klickar 'OK'.
Vi ska nu skapa en textruta som ska fungera som sifferfönster åt kalkylatorn. Hur stor behöver den vara då? Det är ju viktigt för kalkylatorns design. Vi får experimentera. Till att börja med finns möjlighet att koppla textrutan till en variabel av typen 'float' i stället för 'CText' som vi gjort tidigare. Därmed blir fönstrets innehåll automatiskt formaterat. Datatypen float kan innehålla värden vilka visas med fem siffrors precision, och om de är mycket stora eller mycket små presenteras de i exponentform. Om talet är negativt föregås det av ett minustecken. Ett värde som tar upp största möjliga plats bör därför se ut ungefär så här: -8.8888e+008. Skapa textrutan och prova att skriva detta tal. Anpassa storleken och prova igen tills texten får lagom plats.
Textrutan slösar litet med utrymmet, så jag valde att göra den lika bred som själva dialogrutan. För att detta inte skulle se fult ut tog jag bort ramen. Detta kan göras i egenskaperna. Passa på att göra fönstret passivt, eftersom man inte ska skriva något direkt i det:
Egenskapen 'Number' bryr vi oss inte om, eftersom den är till för inmatningskontroll endast. Placera textrutan högst upp till vänster i dialogen, och anpassa dialogens bredd så att den blir så smal som tillåts av textrutan. Observera att det inte är lämpligt att ha marginalerna på nu, eftersom det kräver att dialogrutan är bredare än textrutan. Marginalerna kan du stänga av i verktygsfältet 'Dialog'.
Det enda som återstår nu är att skapa alla knapparna, att ange identitet för alla kontroller samt att koppla textrutan till en medlemsvariabel.
Det sistnämnda gör vi genom att dubbelklicka på textkontrollen med Ctrl-tangenten nedtryckt. ClassWizard hjälper oss då att lägga till variabeln, och att lägga till den kod som behövs för att UpdateData() ska kunna användas till att visa medlemsvariabelns värde i textrutan. Vi kalla medlemsvariabeln m_X av en anledning som vi ska diskutera när vi arbetar med koden till kalkylatorn. Vi måste även välja rätt datatyp. Det är viktigt att vi i vårt fall väljer 'float'. Skulle vi välja 'double' skulle textrutan inte räcka till för alla siffror. Så här ska det se ut:
Övningsuppgift 4.3.1:
· Om du stängde projektet kan du öppna det igen, och öppna dialogen för redigering.
· Ändra font och storlek. Du kan naturligtvis använda en annan font än den som visas i exemplet.
· Skapa textrutan, anpassa den så att talet -8.8888e+008 får plats.
· Ändra egenskaperna för textrutan enligt ovan.
· Skapa en medlemsvariabel m_X av typen float till textrutan.
· Skapa alla knappar, och göra en snygg design. Så här kan det se ut:
· Ange identitet för alla knapparna. Textrutan kan behålla identiteten IDC_EDIT1. Här är en lista på de ID som används i programexemplet:
IDC_CE
IDC_C
IDC_OFF
IDC_MPLUS
IDC_MMINUS
IDC_MR
IDC_DELAMED
IDC_GANGER
IDC_MINUS
IDC_PLUS
IDC_LIKAMED
IDC_0
IDC_1 ...och så vidare
till...
IDC_9
IDC_PUNKT
· Kompilera, länka och testa. Ingenting fungerar än, men vi har ju inte skrivit något, så det är ju inte så konstigt.
· Du kan lämna projektet öppet, vi ska fortsätta i nästa avsnitt.
Ett Windowsprogram fungerar som vi redan vet genom att reagera på händelser. I det här programmet kommer händelserna att nästan uteslutande bestå av knapptryckningar. Undantaget är menyvalen i systemmenyn, men dessa kommer vi inte alls att ändra. Så här skulle ett JSP kunna beskriva programmet:
Vi ska alltså koppla hanterare till alla de knappar vi lagt till dialogrutan. Eftersom vi redan gjort detta i ett tidigare kapitel överlåter jag åt dig att göra det på egen hand.
Övningsuppgift 4.4.1:
· Om du stängde projektet efter föregående avsnitt kan du öppna det igen.
· Använd Wizard Bar eller Class Wizards huvuddialog till att koppla hanterare till alla knappar i dialogrutan. Hanterarna ska naturligtvis ligga i klassen CKalkylatornDlg, i filen kalkylatorndlg.cpp.
· Skriv ingen kod i hanterarna än.
· Du kan lämna projektet öppet, vi ska strax fortsätta.
Om man betraktar en enkel kalkylator finner man att den faktiskt fungerar som ett Windowsprogram. Den gör ingenting utom just när man trycker på knapparna. Vi ska alltså begrunda vad som händer när man trycker på en knapp på kalkylatorn, och skriva in programkod som gör samma sak. Frågan är bara: Hur fungerar en enkel kalkylator egentligen?
Till att börja med så befinner den sig i ett visst tillstånd när man startar den. Sifferfönstret innehåller värdet noll. Vi ska även ha noll i minnet, eftersom kalkylatorn faktiskt kommer att ha en minnesfunktion. Mindre uppenbart är att det faktiskt finns ytterligare ett minnesregister i en vanlig kalkylator. Detta register behövs för att man ska kunna utföra en operation på två operander. Om man till exempel vill beräkna summan av talen 2 och 3 måste dessa lagras i var sitt register. Det ena är faktiskt själva sifferfönstret, och till det har vi kopplat en variabel som vi kallat m_X. Man brukar nämligen säga att det som syns i sifferfönstret 'ligger i' x-registret. Uppenbarligen borde den andra siffran ligga i något som kan kallas y-registret, och därför behöver vi en variabel m_Y.
När överförs då informationen till m_Y? Tänk efter: vi vill summera två och tre. Då trycker vi först på 2:an, sedan på plustangenten, därefter 3:an och till sist på likhetstecknet. Uppenbarligen måste talet 2 överförts från m_X till m_Y när vi tryckte på plustangenten. I en vanlig kalkylator försvinner dock inte den första siffran när man trycker på tecknet, utan först när man börjar mata in nästa siffra. Alltså måste man veta huruvida m_X ska tömmas när man matar in en siffra, eller om det som står i fönstret ska kombineras ihop med det nya talet.
Om vi trycker på 6:an och sedan 7:an ska det ju stå "67" i sifferfönstret, och även i m_X. För att veta detta måste vi ha en variabel som håller reda på om vi ska initiera m_X till den siffra som motsvaras av nedtryckt tangent eller om vi ska kombinera ihop det nya värdet med det gamla. Låt oss kalla den m_InitX. Denna variabel kan vara av datatypen BOOL.
När vi sedan trycker på likhetstecknet måste någon variabel komma ihåg vilket tecken som tryckts tidigare, så vi deklarerar en heltalsvariabel m_Tecken.
När vi trycker in flera siffror i rad är det bara att multiplicera m_X med 10 och sedan lägga till det nya talet. Om man till exempel först tryckt på 6:an och sedan trycker på 7:an så har vi en situation där m_X innehåller 6 och vi vill lägga till 7. Då multiplicerar vi m_X med 10, så att m_X kommer att innehålla 60, och sedan lägger vi till 7:an, varvid m_X innehåller 67. Efter anrop till UpdateData(FALSE) kommer talet "67" att synas i sifferfönstret.
Vi ska titta mer på det strax, men först måste vi konstatera att denna metod inte kan hantera decimaler. För att kunna hantera decimaler behöver vi en variabel som kan hålla reda på decimalpunktens position. Vi kan kalla den m_Decimal.
Övningsuppgift 4.4.2:
· Om du stängde projektet efter föregående övningsuppgift kan du öppna det igen.
· Deklarera en flyttalsvariabel m_Y i klassen CKalkylatornDlg.
· Deklarera även en flyttalsvariabel för minnet, kallad m_M.
· Deklarera en variabel m_InitX av typen BOOL.
· Deklarera en heltalsvariabel för att räkna decimaler. Kalla den m_Decimal.
· Deklarera till sist en heltalsvariabel som ska komma ihåg vilket tecken som gäller. Kalla den m_Tecken.
· Skriv lämpliga kommentarer så att man kan förstå vad variablerna ska vara till för. Se exemplet nedan.
· Du kan lämna projektet öppet, vi ska strax fortsätta.
float m_Y; // Y-register. X-register nedan!
float m_M; // Minne.
BOOL m_InitX; // TRUE om m_X ska tömmas när siffra trycks.
unsigned int m_Decimal;// Antal steg åt höger.
unsigned int m_Tecken; // 0 = inget tecken,
// 1 = division,
// 2 = gånger,
// 3 = minus,
// 4 = plus.
X-registret finns redan deklarerat. ClassWizard hjälpte oss med att deklarera den och associera den med sifferfönstret:
// Dialog Data
//{{AFX_DATA(CKalkylatornDlg)
enum { IDD = IDD_KALKYLATORN_DIALOG };
float m_X;
//}}AFX_DATA
Nu kan vi börja tänka på vad som ska hända när man trycker på knapparna. Först inser man nog att alla variabler borde vara rätt initierade när kalkylatorn startas. De tre registren m_X, m_Y samt m_M måste nollställas. Om m_X är nollställt behöver man inte initiera den variabeln när man trycker på en siffra. (Det var ju det som skulle ske när man trycker på en siffra efter att man tryckt på en teckentangent, och m_X kopierats till m_Y.) Detta styrs av den boolska variabeln m_InitX, vilken alltså borde initieras till FALSE. Vi har heller inte börjat mata in decimaler, så m_Decimal initieras till noll. Till sist har vi m_Tecken, som kan vara 1 - 4 för de olika tecknen, men som kan initieras till noll för att visa att man inte tryckt på något tecken än.
Och var ska vi initiera? Som vi redan lärt oss är det viktigt att ett objekt inte innehåller inkonsistent data, och av den anledningen finns det en eller flera konstruktorer.
Övningsuppgift 4.4.3:
· Om du stängde projektet efter föregående övningsuppgift kan du öppna det igen.
· Initiera alla variabler i CKalkylatorDlg:s konstruktor enligt ovan.
· Du kan lämna projektet öppet, vi ska strax fortsätta.
Låt oss nu börja arbeta med koden till siffertangenterna. Först och främst inser man att det är onödigt att upprepa all kod för alla de tio olika siffrorna 0 - 9. Det är bättre att deklarera en ny funktion som hanterarna till siffertangenterna kan anropa. Denna funktion kan ta ett heltal som argument och utföra bearbetningen med detta argument i stället. Därigenom behöver våra hanterare endast anropa den funktionen med olika argument, motsvarande siffran på den tangent som man klickat på. Om funktioner heter Siffra() kan den anropas så här av de olika hanterarna:
void
CKalkylatornDlg::On0()
{
Siffra(0);
}
void
CKalkylatornDlg::On1()
{
Siffra(1);
}
...och så vidare. Vi vet redan hur man lägger till metoder i en klass, och hur man lägger till hanterare. Här ska vi se hur man kan få hjälp av ClassView med att lägga till en funktion i en klass: Klicka med höger mustangent på klassnamnet, i detta fall CKalkylatornDlg, och välj Add Function:
Då dyker dialogrutan 'Add Member Function upp. Här kan vi fylla i returtyp, funktionsnamn, argumentlista samt access-typ. Det går ävan att göra funktionen statisk och/eller virtuell, men det går utanför denna kurs syfte. Vi fyller i:
När vi klickar på OK skapas dels en funktionsprototyp i klasshuvudet, dels själva funktionskroppen i implementeringsfilen, och det är i den vi ska skriva in vår kod.
I klasshuvudet ser det ut så här:
private:
void Siffra(int n);
Och funktionsprototypen ser ut så här. Den är än så länge tom:
void CKalkylatornDlg::Siffra(int n)
{
}
Ja, vad ska man nu skriva här? Vanligt är att inspirationen tryter när man ser en tom funktionskropp. Det gäller att fundera ut den kod som gör det som ska 'hända' när funktionen anropas, det vill säga när användaren klickat på en knapp.Till att börja med måste vi kontrollera om vi håller på att mata in siffror, eller om detta är första tangenttryckningen för ett värde. Meningen med m_InitX var att hålla reda på detta, så vi förutsätter att detta är gjort. Så här kan man göra:
Då skulle vi kunna skriva enligt följande pseudokod:
Om m_InitX är sant så ska följande göras:
· Värdet i m_X ska nollställas.
· Värdet i m_Decimal ska vara noll.
· m_InitX ska sättas till FALSE så att detta inte upprepas när användaren trycker på nästa siffra.
Därefter ska tryckt siffra, vilken finns i argumentet n, infogas i det tal som syns i sifferfönstret, det vill säga i m_X. Detta tal kan eventuellt vara noll. Hur sker då detta? Tänk på vad som händer när man trycker på en siffertangent på en kalkylator. Om det till exempel redan står '12' i sifferfönstret när man trycker på tangenten '3' så ändras innehållet i sifferfönstret till '123'. Uppenbarligen har talet i m_X multiplicerats med tio, varefter det tryckta talet adderats.
Detta gäller dock inte om man håller på och matar in decimaler. Om det i stället står '1.2' i sifferfönstret när man trycker på 3:an, så ändras talet till '1.23'. Här har alltså det nya talet i n dividerats med tio två gånger, och sedan adderats till m_X.
Hur avgör man då vilket av ovanstående fall som gäller, och hur många gånger talet ska divideras med tio om det är en decimal som matas in? Det är här vi använder m_Decimal. Så här kan arbetsgången vara:
Så här kan det se ut i pseudokod:
Om m_Decimal är lika med noll ska följande göras:
· Multiplicera m_X med 10.
· Addera n till m_X.
Annars görs detta:
· Skapa en temporär flyttalsvariabel och initiera den med värdet i n.
· Dividera den temporära flyttalsvariabeln med 10 så många gånger som anges i m_Decimal. (Använd en for-slinga.)
· Addera den temporära flyttalsvariabeln till m_X.
· Öka m_Decimal med ett.
Det sista steget gör att nästa siffra, om någon sådan trycks, kommer att hamna ännu ett steg längre till höger om decimalpunkten. För att vi sedan ska se resultatet i sifferfönstret är det viktigt att komma ihåg att anropa UpdateData() med argumentet FALSE.
Nu uppstår genast ett behov av att kunna ange att man vill mata in decimaler. För den skull finns en knapp med en punkt, IDC_PUNKT. Vi måste alltså lägga till kod i den knappens hanterare om det ska bli möjligt att mata in decimaler. Här gäller det att tänka sig för. Dels måste vi vara beredda på att användaren börjar hela talet med en punkt om denne vill mata in ett tal som är mindre än ett. Dels måste vi förhindra att punkten används mer än en gång i ett och samma tal.
Det förstnämnda löser vi på exakt samma sätt som när användaren börjar ett nytt tal med en siffra. Kopiera den delen från metoden Siffra().
Det andra löser vi genom att inte göra någonting om m_Decimal inte är noll, för det är det värde m_Decimal ska ha när man inte matar in decimaler.
Vad ska vi då göra när användaren trycker på punkten? Enkelt, m_Decimal ska sättas till 1, för att indikera att nästa siffra som matas in ska divideras med tio en gång för att bli första decimalen. Så här ser pseudokoden ut för den delen av metoden OnPunkt():
Om m_Decimal är lika med noll så gör följande:
· Tilldela m_Decimal värdet 1.
Övningsuppgift 4.5.1:
· Om du stängde projektet efter föregående övningsuppgift kan du öppna det igen.
· Lägg till en metod som heter Siffra(). Metoden ska vara av typen void och ha ett argument, ett heltal som heter 'n'.
· I var och en av de tio hanterare som hör till siffertangenterna lägger du till ett anrop till Siffra(), och anger ett värde motsvarande siffran på tangenten som argument.
· Översätt pseudokoden som angivits ovan till riktig C++ kod och skriv in i Siffra() respektive OnPunkt().
· Kompilera, länka och testa, nu ska det gå att mata in talvärden i kalkylatorn.
· Du kan lämna projektet öppet, vi ska strax fortsätta.
Om du försöker att mata in olika tal nu finner du att du måste stänga programmet och starta det igen mellan de olika talen. Nästa steg vore alltså att se till så att knapparna 'C' och 'CE' fungerar. Skillnaden mellan dessa är att 'CE' bara ska tömma m_X och m_Decimal, medan 'C' ska tömma alla register, och även m_Tecken. Du kan säkert skriva den koden själv. Glöm inte att anropa UpdateData() med argumentet FALSE.
Övningsuppgift 4.5.2:
· Om du stängde projektet efter föregående övningsuppgift kan du öppna det igen.
· Skriv koden till OnC() och OnCE() på egen hand.
· Kompilera, länka och testa. Nu ska du kunna tömma sifferfönstret med dessa knappar.
· Testa nu så att inmatning av olika tal fungerar. Prova såväl stora tal som tal som är mycket mindre än ett.
· Du kan lämna projektet öppet, vi ska fortsätta i nästa avsnitt.
OK, nu kan vi mata in siffror. Hur ska vi sedan kunna räkna med dem? För ändamålet finns ett antal tangenter för de olika räknesätten, samt en tangent för likhetstecknet. Det är nu Y-registret, m_Y, kommer in i bilden. Vill man utföra en operation, till exempel addition, på två olika tal, måste de lagras i två olika register. Detta går till på följande sätt:
· När användaren matar in det första talet lagras det i m_X.
· När användaren sedan trycker på en tangent för ett räknesätt sker följande:
1. Värdet i m_X kopieras till m_Y.
2. m_X ska tömmas, men inte förrän nästa siffra trycks. Markera detta genom att sätta m_InitX till TRUE.
3. Vi måste komma ihåg vilken operation som ska utföras, använd m_Tecken.
· När användaren är klar trycker han på tangenten med likhetstecknet. Då utförs beräkningen.
Vi kan således dela upp det i två steg: klick på operatortangent och klick på likhetstecken. Operatortangenten borde du klara på egen hand nu.
Likhetstecknet blir inte mycket svårare. Man behöver en switch som avgör vilket tecken som gäller, och utför beräkningen samt lagrar resultatet i m_Y. Efter switchen kommer lite till. Värdet i m_Y kopieras till m_X. Sedan initieras m_Y, m_Tecken och m_Decimal med noll, samt m_InitX med TRUE. Glöm inte att anropa UpdateData() så att resultatet syns i sifferfönstret.
Övningsuppgift 4.6.1:
· Om du stängde projektet i föregående avsnitt kan du öppna det igen.
· Skriv koden till OnPlus(), OnMinus(), OnGanger() samt OnDivision() på egen hand. Titta efter i kommentarerna för m_Tecken vilka värden som gäller för de olika operationerna.
· Lägg till koden för OnLikamed() enligt ovanstående diskussion.
· Kompilera, länka och testa. Nu ska du kunna utföra beräkningar med kalkylatorn.
· Du kan lämna projektet öppet, vi ska fortsätta i nästa avsnitt.
Nu ska vi lägga till hantering av minnet. Kalkylatorn ska ha ett minne, och man ska kunna lägga det som finns i m_X till minnet, eller dra ifrån detsamma. Därav tangenterna 'M+' och 'M-'. När man klickar på 'MR' ska minnets innehåll kopieras till m_X. Du kan säkert klara detta på egen hand.
Övningsuppgift 4.7.1:
· Om du stängde projektet i föregående avsnitt kan du öppna det igen.
· Skriv koden till OnMPlus(), OnMMinus() och OnMr() på egen hand.
· Kompilera, länka och testa. Nu ska du kunna summera tal i minnet, dra tal från minnet samt återkalla tal från minnet till sifferfönstret.
· Du kan lämna projektet öppet, vi ska strax fortsätta.
Som du säkert förstår av detta avsnitts titel var det inte detta som var det svåra. Nu ska vi försöka manipulera titelraden. Tanken är att det ska framgå av en gul text '-M-' i titelraden att det finns något i minnet. Om minnet är noll, ska texten försvinna.
Hur går då detta till? Om man söker i hjälpen kanske man finner en metod i CWnd som heter SetWindowText(). Den är avsedd att ändra texten i titelraden. Nu är det bara det att den funktionen skriver med vit text, och jag vill hellre ha en gul text, så att man lättare lägger märke till den. Egentligen vill jag bara krångla till det hela litet, så att vi har en anledning att testa något som inte är helt vanligt.
I första kapitlet testade vi både att skriva och rita i programfönstrets arbetsyta. Samma metoder skulle kunna användas här, det är bara det att vi inte vill skriva i arbetsytan. Söker man i hjälpen finner man kanske att klassen CWnd, vilken vi har ärvt från indirekt, har en metod som ger oss device context för hela programfönster, inte bara arbetsytan. Metoden heter GetWindowDC(). Har man en pekare mot detta device context kan man använda till exempel TextOut() som finns i CWnd, till att skriva var som helst i fönstret. Om pekaren lämpligtvis heter pDC kan man skriva pDC->TextOut() och fylla i lämpliga argument i parentesen.
Vi behöver en sak till. Vi behöver ändra färg
på texten. Nu är det så att klassen CDC innehåller två metoder för detta,
SetTextColor() och SetBkColor. Man måste nämligen ange bakgrundsfärgen också.
Bakgrunden är ju blå, inte vit. Skulle man skriva med en text som har fel
bakgrundsfärg ser texten markerad ut. Detta
fixar vi med SetBkColor(). Textfärgen behöver vara gul när vi visar
'-M-'. Men hur tar vi bort markeringen? Det lättaste sättet är att skriva över
den med samma text, fast blå. Såväl blå som gul färg ställer vi in med metoden
SetTextColor().
Nu uppstår genast nästa fråga: hur anger man en färg till dessa metoder? Enligt hjälpen vill de ha ett argument av typen COLORREF. Slår man upp denna datatyp får man endast veta att den är en 32 bitars variabel med tre färgvärden, ett för rött, ett för grönt och ett för blått. I hjälpen är texten RGB grå, och det innebär att man kan klicka på den för att få ytterligare information i ett pop-up fönster. Detta förklarar heller inte mer. Man kan nöja sig här och beräkna de olika värden som behövs, men det finns faktiskt ett makro som gör det lättare för oss. Vi kan åstadkomma en COLORREF med makrot RGB(), vilket tar de tre färgvärdena som argument. Gul färg får vi genom att blanda rött och grönt. En lagom gul fär blir det med följande blandning: RGB(255,192,0). Den blåa färgen är inte mättad blå, den är mörkblå. Detta kan vi få fram med RGB(0,0,127).
För att slippa göra allt detta på två ställen lägger vi till funktionen Minne(), vilken skriver in texten efter att först ha satt färgen rätt. Om m_M är noll, ska textfärgen vara blå, annars gul.
Övningsuppgift 4.7.2:
· Om du stängde projektet förut kan du öppna det igen.
· Lägg till funktionen Minne().
· Lägg till anrop till Minne() sist i OnMPlus() och OnMMinus().
· I funktionen minne gör du följande:
· Skapa en pekare av typen CDC (Class Device Context, titta i kapitel 1 och i hjälpen).
· Initiera pekaren genom att anropa GetWindowDC().
· Använd pekaren för att anropa SetBkColor(). Färgen ska vara mörkblå.
· Använd pekaren för att anropa SetTextColor(). Om m_M är noll ska färgen vara blå, annars gul.
· Använd pekaren för att anropa TextOut(). Lämplig position är x = 125 och y = 4. Slå upp funktionen i hjälpen, annars får du det inte rätt. Du kan använda den varianten som tar fyra argument.
· Nu är det dags att kompilera, länka och testa igen. Mata in ett tal, lagra det i minnet med 'M+'. Det gula M:et ska tändas. Tryck sedan 'M-'. Det gula M:et ska försvinna. Ett annat sätt att tömma minnet är att trycka på 'C'.
· Du kan lämna projektet öppet, vi ska fortsätta i nästa avsnitt.
Nu har vi bara en sak kvar, att stänga programmet. Som utlovat ska vi inte göra det på vanligt sätt. I stället ska vi titta i kapitel ett, i fönsterproceduren. Där finner vi att programmet stoppas med hjälp av ett speciellt meddelande, nämligen WM_QUIT, vilket kan skickas med hjälp av funktionen PostQuitMessage(). Argumentet vi använde var 0. Detta anger normalt programslut, medan annat värde anger att programmet avbrutits på grund av något fel.
Övningsuppgift 4.8.1:
· Om du stängde projektet i föregående avsnitt kan du öppna det igen.
· I hanteraren för Off-knappen, OnOff() lägger du in ett anrop till PostQuitMessage() med argumentet 0.
· Kompilera, länka och testa. Nu ska allt fungera.
Detta är som sagt inte alls nödvändigt i det här programmet, jag vill bara visa att det går. Det kan ju finnas situationer när man vill stänga programmet utan att användaren ska behöva göra något speciellt, och då kan denna metod komma väl till pass. I detta fall hade det varit enklare att behålla någon av knapparna 'OK' och 'Cancel', och bara byta texten på knappen till 'Off'.