5.
Rullningslister
· Tillämpning med rullningslist.
· Rullningslisten.
· Rubrikobjekt, ‘static’.
· Meddelanden från ScrollBar.
· Bibehålla inställningar mellan anrop.
· MessageBox().
· AfxMessageBox().
· Aktiv och Visa.
· Övriga listkoder.
· Att hitta rätt list.
Tillämpning med rullningslist.
I detta kapitel ska vi se hur man använder rullningslister. Vi skapar en applikation med en dialogruta på samma sätt som vi gjort tidigare. Så här ska det se ut när vi anpassat våra menyer, skapat en dialogruta och öppnat den:
Vi skapar dialogrutans objekt i vyn. Dialogrutan har kvar originalknapparna för ‘OK’ och ‘Cancel’, men ‘Caption’ är översatt till svenska samt försedd med Alt-snabbtangenter. Två kryssrutor ‘Aktiv’ och ‘Visa’ syns också tillsammans med en horisontell rullningslist. Vad som inte syns är att det ovanför kryssrutorna finns ett rubrikobjekt. Det syns inte just nu, därför att det inte finns någon text i det.
Observera punkterna efter menyvalet ‘Justera…’. Detta betyder att menyvalet leder till öppning av dialogruta. När vi så småningom blir färdiga med all kod till dialogrutan kommer den att se ut så här:
Vi kommer även att lägga till funktionalitet för att återställa rullningslisten till sitt ursprungliga värde. Detta gör vi med hjälp av MessageBox() i avsnittet med samma namn. Om man väljer ‘Läs Av’ ska nämligen detta visas:
Och om man klickar på ‘Ja’ ska rullningslistens inställning återställas till 50, varefter följande MessgeBox() visas:
Vi passar även på att ändra litet i ‘Om…’-dialogrutan. Menyvalet ska heta ‘Om Scrollbars…’ och dialogrutan kan se ut ungefär så här när du ändrat i ikonen som tillhör MainFrame:
(Försök att rita en finare katt själv, det är inte så lätt som det ser ut!)
För att underlätta för såväl dig själv som för handledaren att införa respektive jämföra häftets text med din källkod är det starkt rekommendabelt att du följer nedanstående namnsättning:
· Projekt............................ : Scrollbars
· Dialogrutans klass........... : CListDialog
· Dilogruteobjektets namn.. : m_ListDialog
· Rullningslistens id........... : IDC_LIST
· Rubrikobjektets id........... : IDC_TEXT
· Kryssrutan Aktiv:s id....... : IDC_AKTIV
· Kryssrutan Visa:s id........ : IDC_VISA
Rullningslisten.
Rullningslisten har privata medlemmar avsedda att påverkas via dess accessfunktioner:
· SetScrollRange(). Sätter värde för rullningslistens övre och nedre gränsvärde.
· SetScrollPos(). Sätter värde för rullningslistens aktuella position.
Vi kan ha egna variabler i dialogruteklassen som håller motsvarande värden åt oss. Dessa ska vara av typen ‘int’ och kan initieras i dialogrutans konstruktor. I vårt exempel namnger vi och initierar dem som följer:
· m_ListPos = 50.
· m_ListMin = 1.
· m_ListMax = 100.
Initiering som använder metoder i rullningslistens objekt kan inte ske i dialogrutans konstruktor, eftersom den kallas innan dialogrutan öppnar de olika fönster som representerar kontrollerna. Dessutom behöver vi initiera rullnings-listen varje gång vi öppnar dialogrutan. För att klara sådan initiering skickas ett meddelande WM_INITDIALOG till dialogrutans klass varje gång man anropar DoModal(). Koppla en funktion till meddelandet. Funktionen kommer att heta OnInitDialog(). Här kan vi anropa SetScrollRange() och SetScrollPos().
För att kunna anropa dessa behöver vi adressen till rullningslistens objekt, och det vet vi ju hur man skaffar sig från tidigare kapitel. Om man slår upp ‘hierarchy chart’ i hjälpen kan man finna att klassen för en rullningslist heter ‘CScrollBar’. Funktionen GetDlgItem() returnerar en allmän objektspekare, men vi behöver offsets till metoder i CScrollBar, alltså måste vi explicivt typomvandla resultatet från GetDlgItem() till CScrollBar*. Kalla pekaren m_ListPekare.
? GetDlgItem(),
SetScrollRange(), SetScrollPos(), GetScrollRange(), GetScrollPos().
Rubrikobjekt, ‘static’.
Man kan inte koppla en medlemsvariabel till ett rubrikobjekt. Trots detta är det möjligt att ändra texten. Många kontroller har en ledtext, och ett rubrikobjekt har bara en ledtext, och ledtexten på en kontroll kan ändras med hjälp av meto-den SetDlgItemText(), vilken finns i CDialog. Om man slår upp den i hjälpen finner man att den tar emot två argument, id för kontrollen och en text. Texten kan vara såväl char* som CString-objekt.
Naturligtvis är det lämpligt att skapa två rubrikobjekt, ett med en fast ledtext med texten ‘Listan står nu på värdet:’, och ett som visar rullningslistens inställda värde, och kombinera ihop dem snyggt i dialogrutan. Man måste dock tänka på att det numeriska värdet som ska visas varken är char* eller CString-objekt. Det måste omvandlas till text, och till detta har vi tillgång till funktionen itoa(). Denna funktion ingår i C++, medan den i C fanns att tillgå i string.h. Vi behöver i vårt fall alltså inte ha med någon extra #include-sats för att få tillgång till itoa().
Vi kommer dock att använda endast ett objekt, för att ytterligare demonstrera användning av klassen CString. Först deklarerar vi ett CString-objekt att använda som andra argument till SetDlgItemText(), samt en charlista:
CString
csText;
char
cPos[25];
Som namnet för charlistan antyder är den till för att hålla rullningslistens inställda värde i form av text. Vi använder itoa() för att omvandla det numeriska värdet, och den funktionen behöver hela 25 positioner att arbeta med. Minskar man detta utrymme riskerar itoa() att orsaka ett skyddsfel. Så här använder man itoa() för att omvandla det numeriska värdet i m_ListPos till text, vilket lagras i cPos:
itoa(m_ListPos, cPos, 10);
Det sista argumentet är omvandlingsbasen. Det går även att använda 2, 8 respektive 16.
Därefter försöker vi slå ihop en fast text ‘Listan står nu på värdet: ’ med cPos. Detta går ju normalt inte, man får ett kompileringsfel som säger att man inte kan addera två pekare. Däremot kan vi typomvandla den ena texten till CString, och sedan lagra resultatet i csText:
csText = (CString) "Listen står nu på värdet: " + cPos;
Detta fungerar. Vi kunde lika gärna typomvandlat den andra texten. I detta enkla exempel, där vi lägger ihop en fast text med en variabel, kunde vi lika gärna initierat csText med den första texten, och sedan skarvat på cPos med det värde vi får när vi anropat itoa():
CString csText = "Listen står nu på värdet: ";
...
csText = csText
+ cPos;
Den första metoden visar dock på en flexibel metod man kan använda i alla lägen när man behöver skarva ihop texter som inte är av typen CString. Typomvandling till CString ger tillgång till alla operatorer och metoder i klassen.
Testar vi nu, ska dialogrutan se ut så här:
Observera att varken rullningslist eller kryssrutor fungerar än. Vi kan dra i rullningslistens handtag, men värdet i rubrikobjektet ändras inte, och när vi släpper handtaget faller det tillbaka till ursprungsläget. Vi ska ändra på detta i nästa avsnitt, där vi tittar på de meddelanden som rullningslisten lämnar ifrån sig.
Meddelanden från ScrollBar.
En tryckknapp har två meddelnanden, ‘BN_CLICKED’ och ‘BN_DOUBLECLICKED’. En rullningslist har av naturliga orsaker fler. Dessa är dock samlade under ett meddelande, antingen WM_VSCROLL eller WM_HSCROLL beroende om vi har en vertikal eller, som i vårt fall, en horisontell rullningslist. Skapar man en handler till detta meddelande får den funktionen ett argument som talar om vilket meddelande som gäller. Funktionen ser ut så här när vi skapat den:
void CListDialog::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: Add your message handler code
here
// and/or call default
CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}
Första argumentet är det meddelande vi letar efter. Det kan innehålla något av följande meddelanden:
· SB_THUMBPOSITION. : När vi släpper handtaget.
· SB_THUMBTRACK...... : När vi flyttar handtaget.
· SB_LINELEFT.............. : När vi klickar på minska-pilen.
· SB_LINERIGHT............ : När vi klickar på öka-pilen.
· SB_PAGELEFT............. : När vi klickar mellan handtaget och
minska-pilen.
· SB_PAGERIGHT.......... : När vi klickar mellan handtaget och
öka-pilen.
Vi kan använda detta till att avgöra vad som ska ske när användaren manipulerar rullningslisten. Vi kan börja med att prova SB_THUMBPOSITION, men först en sak till.
Andra argumentet anger vilken position handtaget stod på när användaren släppte upp musknappen. Rullningslisten uppdaterar som vi märkt inte sig själv. Vad vi behöver göra är att anropa SetScrollPos().
Tredje argumentet innehåller adressen till rullningslistens objekt, och den behöver vi i detta fall för att anropa metoden.
Om vi dessutom tänker oss att textobjektet ska återspegla den nya inställningen kan vi använda samma metod som vi använde tidigare. Så här kan vi göra:
void CListDialog::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
CString csText;
char cPos[25];
switch(nSBCode)
{
case SB_THUMBPOSITION:
pScrollBar->SetScrollPos(nPos);
itoa(nPos, cPos, 10);
csText = (CString) "Listen står nu på värdet: " + cPos;
SetDlgItemText(IDC_TEXT, csText);
break;
}
CDialog::OnHScroll(nSBCode, nPos,
pScrollBar);
}
Vi ska lägga till de övriga koderna senare. Nu kan vi testa rullningslisten:
Bibehålla inställningar mellan anrop.
Trots att vi har deklarerat dialogrutans objekt globalt i klassen CScrollbarsView märker vi nu att vi tappar bort inställningen mellan anropen. Vad beror detta på? När vi öppnar dialogrutan ställs rullningslisten och rubriken in på det värde som finns i m_ListPos. Tittar vi i ovanstående kod, så ser vi att vi inte ändrar värdet i denna medlem när användaren flyttar handtaget. Borde vi inte göra det?
Svaret är nej, nu har vi en chans att skilja på ‘OK’ och ‘CANCEL’. Låt oss spara värdet i m_ListPos när användaren klickar på ‘OK’, men inte annars. Lägg till en ‘handler’ till OK-knappen. Skaffa som vanligt en pekare mot rullningslistens objekt, och använf metoden GetScrollPos() för att lagra inställningen i m_ListPos.
Lyckas du med detta, ska värdet finnas kvar om du klickar på ‘OK’, men inte om du klickar på ‘CANCEL’.
? GetScrollPos().
MessageBox().
När man väljer [Arkiv - Läs Av...] ska man belönas med följande:
För att kunna göra detta måste vi lära oss litet mer om MessageBox(). Vi har tidigare endast använt MessageBox() till att skriva ut en text, men funktionen tar faktiskt tre argument:
1. lpszText.......... : Meddelandet som ska skivas ut.
2. lpszCaption..... : Texten i meddelanderutans titelrad.
3. nType.............. : Meddelanderutans stil.
Till att börja med ska konstateras att ‘lpsz’ står för Long Pointer to String with Zero termination, d.v.s. en lång textpekare. Det är det vi får när vi anger en textkonstant e.d.
De två första parametrarna är tydliga nog, men den tredje kräver en hel del förklaring. Den kan innehålla en eller flera stilflaggor i kombination. Man kombinerar ihop flaggorna med hjälp av ‘|’, till exempel MB_ICONQUESTION|MB_YESNO.
Det finns fyra huvudgrupper av stilar:
1. Grafiska objekt, ikoner, typ frågetecken etc.
2. Specifikation av de knappar som ska ingå.
3. Vilken knapp som ska vara förvald, det vill säga reagera direkt på ‘Enter’-tangenten.
4. Meddelanderutans ‘modalitet’, det vill säga huruvida den tvingar användaren att besvara meddelanderutan innan man kan fortsätta.
De grafiska objekten är följande:
· MB_ICONEXCLAMATION - En bild med ett utropstecken visas i dialogrutan.
· MB_ICONINFORMATION - En bild med ett informations-‘I’ visas i dialogrutan.
· MB_ICONQUESTION - En bild med ett frågetecken visas i dialogrutan.
·
MB_ICONSTOP - En bild med ett stopptecken
visas i dialogrutan.
De olika knappgrupperingarna som kan ingå är följande:
· MB_ABORTRETRYIGNORE - Meddelanderutan innehåller tre knappar: ‘Avbryt’, ‘Nytt försök’ samt ‘Ignorera’.
· MB_OK Meddelanderutan innehåller en knapp: ‘OK’.
· MB_OKCANCEL Meddelanderutan innehåller två knappar: ‘OK’ och ‘Ångra’.
· MB_RETRYCANCEL Meddelanderutan innehåller två knappar: ‘Nytt försök’ och ‘Avbryt’.
· MB_YESNO Meddelanderutan innehåller två knappar: ‘Ja’ och ‘Nej’.
· MB_YESNOCANCEL Meddelanderutan innehåller tre knappar: ‘Ja’, ‘Nej’ samt ‘Avbryt’.
Förvald knapp kan vara en av följande:
· MB_DEFBUTTON1 Första knappen är förvald. Observera att första knappen alltid är förvald om man inte angvänt flaggan MB_DEFBUTTON2 eller MB_DEFBUTTON3.
· MB_DEFBUTTON2 Andra knappen är förvald.
· MB_DEFBUTTON3 Tredje knappen är förvald.
En av följande modaliteter kan anges:
· MB_APPLMODAL Användaren måste
besvara meddelandet innan han kan fortsätta arbeta med det program som anropade
denna MessageBox(). Det är dock möjligt att växla till andra program och arbeta
med dem. Om inget annat anges gäller MB_APPLMODAL.
· MB_SYSTEMMODAL Alla program är
spärrade tills användaren besvarat meddelandet. Denna typ av meddelande är
endast avsedda för meddelande av vikt för hela systemet, som till exempel
programfel som kan resultera i dataförlust i andra program, och ska därför
användas sparsamt.
· MB_TASKMODAL Ungefär detsamma
som MB_APPLMODAL, men har ingen
användning vid programmering under MFC. Denna flagga är reserverad för bruk av
program som inte har något programfönster.
Nu kan vi lägga till kod i funktionen OnArkivLsav() som visar vår messagebox. Observera att värdet som ska skrivas ut hämtas direkt ur m_ListDialog:s medlemsvariabel m_ListPos:
Läser man om MessageBox() i hjälpen finner man att funktionen returnerar ett värde motsvarande den knapp användaren tryckt på. ‘Ja’-knappen returnerar IDYES, och vi kan testa på detta värde för att eventuellt återställa värdet till 50 och sedan visa upp en annan meddelanderuta:
AfxMessageBox().
MessageBox() är en gammal funktion. Tidigare var det den enda färdiga funktionen som automatiskt visade en meddelanderuta. När MFC kom infördes en
ny funktion som var mer anpassad till den meddelandestruktur som gäller i de program vi genererar med MFC och Afx. Den nya funktionen har därför fått namnet AfxMessageBox().
Den gamla funktionen MessageBox() har införlivats i klassen CWnd. Det är alltså möjligt att använda den i CView, eller i vårt fall CScrollbarsView eftersom dessa ärver från CWnd. Det går dock inte att använda MessageBox() direkt i dokumentklassen. Naturligtvis kan man hämta adressen till vyn från dokumentet genom att anropa först GetFirstViewPosition() och sedan GetNextView(), men det är sällan man behöver detta.
Den nya funktionen AfxMessageBox tillhör dock gruppen globalt deklarerade funktioner, och den kan användas när som helst. Den introducerar samtidigt en ny standard, där man bland annat inte längre tillåter avvikande titelrad. Titeln blir alltid programmets namn.
Slå upp såväl MessageBox() som AfxMessageBox() i hjälpen, och läs om hur de skiljer sig. I fortsättningen ska vi använda AfxMessageBox(). MessageBox() finns kvara av kompatibilitetsskäl, samt för den som programmerar utan MFC, det vill säga med SDK i stället. Observera att det inte är samma MessageBox() som ligger i SDK (Software Developers Kit). Här har vi det extra argumentet först i argumentlistan, vilket talar om vilket fönster meddelanderutan hör till.
? MessageBox() under MFC, AfxMessageBox() styles samt MessageBox under SDK.
Aktiv och Visa.
I föregående kapitel använde vi knappar till att aktivera/avaktivera respektive visa/gömma en kryssruta. I detta kapitel ska vi använda kryssrutor till att göra detsamma med rullningslisten.
Vi använder ClassWizard till att lägga ‘handlers’ till kryssrutorna. Se kapitel fyra under ‘Använda krysset’. Kontrollerna heter, som vi bestämde tidigare i detta kapitel, IDC_AKTIV respektive IDC_VISA.
Lägg till medlemsvariabler för de bägge kryssrutorna: m_Aktiv respektive m_Visa. De ska vara av typen BOOL. Glöm inte att de ska vara initierade till TRUE, om vi ska komma in på ‘rätt fot’ i dialogrutan, det vill säga listen är ju synlig och aktiv, alltså ska bägge kryssrutorna vara förkryssade samt bägge variablerna vara TRUE.
Rullningslisten är liksom tryckknappen ett fönster, och har ärvt samma funktioner av CWnd för hantering av synlighet och tillgänglighet som tryckknappen, vilket vi diskuterade i kapitel fyra. Därför bör du på egen hand kunna åstadkomma följande:
Övriga listkoder.
Nu återgår vi till OnHScroll(). Här fanns ett argument nSBCode (Numeric ScrollBar Code), och i avsnittet ‘Meddelanden från ScrollBar’ finns en lista med de olika värden vi kan finna i nSBCode. Vi har bara behandlat en kod, SB_THUMBPOSITION, den som talar om var användaren släppte handtaget. Det finns en kod SB_THUMBTRACK som anger att användaren flyttat handtaget. Detta meddelande genereras varje gång användaren flyttat handtaget en logisk enhet på skärmen, vare sig denne släppt knappen eller ej. Här har vi möjlighet att få en mera ‘levande’ reaktion på handtagets rörelser. Vi ska göra exakt samma sak som reaktion på detta meddelande, så vi behöver inte skriva om koden, bara lägga till ett ‘case’ till över samma kod. Kom ihåg att ett ‘case’ gäller till nästa ‘break’, inte bara till nästa ‘case’.
När man klickat på någon av pilknapparna eller utrymmet mellan handtaget och ena pilknappen, kommer inte handtaget att vara flyttat när vi kommer in i denna funktion. Det innebär att värdet i nPos är noll. Vi måste använda en medlem i CScrollBar som heter GetScrollPos() för att ta reda på var listen står (eller använda m_ListPos om den helt säkert är rätt). Sedan kan vi öka respektive minska positionen ett steg (om användaren tryckt på en av pilknapparna) eller en sida (om användaren tryck mellan handtaget och en av pilknapparna).
En sida kan betyda olika i olika program. En vertikal rullningslist i ett redigeringsprogram bör väl rulla lika många steg som motsvarar en synlig sida på skärmen, eller kanske en á två rader mindre. Andra program kanske flyttar ett fast antal steg medan åter andra flyttar en viss procent av rullningslistens hela omfång. I vårt fall är omfånget bestämt till 1 - 100, så det sista skulle ju inte vara nödvändigt, men prova i alla fall att skriva en logik som använder nedre och övre gränsen och flytta handtaget 10% därav.
Och en sak till: Glöm inte att kontrollera gränserna när du flyttar handtaget. Om man hamnar utanför området, det vill säga om man anger ett värde i SetScrollPos() som ligger utanför de gränser som angivits vid anrop till SetScrollRange(), kommer programmet att krascha!
Varje ‘case’ ska själv uppdatera texten. Det är inte rekommendabelt att man gör det efter switchen, eftersom det inte är helt säkert att vi har något äkta ‘case’. I detta fall har vi det, men i andra fall har vi kanske flera rullningslister, eller också har vi ett program där fönstret är försett med rullnigslister, och i sådant fall måste vi dessutom kontrollera att användaren klicka på ‘vår’ rullningslist, och inte programfönstrets. Annars sker det ‘lustiga’saker när användaren använder programfönstrets rullningslister.
Övningsuppgift:
· Skapa ovanstående program under projektnamn Scrollbars.
· Testa att göra det på samma sätt som här i häftet.
Att hitta rätt list.
Hur kan man då veta vilken rullningslist som använts? Det tredje argumentet innehåller ju adressen till rullnigslistobjektet. Denna adress kan användas att jämföra med resultatet man får när man anropar GetDlgItem() för vår rullningslist. Speciellt i de fall när man har fler än en horisontell eller fler än en vertikal rullningslist måste man använda denna metod för att avgöra vilken rullningslist som avses, eftersom vi bara har två huvudmeddelanden: ON_HSCROLL och ON_VSCROLL.
Övningsuppgift:
· Skapa en rullningslist och ett rubrikobjekt till.
· Den övre rullningslisten ska påverka det övre rubrikobjektet.
· Den nedre rullningslisten ska påverka det nedre rubrikobjektet.
· Så här ska det se ut:
Vi kommer i ett senare kapitel att skapa kontroller direkt i programfönstret. Om man gör det, och anpassar förnstrets storlek så att kontrollerna inte får plats, så lägger programförnstret självt upp rullningslister för att man ska kunna rulla fönstret till en position där man kommer åt en viss kontroll. Då har vi en rullningslist till som kommer att aktivera vår ‘handler’. Det är då naturligtvis nödvändigt att kontrollera så att vi inte gör något i vår kod när användaren i själva verket rullat innehållet i programfönstret.