2.
Programmets beståndsdelar
· Applikationen.
· Afx meddelandehantering.
· Variablernas plats.
· ClassWizard.
Applikationen.
Den centrala windowshanteringen sker i den s.k. applikationen (engelska application), vilken hanterar initiering av vårt program. Här finns också ett par grundläggande funktioner för hantering av windowsmeddelanden , vilka alla har ett namn som börjar på ‘Afx’. Förkortningen står för ‘Application Frame-work eXchange’. Vi kommer senare att se hur ‘Afx’ underlättar för oss att lägga till våra kommandohanterare. Hur fick vi nu tillgång till detta? Titta i filen Hello.h:
// Hello.h :
main header file for the HELLO application
#ifndef
__AFXWIN_H__
#error include 'stdafx.h' before
including this file for PCH
#endif
#include
"resource.h" // main
symbols
///////////////////////////////////////////////////////////////
// CHelloApp:
// See
Hello.cpp for the implementation of this class
class CHelloApp
: public CWinApp
{
public:
CHelloApp();
// Overrides
// ClassWizard generated virtual function
overrides
//{{AFX_VIRTUAL(CHelloApp)
public:
virtual BOOL InitInstance();
//}}AFX_VIRTUAL
//
Implementation
//{{AFX_MSG(CHelloApp)
afx_msg void OnAppAbout();
// NOTE - the ClassWizard will add and
remove member
// functions here.
// DO NOT EDIT what you see in these
blocks of generated
// code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
Här ser vi att vår applikation bygger på klassen CHelloApp, vilken ärver direkt från CWinApp. I ClassView ser man att objektet ‘theApp’, som skapas med CHelloApp som mall i filen Hello.cpp är globalt deklarerat.
? CWinApp, CWinApp::CWinApp,
CWinApp Class Members.
När vi skapade vårt projekt användes ett hjälpprogram som heter ClassWizard. Detta program kommer att assistera oss under hela vårt MFC-projekt, och är bl.a. ansvarig för att vi kan se klassträdet i ClassView. Betrakta nu källkoden ovan och lägg speciellt märke till följande:
1. Villkorlig kompilering tvingar oss att ta med filen ”stdafx.h” i vårt projekt. Observera att den ligger i vårt projekt och är skräddarsydd för detta. Öppna den och se vilka #include-satser den innehåller.
2. Vi får funktionen InitInstance() utbytt (override) för att kunna anpassa initiering av vårt program. Detta har ClassWizard lagt in mellan kommentarerna //{{AFX_VIRTUAL(CHelloApp) och //}}AFX_VIRTUAL.
3. Mellan kommentarerna //{{AFX_MSG(CHelloApp) och //}}AFX_MSG finns satsen afx_msg
void OnAppAbout();
Här hanteras det menykommando som visar upp
About-dialogrutan. ClassWizard lägger själv in Afx-kommandon här.
4. Dessutom avslutas klassbeskrivningen med ett makro: DECLARE_MESSAGE_MAP(). Slå upp detta i hjälpen och läs Microsofts ‘recept’ på hur det används.
1. Tittar vi i filen stdafx.h ser vi följande (dubbelklicka på filnamnet i FileView):
// stdafx.h :
include file for standard system include files,
// or project
specific include files that are used frequently, // but are changed
infrequently
#define
VC_EXTRALEAN // Exclude rarely-used
stuff
// from Windows headers
#include
<afxwin.h> // MFC core and
standard components
#include
<afxext.h> // MFC
extensions
#ifndef
_AFX_NO_AFXCMN_SUPPORT
#include
<afxcmn.h> // MFC support for
Windows 95
//
Common Controls
#endif //
_AFX_NO_AFXCMN_SUPPORT
Här väljs alltså de headerfiler vi behöver för ett projekt så som vi beställde det av programgeneratorn. Denna #include behövs i alla projektets .cpp-filer. Vi kommer efter hand också att se hur man måste ta med ‘främmande’ headerfiler för att de olika delarna ska känna till varandras klassbeskrivningar.
2. Funktionen InitInstance() ser ut så här (dubbelklicka på funktionsnamnet i ClassView):
///////////////////////////////////////////////////////////////
// CHelloApp initialization
BOOL
CHelloApp::InitInstance()
{
// Standard initialization
// If you are not using these features and wish to reduce
// the size of your final executable, you should remove
// from the following the specific initialization routines
// you do not need.
LoadStdProfileSettings(); // Load standard INI file
// options (including MRU)
// Register the
application's document templates. Document
// templates serve as the connection between documents,
// frame windows and views.
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CHelloDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CHelloView));
AddDocTemplate(pDocTemplate);
// Parse command line for standard shell commands, DDE,
// file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
return TRUE;
}
Enligt hjälpen används LoadStdProfileSettings() för att hämta inställningar till förhandsgranska och senast använda filer. Man kan ange en parameter som avser hur många filer som MRU-listan ska kunna innehålla, men argumentet är förvalt till ett värde som fördefinierats i _AFX_MRU_COUNT. Denna definition kunde vi påverka i applikationsguiden, MFC AppWizard, steg 4.
3. Meddelandehantering hanteras via Afx i
denna och andra filer. Man har alltså delat upp meddelandehanteringen i de
olika objekten, så att man kan få t.ex. ett menykommando att aktivera en
funktion i ett viss objekt, MainFrame, dokument eller vy. Titta i de olika
filerna och se hur AppWizard lagt in kommentarer för ClassWizard i alla
klasserna. Lägg märke till att man inte ska ändra i denna kod eller i
kommentarerna. Vill vi lägga in Afx-meddelanden manuellt gör vi det utanför
kommentaravsnitten.
4. Enligt hjälpen behövs ytterligare två makron, BEGIN_MESSAGE_MAP och
END_MESSAGE_MAP, vilka vi hittar i .cpp-filerna. I hjälpen finns också hänvisning
till ytterligare information: Working with Messages and Commands. Läs
mer på egen hand allt efter intresse, är det för mycket nu, vet du hur du ska
kunna hitta detta senare, när du behöver veta mer. Använd
‘synkroniseringsknappen’ längst till vänster i hjälpens verktygsfält, så ser du
i vilken bok allt detta står:
? InitiInstance(),
DECLARE_MESSAGE_MAP - Working with Messages and Commands, orientera dig i
avsnittet MFC 4.0 i boken Visual C++ Books.
AFX meddelandehantering.
Vi har redan lagt in ett menyval kopplat till en funktion i MainFrame. Denna funktion visar en MessageBox() som hälsar oss med texten ‘Hello World!’. Nu ska vi se hur man kan lägga in meddelanden i andra objekt. Hur valde vi vilket objekt funktionen skulle hamna i? Jo, vi öppnade filen MainFrm.cpp (t.ex. genom att dubbelklicka på någon medlemsfunktion tillhörande CMainFrame i ClassView), och valde objektet ‘ID_FILE_HELLO’ och meddelandet ‘COMMAND’ i de två listrutor som finns i redigeringsfönstrets verktygsfält. (Se föregående kapitel.)
Detta verktygsfält kommer automatiskt fram om man redigerar en fil innehållande fullständig deklaration av de medlemsfunktioner som ingår i en klass som finns med i ClassView. En sådan fil kallas ‘implementeringsfil’. Verktygsfältet innehåller även en knapp märkt ‘.h’, vilken öppnar motsvarande klass headerfil, den fil som innehåller klasshuvudet eller klassbeskrivningen. (Har du tänkt på att man kan ha flera filer öppna i redigeringsfönstret samtidigt, och växla mellan dem m.h.a. <Ctrl + Tab>? Redigeraren har således MDI, Multiple Document Interface.)
Övningsuppgift:
· Lägg på motsvarande sätt till ett menyval ‘Dokument’ direkt under menyvalet ‘Hello’. ID ska bli ‘ID_FILE_DOKUMENT’.
· Koppla menyvalets kommando till en funktion i klassen CHelloDoc på samma sätt som vi gjorde i kapitel 1.
· Lägg in en AfxMessageBox() i den nya funktionen. Texten ska vara ‘Detta är dokumentet!’.
· När du kompilerar kommer först resurserna att kompileras om. Lägg sedan speciellt märke till vilka filer som kompileras. Det är bara de filer som ändrats som kompileras.
· Öppna de filer som kompilerades om och försök hitta de ändringar som ClassWizard lagt in. Ändringarna kan finnas i såväl headerfil som implementeringsfil.
· Om du inte hittar alla ändringar, skriv ut filerna på papper. Du får en chans till i nästa uppgift, och då kan du jämföra med dessa utskrifter.
Övningsuppgift:
· Lägg på motsvarande sätt till ett menyval som aktiverar en funktion i ‘Vyn’ (CHelloView),. Funktionen ska skriva ut meddelandet ‘Detta är vyn!’.
· Observera kompileringen igen, leta upp ändringarna och använd eventuella utskrifter från föregående exempel.
· Jämför med diskussionerna i föregående avsnitt.
Variablernas plats.
Vi har redan sett att ett objekt bör innehålla de variabler som beskriver objektet, de utgör objektets ‘attribut’. När vi nu har ett program som består av ett antal objekt, applikation, programfönster, dokument och vy, så måste vi ta ställning till vilka variabler som hör till vilka objekt. Vi måste tänka i termer som ‘vilket objekt hjälper denna variabel att beskriva?’, ‘vad behöver detta objekt för attribut?’ etc.
När vi senare lägger till nya objekt, t.ex. en ny dialogruta, kan man kanske tänka att avgörandet är lätt, alla nya variabler hör naturligtvis till det nya objektet. Vi kommer dock att se att det inte är helt självklart att det är så i ett kommande kapitel.
Vi börjar från början. Klassen CHelloApp innehåller från början allt som behövs för att initiera och köra programmet. Det är vanligt att man inte lägger några nya variabler alls här, även om det kan finnas situationer då det kan vara påkallat. För stunden lämnar vi den som den är.
CMainFrame är klassen för vårt programfönster. Här sker meddelandehantering etc. Om man inte använder arbetsytan (engelska client area) till något speciellt, utan bara som ett utrymme där dokumentet visas, så behöver man inte heller här lägga till några variabler. Vi kommer senare att skapa ett specialfall där vi betraktar programfönstret som en dialogruta, men då bakar vi in en speciell klass för detta, och deklarerar i alla fall inte något speciellt i CMainFrame.
Nu är det bara två klasser kvar, dokumentet och vyn. Som hörs på namnet är dokumentet avsett att innehålla vårt dokument. Det vi bearbetar i programmet lägger vi in här. Skriver vi en ordbehandlare bör dokumentet användas för att administrera vår text. Det finns speciella funktioner i klassen CDocument (vilken vår dokumentklass CHelloDoc ärver av) för dokumenthantering, t.ex. spara på disk och hämta från disk.
Här finns också möjlighet att extrahera ett eventuellt filnamn från kommandosträngen. Detta innebär att man kan associera filer med visst filnamnstillägg med sitt program. När man sedan dubbelklickar på ett sådant dokument kommer programmet att startas, och dokumentet kan hitta filnamnet för att sedan öppna filen och läsa in den i dokumentet.
? Titta i avsnittet
CDocument Class Members, och skaffa dig en allmän uppfattning om hur informationen är indelad, och vilka
medlemmar som finns.
Till sist har vi vyn, CHelloView, vilken vi låtit ärva av CView (förvalt). Denna klass hanterar det grafiska gränssnittet, d.v.s. det vi ser på skärmen. Ofta innehåller vyn en delmängd av dokumentet, t.ex. en post i en lista, där hela listan finns i dokumentklassen, och den post som syns på skärmen finns i vyn. Vanligt är dock även att man använder variabler direkt från dokumentet.
I vyn finns funktioner för visning på skärmen. De finns listade i hjälpen under ‘CView Class Members’, och de är många. Slå upp dem i hjälpen för en allmän orientering.
I CDocument och CView finns funktioner för korsreferens mellan de olika klasserna (egentligen mellan de objekt som skapas med de ärvande klasserna som mall). De fungerar så att de ger adressen till det andra objektet. Behöver man nå någon publik medlem i dokumentet från vyn, så behöver man adressen till vyn. Den kan man få m.h.a. funktionen GetDocument(). Slå upp den i hjälpen.
Det finns motsvarighet i CDocument, men man kan ha mer än en vy som hör ihop med ett dokument, därför finns två funktioner: GetFirstViewPosition() och GetNextView(). Slå upp även dessa i hjälpen.
Hur kan man ha mer än en vy? Det finns två fall: Man kan ha ett MDI, Multiple Document Interface, d.v.s. ett program som kan öppna mer än ett dokument. Hit hör Developer Studios redigerare. Varje textfil som är öppen för redigering har sin egen vy, oavsett om den syns på skärmen eller ej. Det motiverar dock inte att man har dessa två funktioner eftersom vi fortfarande har förhållandet 1:1 mellan dokument och vyer. Dock fanns det två fall.
Samma redigerare duger för att demonstrera det andra fallet: när man har s.k. ‘split view’. Titta noga på rullningslisterna. I början finns en smal knapp mellan pilknappen och det fält där handtaget kan manipuleras. Denna smala knapp kan man dra ut i nämnda fält, varvid vyn delas, så att man kan se samma dokument i två eller fyra vyer. Genom att manipulera de vanliga handtagen kan man i dessa vyer se olika delar av dokumentet. I detta fall har vi flera olika vyer som hör till samma dokument, och därför behöver vi de två ovannämnda funktionerna.
Så här ser knapparna ut:
Man kan antingen dra respektive knapp till önskat läge eller dubbelklicka på den, så delas vyn i två lika delar. Så här kan det se ut om vi vill betrakta de två funktionerna OnDraw() och GetDocument() i CHelloView (HelloView.cpp):
Vi ska nu testa att använda GetDocument(). Som synes ovan returnerar den en pekare av typen CHelloDoc* (i vårt exempel). Det betyder att vi kan nå en publik medlem i dokumentet m.h.a. ‘->’. Om vi t.ex. har en variabel m_iVar i dokumentet kan vi i en funktion i vyn deklarera en pekare avsedd att peka mot CHelloDoc, och sedan använda den för att komma åt variabeln:
// Detta sker i en funktion i CHelloView:
CHelloDoc *pDoc
= GetDocument();
pDoc->m_iVar = 1; // Tilldela dokumentets variabel värdet 1.
Detta kräver naturligtvis att CHelloDoc är känt när CView kompileras. De kompileras inte samtidigt, utan en i taget. Därför finns kompileringsdirektivet #include "HelloDoc.h" med i HelloView.cpp, tillsammans med #include "HelloView.h" och #include "Hello.h".
För att testa variabeln lägger vi upp en ny meny som har menyval för att sätta vissa fasta värden, samt ett för att rapportera värdet via en MessageBox(). Observera att MessageBox() inte understödjer intelligent stränghantering, så vi gör i stället en switch som använder värdet i variabeln för att skriva ut en av flera meddelanderutor med olika fasta texter som talar om vilket värde variabeln har, t.ex:
MessageBox(”m_iVar innehåller värdet 1.”);
Övningsuppgift:
· Skapa en publik variabel av typen int i CHelloDoc. Den ska heta m_iVar.
· Skapa en ny meny som heter ‘Variabel’.
· Lägg in menyvalen ‘Ett’, ‘Två’, ‘Tre’ och ‘Visa Aktuellt Värde’.
· Koppla de fyra menykommandona till funktioner i CHelloView.
· I alla fyra funktionerna skapas en lokalt deklarerad pekare pDoc som tilldelas adressen till CHelloDoc (eller egentligen objektet) m.h.a. funktionen GetDocument():
· Skriv i de tre första funktionerna in kod som ger variabeln m_iVar i CHelloDoc värdet 1, 2 respektive 3.
· Skriv i den sista en switch som testar på variabeln m_iVar i CHelloDoc och i tre olika fall, 1, 2 respektive 3, skriver ut meddelanderutor med texten ‘m_iVar innehåller värdet 1.’, ‘m_iVar innehåller värdet 2.’ Respektive ‘m_iVar innehåller värdet 3.’.
· Om man inte har valt något värde innan man väljer menykommandot ‘Visa Aktuellt Värde’ ska meddelandet ‘V.G. välj ett värde först!’ visas.
· För att detta ska kunna ske ordnat bör vi initiera m_iVar. Det kan lämpligen göras i dokumentets konstruktor.
· Så här ska det se ut om man valt först ‘Två’ och sedan ‘Visa Aktuellt Värde’:
Observera att du inte behöver deklarera variabeln på vanligt sätt. Prova gärna att klicka med höger mustangent på namnet CHelloDoc i ClassView, och välj Add Variable… i menyn som dyker upp:
På motsvarande sätt kan man lägga till medlemsmetoder.
Övningsuppgift:
· Flytta variabeln till vyn i stället.
· Initiering kan ske i vyns konstruktor.
· Tag bort pekarna ‘pDoc’ ur alla fyra ‘Command handler functions’ du själv skrivit.
· Kompilera, länka och testa så att det fortfarande fungerar.
Det är inte alltid självklart att man initierar allting i vyns konstruktor. Denna körs innan allt som tillhör det grafiska är klart. Variabler kan man alltid initiera där, men om man vill anropa funktioner som initierar t.ex. fönstrets storlek, så kommer dessa att anropas automatiskt efter konstruktorn, och då förlorar vårt anrop sin effekt. Av den anledningen finns en funktion i CView som vi kan byta ut (override), vilken heter OnInitialUpdate(). Slå upp den i hjälpen.
Om man har vyns implementeringsfil öppen för redigering, i vårt fall HelloView.cpp, kan man lätt be ClassWizard lägga till utbyte av OnInitialUpdate(). I listrutan ‘CHelloView Object IDs’ väljer vi ‘CHelloView’, och i listrutan ‘Messages’ väljer vi ‘OnInitialUpdate’. Observera att de meddelanden som redan har hanteringsfunktioner är skrivna med fetstil. Om man väljer en av dessa så positioneras markören till funktionen. Om man väljer en som inte är skriven i fetstil, som i detta fall, skapas en ny funktion efter att programmeraren tillfrågats om det är det han vill göra:
Funktionen vi får innehåller anrop till basklassens motsvarighet. Därför är det viktigt att vi skriver in vår kod på rätt ställe. Kommentaren ‘// TODO…’ visar var vi bör fylla i vår kod för att undvika konflikter. Om vi bara använder OnInitialUpdate() till att initiera m_iVar har det egentligen ingen betydelse om det sker före eller efter anrop till CView::OnInitialUpdate(). Så här ser det ut:
void
CHelloView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Add your specialized code here
and/or call the base class
}
Övningsuppgift:
· Lägg till OnInitialUpdate() enligt ovan.
· Flytta initieringen av m_iVar till OnInitialUpdate().
· Kompilera, länka och testa så att allt fortfarande fungerar.
ClassWizard.
[View - ClassWizard...] Ctrl + W
Vi har nu använt ClassWizard till diverse olika saker. Låt oss ta en titt på dess huvuddialogruta. Den har fem flikar. Den första fliken behandlar meddelandehantering:
Här finns de listrutor vi redan sett i redigeraren, en för objekt och en för meddelande. Man kan byta klass i listrutan ‘Class name’. Det finns en listruta för klassens metoder, och man kan ‘hoppa’ till en funktion genom att dubbelklicka på dess namn, eller markera den och klicka på ‘Edit Code’. En markerad metod kan även tas bort m.h.a. ‘Delete Function’. Man ombeds då att själv ta bort själva funktionskoden, endast Afx-hanteringen tas bort av ClassWizard.
Knappen ‘Add Function’ lägger till en funktion, och ‘Add Class’ lägger till en klass. Det är lönt att prova ‘Help’-knappen i denna dialogruta, den ger en kort beskrivning av vad man kan göra med de kontroller som finns i respektive flik.
Behöver man mer djupgående förklaring måste man dock leta upp motsvarande hjälpavsnitt på annat sätt.
Nästa flik behandlar de variabler som kopplas till kontroller i klassen. Vi har ännu inga sådana, och kommer att prova det i nästa kapitel, vilket handlar om dialogrutor.
Object Linking and Embedding (OLE) behandlas i två flikar, ‘OLE Automation’ och ‘OLE Events’. Vi tar inte upp OLE här. Det ingår i självstudien ‘Scribble’ som vi ska gå igenom efter detta häfte.
Den sista fliken visar information om de olika klasserna:
Här kan man se vilka filer klassen finns deklarerad i, vilken klass vi ärvt från, och eventuell resurs, om klassen tillhör en sådan (t.ex. dialogrutor och menyer).
Observera att många av de tjänster vi kan få via ClassWizards dialogruta med sina fem flikar, kan man nå på andra sätt. Det är dock fortfarande ClassWizard som sköter om det hela.
? Prova Help-knappen i de olika flikarna.