*:85/ID200V C++ HT07 Föreläsning 11 Genomgång av inlämningsuppgiften Inluppen: exempel på en liten tillämpning Vandring över skärmen med piltangenterna När markören hamnar över en knapp visas knappen med omvänd färg. Om man då trycker på RETURN utförs handlingen (värdet i displayen ökas eller minskas med 1, eller programmet avslutas). Komponenterna behöver inte vara inramade Längst ner en meny: vänster/högerpilar byter alternativ, RETURN utför handlingen. Om markören finns inom menyn fungerar tangenterna +, - och q som kortkommandon. Obs! Exemplet är mycket enkelt! Bild 2 1
Ett större exempel Mail-programmet Pine. Komponenterna är inte inramade, spänner över hela fönsterbredden. Lägg märke till menyns utformning. Bild 3 Samma exempel, annan bildskärmsbild Även i Pine beror tangentkommandons betydelse på var markören befinner sig. Scrollande delfönster o.s.v. är inte så svåra att göra, men de ingår inte i den obligatoriska delen av uppgiften Bild 4 2
Åter till det lilla exemplet: skiss på tillämpningsprogrammet class Value{ int val; public: void oka() { val++; // Och visa nya värdet! } void minska() { val--; // Och visa nya värdet! } }; int main(){ Value v; Session ses; // Skapa displayen så att v kan visa värdet där // Skapa knappen för öka, koppla till v.oka // Skapa knappen för minska, koppla till v.minska // Skapa knappen för sluta, koppla till exit // Skapa menyn // Skapa menyvalet +, koppla till v.oka // Skapa menyvalet -, koppla till v.minska // Skapa menyvalet q, koppla till exit // Skapa kortkommandot +, koppla till v.oka // Skapa kortkommandot -, koppla till v.minska // Skapa kortkommandot q, koppla till exit ses.run(); } Tillämpningen anger vid skapandet av skärmobjekten var på skärmen de skall vara, vad som skall stå i dem o.s.v. Bild 5 Inlämningsuppgiften Skapa (början till) ett klassbibliotek med komponenter för sådana tillämpningar Obligatoriska komponenter: etikett (label), knapp, editerbar sträng, meny, sammansatt komponent session Bestäm själva detaljutformningen (inramade komponenter eller ej, eller både och, färganvändning osv) Biblioteket skall vara förberett för utbyggnad, dvs utformat mer generellt än vad som krävs just här För användaren (tillämpningsprogrammeraren): bekvämt men flexibelt, dvs implementerade defaultbeteenden men möjlighet att ändra Internt: hög grad av uppdelning, varje klass sköter sina naturliga uppgifter Bild 6 3
Tilläggskrav på implementering Datasamlingar implementerade med STL-behållarklasser Genomgångar, sökningar osv med generiska algoritmer från standardbiblioteket Adaptorer för funktionsobjekt (bind1st, bind2nd, mem_fun osv) används där det är möjligt, egna funktionsobjekt skapas annars. Kopplingar till tillämpningsklassers metoder sker genom medlemsfunktionspekare. Komponentobjekten hanteras via referensräknande pekarklasser. Biblioteket reagerar på felaktigheter (överlappande komponenter, komponenter utanför skärmarean osv) genom att generera undantag. Icke-triviala medlemsfunktioner definieras utanför klassdefinitioner (gäller även mallar). Deklarationer av klasser i headerfiler, definitioner av medlemsfunktioner och hjälpfunktioner i implementeringsfiler (gäller inte mallar eller infogade (inline) funktioner). Hög grad av uppdelning i filer - helst varje klass på egen fil (använd make!). Bild 7 Förenklingar och önskemål Förenklingar: Icke-överlappande komponenter Önskemål: Kontroll av vilken komponent markören befinner sig i, kontroll av om komponenter överlappar varandra, eventuell beräkning av position för centrering av text osv är operationer som har att göra med rektanglar. Skapa gärna en klass rektangel med denna funktionalitet som sedan kan användas i komponentklasserna, gröta inte ner komponenterna eller sökfunktionerna med koordinataritmetik. Bild 8 4
En möjlig arkitektur för exemplet (instansexempel) Session getcont setcont run content remove Knapp öka Knapp minska Knapp sluta Menyvalen Display Meny + - q q + - main v 7 öka minska Ett tillämpningsobjekt Bild 9 Rectangle Förslag till grov klasstruktur contains Passive handle_event Component erase enter leave handle_event Edit_string Button Compund handle_event handle_event handle_event get_text remove Menu handle_event Label set_text Session getcontent setcontent run Action_base perform Action<> Ptr<> * -> perform Bild 10 5
Grov beskrivning av klasser Rectangle innehåller skärmkoordinater för en rektangulär area och medlemsfunktioner för koordinataritmetik, t.ex. contains() som tar en punkt (kolumnoch radnummer) och returnerar om denna punkt ligger inom dess area. Är mest till för hålla koordinataritmetik separerad från den övriga koden för komponenterna. Component den egentliga rotklassen i hierarkin. Borde innehålla en rent virtuell medlemsfunktion () som implementeras i subklasserna och ritar upp komponenten på skärmen; en rent virtuell medlemsfunktion handle_event() som tar en Event-struct (så att komponenten kan se vad det var för tangent och var den trycktes) och returnerar ett booleskt värde (true om komponenten hanterat händelsen, false annars); två virtuella funktioner enter() och leave() som borde vara implementerade som tomma funktioner men kan överskuggas av subklasser om man vill att något speciellt skall hända när markören flyttas in i komponentens område resp. flyttas ut därifrån. Innehåller även icke-virtuell funktion erase() Passive rotklassen för passiva komponenter, kan implementera handle_event()- medlemsfunktionen så att den alltid returnerar false - passiva objekt reagerar inte på några händelser Bild 11 Label lövklass, implementerar () som skriver ut texten på skärmen Edit_string lövklass, editerbar sträng som implementerar handle_event(): den reagerar på skrivbara tecken genom att lägga in dem i en intern sträng och skriva ut den förändrade strängen på skärmen samt även på delete-tangenten och rubout-tangenten genom att ta bort tecken ur strängen. Den implementerar även () och har en egen medlemsfunktion get_text(). Denna klass borde implementeras sist. Button lövklass, implementerar () och handle_event(): den reagerar på RETURNtangenten genom att anropa någon medlemsfunktion i något objekt av någon klass i tillämpningen. Måste alltså ha en koppling till tillämpningen (en Action_base *, se nedan Action och Action_base). Bild 12 6
Compound klassen för sammansatta skärmobjekt. Borde innehålla en datasamling för de ingående skärombjekten (lämpligen en vector med "pekare" till skärmobjekt) och en datasamling med kortkommandon (lämpligen en map med tangentkoder som nycklar och Action_base-pekare som värden - se Action och Action_base). Borde ha två funktioner (): en för att era komponenter och en för att era kortkommandon, samt funktionen remove() för borttagning av komponenter. Implementerar handle_event() genom att leta upp den komponent inom vars area händelsen inträffat och anropa dess handle_event(). Om komponenten svarar att den inte hanterar denna händelse (eller om händelsen inträffat utanför någon ingående komponent) så kontrolleras om detta objekt självt kan hantera händelsen (t.ex. genom att titta i kortkommando -samlingen). Medlemsfunktionen () implementeras genom att gå igenom de ingående komponenterna och anropa deras (). Bild 13 Menu egentligen ett sammansatt objekt vars ingående komponenter endast kan vara knappar. Dessutom borde piltangenter flytta markören till nästa/föregående knapp och knapparna borde ligga bredvid varandra. Obs dock att Meny inte kan vara en subklass till Compound - ett subklassobjekt skall kunna användas i alla de sammanhang där ett basklassobjekt förväntas - till ett Compound-objekt kan man era godtyckliga komponenter, men det kan man inte till ett Menu-objekt. Däremot borde Menu implementeras genom att innehålla ett Compound-objekt och de flesta medlemsfunktioner i Menu kommar bara att innehålla anrop av motsvarande funktion i det inneslutna Compound-objektet. En bra sak vore om -funktionen för knappar i Menu inte tog en färdigskapad knapp utan bara info om vad som skall stå på knappen och vad den skall kopplas till, då kan Menu-objektet räkna ut vilka koordinater nästa knapp i menyn skall ha och skapa knapp-objektet självt, innan det skickas vidare till () i det inneslutna Compund-objektet. Några design-beslut om hur menyerna skall se ut (stående, liggande, knappstorleken, hur kan tillämpningen styra detta...) måste göras i så fall. Bild 14 7
Action och representerar en koppling mellan biblioteksklasser och tillämpningen. Action_base Innehåller en pekare till ett objekt av en tillämpningsklass, en medlemspekare till en medlemsfunktion i denna klass samt medlemsfunktionen perform() som anropar denna medlemsfunktion hos detta objekt. Eftersom tillämpningsklassen är okänd måste Action vara en mall. Eftersom man vill kunna ha objekt av olika instansieringar av denna mall i samma datasamling (samlingen av kortkommandon i ett Compound-objekt) så bör man skapa en basklass (som inte är en mall) till Action, nämligen Action_base. Denna basklass borde innehålla en virtual-deklaration av medlemsfunktionen perform(). I de övriga biblioteksklasser där man vill ha en koppling till tillämpningars objekt och medlemsfunktioner kan man nu ha en pekare till Action_base, som alltså kan peka ut objekt av olika instansieringar av Action. Den föreslagna klassen Action representerar en förbindelse med en medlemsfunktion i ett objekt. Man kan tänka sig att en tilläpmning även skulle vilja kunna skapa en koppling mellan bibliotekskomponenter och fristående funktioner i tillämpningen. Vill man förbereda för det kan man göra en specialisering av Action-mallen där typen man specialiserar för är en funktionspekartyp och där Action-klassen innehåller en funktionspekare istället för en objektpekare och en medlemsfunktionspekare. Bild 15 Session representerar hela terminalskärmen. Innehåller ett sammansatt objekt som i sin tur innehåller skärmobjekten. Har medlemsfunktionerna getcontent() för att returnera en pekare till det sammansatta objektet och setcontent() för att byta ut det mot ett annat. Tillämpningen skapar ett Session-objekt, erar komponenter och kortkommandon till dess Content och anropar sedan funktionen run(). run() innehåller en loop där man läser in nästa händelse och skickar denna händelse till rätt komponent. Förutom händelseinläsning är gången precis som för ett sammansatt objekts händelsehantering. Bild 16 8