MFC Rahmenprogramm
Ab dieser Lektion werden wir zwei Neuerungen einführen: zum einen werden wir alle
weiteren Beispiele mit den Anwendungs-Assistenten erstellen und zum anderen werden neue
Klassen und Methoden nicht mehr so ausführlich beschrieben wie bisher. Sie sollten ab
jetzt ausgiebig die Online-Hilfe benutzen beim Erstellen der Beispiele.
Sehen wir uns nun den Aufbau eines vom Anwendungs-Assistenten erstellten Programms
einmal an.
 |
Starten Sie zunächst das Visual Studio. Wählen Sie dann aus dem
Dateimenü den Eintrag Neu... aus. Wählen Sie dann als Projekttyp nun
MFC-Anwendungs-Assistent (exe) aus und geben dem Projekt den Namen MFCMsg.
Klicken Sie dann den OK Button an.
 |
Als nächstes müssen Sie sich durch sechs weitere Dialoge durcharbeiten damit der
Anwendungs-Assistent den Rahmen Ihrer Anwendung erstellen kann. Fangen wir also an.
 |
Im ersten Schritt wählen Sie als Anwendung eine SDI-Anwendung
aus und nehmen die Markierung bei Unterstützung der Dokument-/Ansicht-Architektur
weg. Für die Ressourcensprache lassen Sie Deutsch eingestellt. Klicken Sie aber
noch nicht den Weiter Button an! Sie erhalten gleich noch eine kleine Erklärung
zu den eingestellten Punkten.
 |
SDI-Anwendungen erlauben nur die Darstellung eines Dokuments gleichzeitig
während MDI-Anwendungen die Darstellung von mehreren Dokumenten gleichzeitig in
einer Anwendung zulassen. Ein typischer Fall für eine SDI-Anwendung ist das Programm
NOTEPAD. Sie können mit dem NOTEPAD immer nur ein Dokument zur gleichen Zeit bearbeiten.
Das Programm WINWORD dagegen ist ein typischer Vertreter einer MDI-Anwendung. Mit WINWORD
können Sie beliebig viele Dokumente gleichzeitig verarbeiten. Die letzte einstellbare
Programmart Dialogfeldbasierend erstellt eine Anwendung, die nur aus einem
einzelnen Dialog besteht.
Danach folgt im Dialog die Auswahl ob die zu erstellende Anwendung die Dokument/Ansicht
Architektur (Doc/View) unterstützen soll. Dem Doc/View Modell ist ein gesondertes
Kapitel gewidmet weshalb wir diese Unterstützung vorerst ausschalten.
Die letzte Einstellung in obigen Dialog bestimmt die in den Ressourcen eingesetzte
Sprache. So können Sie auch mit einer deutschen Version des Visual Studios Anwendungen
erstellen in den die Standard-Menütexte in englischer Sprache dargestellt werden.
 |
Verlassen Sie jetzt den mit Schritt 1 bezeichneten Dialog über
den Button Weiter. Im nächsten Dialog können Sie angeben ob der Assistent für
Sie eine Datenbankunterstützung mit ins Rahmenprogramm aufnehmen soll.

Hier sollte die Auswahl Keine selektiert sein. Auch den
Datenbanken wird ein gesondertes Kapitel gewidmet. Klicken Sie den Button Weiter
an um zum nächsten Dialog zu gelangen.
Der nächste Dialog erlaubt bei Doc/View Anwendungen die Unterstützung
von Verbunddokumenten. Bei unserer einfachen Anwendung, ohne Doc/View Modell, sind die
meisten der Auswahlfelder gesperrt. Lediglich die Unterstützung von
ActiveX-Steuerelementen ist eingeschaltet. Schalten Sie deren Unterstützung hier nun aus.
Auch den ActiveX-Steuerelementen wird ein gesondertes Kapitel gewidmet werden. Klicken Sie
erneut den Weiter Button an.

Der nächste Dialog legt das grundsätzliche Layout des Fensters fest.

|
Da wir keine der hier aufgeführten Merkmale bisher behandelt haben, könnten wir
eigentlich alle Merkmale deaktivieren. Leider besitzt die Erstellung einer nicht Doc/View
basierenden Anwendung noch einen 'kleinen' Fehler, wenigstens bis zum Service Pack 2.
 |
Wenn Sie eine nicht Doc/View basierende Anwendung ohne Symbolleiste und ohne
Statusleiste über den Anwendungs-Assistenten erstellen, so ist das Programm nicht
lauffähig wenn Sie nicht mindestens das Service Pack 3 installiert haben. Fügen Sie Ihrer Anwendung immer eine Symbolleiste und/oder Statusleiste
hinzu wenn kein Service Pack 3 installiert ist (was Sie aber unbedingt
dann nachholten sollten). Bei Doc/View basierenden Anwendungen funktioniert die Erstellung des Rahmenprogramms
auch dann fehlerfrei wenn keine der beiden Merkmale ausgewählt wird. |
Kehren wir nun wieder zu unserem Beispiel zurück.
 |
Nehmen Sie die oben angegebenen Einstellung vor. Unser Beispiel besitzt,
zur Vermeidung des erwähnten Fehlers, eine Statusleiste die aber zur Zeit nicht weiter
betrachtet wird. Über den Button Weitere Optionen... könnten Sie das Layout des
Hauptfensters noch weiter anpassen (was wir hier aber nicht tun wollen). Klicken Sie jetzt
den Button Weiter an um den nachfolgenden Dialog zu erhalten.

Die Einstellungen in diesem Dialog sind selbsterklärend. Lassen Sie alle
Vorgaben stehen und gehen dann weiter zum nächsten Dialog.

Wir haben die Ziellinie erreicht. Im letzten Dialog können Sie für die
einzelnen, vom Assistenten zu erstellenden Klassen deren Namen sowie bei einigen die Namen
der Header- und Implementierungsdatei (das ist die Datei mit dem eigentlichen Code)
ändern. Belassen Sie für das Beispiel alles auf den Vorgabewerten und klicken Sie den
Button Fertigstellen an.
DAS WAR'S!
Na ja, nicht ganz. Zum Schluss zeigt der Assistent in einem Dialog
nochmals alle vorgenommen Einstellungen an. Klicken Sie zum Erzeugen des Rahmenprogramms
den OK Button an.
Übersetzen und starten Sie nun einmal das Beispiel. Schließlich wollen
Sie auch sehen was der Assistent für Sie so alles erledigt hat. |
Sie haben soeben eine vollständige Anwendung erstellt, einschließlich Menüleiste,
Statuszeile und Info-Dialog. Diesen Dialog erreichen Sie übrigens wenn Sie im Menü das
Fragezeichen anklicken. Tolle Sache, nur durch Klicken zum eigenen Fenster!
Nachdem der Assistent seine Arbeit verrichtet hat wollen wir uns nun einmal ansehen,
was so alles erstellt wurde.
 |
Wechseln Sie zur Klassenansicht und öffnen Sie dann alle
Klassenbäume. Sie sollten jetzt folgende Darstellung erhalten:
 |
Wie Sie der Klassenansicht entnehmen können, hat der Assistent zu Ihrem Projekt
vier Klassen und ein globales Objekt hinzugefügt:
- CAboutDlg: Diese Klasse enthält den Dialog der anzeigt wird wenn im Menü das
Fragezeichen angeklickt wird.
- CChildView: Diese von CWnd abgeleitete Klasse ist der Ausgabebereich
der Anwendung.
- CMainFrame: Rahmenfenster der Anwendung mit dem Menü, abgeleitet aus CFrameWnd.
- CMFCMsgApp: Die Anwendungsklasse, abgeleitet aus CWinApp.
- theApp: Das globale Anwendungsobjekt.
Die Klassen CMFCMsgApp und CMainFrame sowie das globale
Anwendungsobjekt kennen Sie schon aus der vorherigen Lektion. Neu hinzugekommen ist zum
einen die Klasse CAboutDlg für den Info-Dialog sowie die Klasse CChildView
für den Ausgabebereich. Mit den Dialogen werden wir uns in einem getrennten Kapitel
später noch beschäftigen. Viel interessanter ist im Augenblick die Sache mit der Klasse CChildView.
Die MFC trennt streng zwischen dem Rahmenfenster, das für die Menübearbeitung, die
Statusleiste und einer eventuellen Symbolleiste zuständig ist, sowie zwischen dem
Ausgabebereich der Anwendung. Der Ausgabebereich ist ein dem Rahmenfenster untergeordnetes
Fenster und verdeckt in der Regel die Client-Area des Rahmenfensters vollständig.

Mehr zu untergeordneten Fenstern erfahren Sie in der übernächsten Lektion. Beachten
Sie bitte, dass die Trennung zwischen Rahmenfenster und Ausgabebereich sich unmittelbar
auf die Verarbeitung von Nachrichten auswirkt. Wird z.B. das Fenster durch Ziehen am
Rahmen vergrößert, so erhält das Rahmenfenster zunächst eine entsprechende WM_SIZE
Nachricht zugesandt. Die MFC leitet jedoch diese Nachricht standardmäßig auch an das
untergeordnete Fenster weiter, so dass dieses ebenfalls darauf reagieren kann. Auch der
umgekehrte Nachrichtenweg ist jedoch möglich. Klickt z.B. der Anwender mit der Maus in
den Ausgabebereich, so erhält zuerst das untergeordnete Fenster die Nachricht zugesandt
und leitet diese dann bei Bedarf an das Rahmenfenster weiter. Aber mehr zur
Nachrichtenbearbeitung innerhalb der MFC gleich in dieser Lektion.
Sehen wir uns zunächst grob den vom Anwendungs-Assistenten erzeugten Code an. Beginnen
wir mit der Methode InitInstance(...) des Anwendungsobjektes.
BOOL CMFCMsgApp::InitInstance()
{
....
#ifdef _AFXDLL
Enable3dControls();
#else
Enable3dControlsStatic();
#endif
....
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
....
CMainFrame* pFrame = new CMainFrame;
m_pMainWnd = pFrame;
....
pFrame->LoadFrame(IDR_MAINFRAME,
WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL,
NULL);
....
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
} |
Zuerst wird mittels der Methoden Enable3dxxx(...) die DLL CTL3D32.DLL
geladen. Dadurch erhalten die Steuerelemente ihren 3D Look. Die nächste Funktion SetRegistryKey(...)
bestimmt den von der Anwendung verwendet Registrierungsdatenbank-Eintrag. Da unser
Beispiel keinen Eintrag in der Registrierungsdatenbank vornimmt, überspringen wir hier
diese Funktion. Sie wird uns bei der Behandlung des Doc/View Modells wieder begegnen.
Danach wird das Fensterobjekt für das Hauptfenster erzeugt und der Zeiger darauf
in m_pMainWnd abgespeichert. Beachten Sie bitte, dass hier ein Objekt unserer Fensterklasse CMainFrame
erstellt wird und kein CFrameWnd-Objekt. Die Erstellung des Hauptfensters erfolgt
anschließend mittels der CFrameWnd Methode LoadFrame(...). Im Gegensatz
zur bisherigen Methode Create(...) lädt LoadFrame(...) zusätzlich die
mit dem Fenster verbunden Ressourcen wie z.B. das Menü. Welche Ressourcen mit dem Fenster
verbunden werden wird über den ersten Parameter der Methode definiert.
 |
Wechseln Sie jetzt einmal in die Ressourcen-Ansicht und öffnen dort alle
Ressource-Typen. Sie sollten dann ein Anzeige in folgender Form erhalten:
 |
Die Ressourcen-Typen Accelerator, Icon und Menu enthalten alle Ressourcen mit
der ID IDR_MAINFRAME. Diese ID wurde ebenfalls als erster Parameter beim Aufruf von LoadFrame(...)
angegeben. Nur die String Table weicht hier etwas ab. Sie enthält lediglich einen String mit
dieser ID. Und dieser String wir als Fenstertitel verwendet. Aber mehr zu den Ressourcen
später im Kurs. Nach der Angabe der Ressourcen-ID folgt beim Aufruf der LoadFrame(...)
Methode der Stil des Rahmenfensters. Und auch hier hat die MFC eine Erweiterung der
bisherigen Fensterstile (Präfix WS_xxx) eingeführt. Der MFC-interne Fensterstil
FWS_ADDTOTITLE ist dafür verantwortlich, dass beim Laden einer Datei der Dateiname in der
Titelleiste mit angegeben wird. Aber auch dies werden wir erst später beobachten können,
nämlich wenn wir Dateien laden. Wenden wir uns jetzt den beiden letzten Aufrufen ShowWindow(...)
und UpdateWindow(...) zu. Die Methode ShowWindow(...) kennen Sie ja
schon aus der vorherigen Lektion. Sie bringt das Fenster letztendlich zur
Anzeige. Und UpdateWindow(...) veranlasst das sofortige Neuzeichnen des
Fensterinhaltes, unter Umgehung der Nachrichtenschlange. Damit ist die Methode InitInstance(...)
komplett. Wenden wir uns nun der Klasse CMainFrame des Rahmenfensters zu.
Die Fensterklasse CMainFrame des Rahmenfenster enthält unter anderem auch
zwei Membervariable. Die erste Variable m_wndStatusBar enthält das Objekt für
die Darstellung der Statusleiste und die zweite Variable m_wndView das Objekt
für den Ausgabebereich der Anwendung. Die Behandlung der Statusleiste stellen wir
zunächst noch etwas zurück. Und zum Ausgabebereich der Anwendung kommen wir gleich noch.
Von den 'vielen' Methoden der Klasse CMainFrame wollen wir uns hier nur zwei genauer
ansehen. Die erste Methode PreCreateWindow(...) kennen Sie ja schon aus der
vorherigen Lektion. Diese Methode wird noch vor der Erstellung des Fensters durch den
MFC-Rahmen aufgerufen.
BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// ZU ERLEDIGEN: Ändern Sie hier die Fensterklasse oder das
Erscheinungsbild,
// indem Sie CREATESTRUCT cs modifizieren.
cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
cs.lpszClass = AfxRegisterWndClass(0);
return TRUE;
} |
Innerhalb dieser Methode wird zunächst der Stil des Rahmenfensters gesetzt und dann
die WINDOWS Fensterklasse für das Rahmenfenster registriert. Wenn Sie in dieser Methode
die Einstellungen des Fenster verändern wollen so beachten Sie jedoch folgendes:
 |
In der PreCreateMethode(...) des Rahmenfensters können nur die Einstellungen
geändert werden, die sich auch auf das Rahmenfenster beziehen wie z.B. die Größe und
Position des Fensters. Nicht einstellen können Sie hier z.B. die Hintergrundfarbe des
Fensters da diese eine Eigenschaft des Ausgabebereichs ist. |
Die zweite interessante Methode ist die Methode OnCreate(...). Diese Methode
wird bei der Fenstererstellung durch WINDOWS als Reaktion auf eine WM_CREATE Nachricht
aufgerufen. Zum Zeitpunkt des Aufrufs dieser Methode ist das Fenster bereits erstellt aber
noch nicht sichtbar.
int CMainFrame::OnCreate(LPCREATESTRUCT
lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
// create a view to occupy the client area of the frame
if (!m_wndView.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST,
NULL))
{
TRACE0("Failed to create view
window\n");
return -1;
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Statusleiste konnte nicht erstellt
werden\n");
return -1; // Fehler bei Erstellung
}
return 0;
} |
In der durch den Anwendungs-Assistenten erstellten Methode wird zuerst die
Basisklassen-Methode aufgerufen und danach das Fenster für den Ausgabebereich der
Anwendung erstellt. Zum Schluss wird noch die Statusleiste erzeugt.
Wenden wir uns jetzt dem Ausgabebereich der Anwendung zu. Für die Ausgabe verwendet
das Rahmenprogramm der MFC ein eigenes Fenster. Diese Fenster wird durch das Objekt CChildView
gekapselt und CChildView wiederum ist von CWnd abgeleitet. Die Größe
dieses Fensters wird von der MFC immer so angepasst, dass es genau in die Client-Area des
Rahmenfenster passt. Sie werden also unter normalen Umständen niemals die Client-Area des
Rahmenfensters zu Gesicht bekommen. Außer einem Konstruktor und einem Destruktor hat der
Anwendungs-Assistent dem Fensterobjekt noch zwei Methoden hinzugefügt: die Methode PreCreateWindow(...)
und die Methode OnPaint(...). Mit der zuletzt genannten Methode OnPaint(...)
werden wir uns im nächsten Kapitel noch näher befassen. Sie ist für die Darstellung der
Daten verantwortlich. Viel interessanter ist im Augenblick die Methode PreCreateWindow(...),
die Sie ja schon vom Rahmenfenster her kennen.
BOOL
CChildView::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CWnd::PreCreateWindow(cs))
return FALSE;
cs.dwExStyle |= WS_EX_CLIENTEDGE;
cs.style &= ~WS_BORDER;
cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
::LoadCursor(NULL, IDC_ARROW),
HBRUSH(COLOR_WINDOW+1), NULL);
return TRUE;
} |
Nach dem Festlegen des Fensterstils wird für dieses, dem Rahmenfenster untergeordnete,
Fenster eine eigene Fensterklasse registriert. Die hierfür eingesetzte Methode AfxRegisterWndClass(...)
kennen Sie ja schon aus der vorherigen Lektion. Standardmäßig wir eine WINDOWS
Fensterklasse für das Fenster erstellt die Doppelklick-Nachrichten verarbeitet, den
Fensterinhalt beim Ändern der Fenstergröße immer neu darstellt, als Cursor den
Standard-Cursor und als Hintergrundfarbe die in der Systemsteuerung eingestellte Farbe
verwendet. Die hier erzeugte WINDOWS Fensterklasse benötigt kein eigenes Icon da als
Fenstersymbol das Icon des übergeordneten Rahmenfensters benutzt wird.
 |
Versuchen Sie nicht durch Überschreiben der Elemente cx/cy der Struktur
CREATESTRUCT cs die Größe des Ausgabebereichs anzupassen. Die MFC wird dies
ignorieren und den Ausgabebereich immer dem Rahmenfenster anpassen! Wie Sie
das Rahmenfenster an den Ausgabebereich, auch Ansicht oder View genannt,
anpassen können, das erfahren Sie in den Tipps&Tricks zu dieser
Lektion. |
So, damit können wir zur Übung in dieser Lektion übergehen. Das wichtigste haben Sie
nun erfahren. Auf den Rest werden wir im Verlaufe des Kurses noch zu sprechen kommen. Aber
eines nach dem anderen!
 |
Passen Sie nun die erstellten Fenster so an, dass das Rahmenfenster
zentriert und wieder halb so groß wie der Desktop dargestellt wird. Der
Fensterhintergrund des Ausgabebereichs soll wieder hellgrau dargestellt werden und als
Cursor soll der Cursor IDC_UPARROW angezeigt werden.
 |
Bevor Sie an dieser Übung verzweifeln kann ich Ihnen auch die Lösung anzeigen. |
Lösung zur Anpassung der Anwendung
BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// ZU ERLEDIGEN: Ändern Sie hier die Fensterklasse oder ....
// Groesse des Desktops auslesen
int nWidth =
::GetSystemMetrics(SM_CXSCREEN);
int nHeight = ::GetSystemMetrics(SM_CYSCREEN);
// Rahmenfenstergroesse und -position
anpassen
cs.cx = nWidth>>1;
cs.cy = nHeight>>1;
cs.x = nWidth>>2;
cs.y = nHeight>>2;
cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
cs.lpszClass = AfxRegisterWndClass(0);
return TRUE;
} |
Zuerst wird die Größe und Position des Rahmenfensters entsprechend
den Vorgaben angepasst.
BOOL
CChildView::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CWnd::PreCreateWindow(cs))
return FALSE;
cs.dwExStyle |= WS_EX_CLIENTEDGE;
cs.style &= ~WS_BORDER;
cs.lpszClass =
AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
::LoadCursor(NULL, IDC_UPARROW),
HBRUSH(::GetStockObject(LTGRAY_BRUSH)), NULL);
return TRUE;
} |
Und anschließend die Eigenschaften des Ausgabebereichs.
Beachten Sie bitte, dass Sie hier Anpassungen in beiden PreCreateWindow(...)
Methoden vornehmen müssen. Wie im Kurs bereits gesagt wird die Größe und Position in
der Regel über das Rahmenfenster eingestellt und die Eigenschaften des Ausgabebereichs
über dessen WINDOWS Fensterklasse.
Ende der Lösung
|
|
Sehen wir uns zum Schluss dieser Lektion nochmals an, welche Methoden in welcher
Reihenfolge aufgerufen werden:

Die Startup-Funktion AfxWinMain(...) wird direkt aus der MFC-internen WinMain(...)
Funktion heraus aufgerufen. AfxWinMain(...) ruft dann die InitInstance(...)
Methode des Anwendungsobjektes auf. Innerhalb von InitInstance(...) wird das
Rahmenfenster der Anwendung erstellt. Dies führt vor der Erstellung der Fensters zum
Aufruf der PreCreateWindow(...) Methode des Rahmenfensters. Beim Erstellen des
Rahmenfensters durch die MFC wird unter anderem die Nachricht WM_CREATE ausgelöst, was
wiederum zur Ausführung der Methode OnCreate(...) führt. Die Methode OnCreate(...)
erstellt nun ihrerseits das Fenster für den Ausgabebereich der Anwendung. Noch vor dessen
Erstellung wird die PreCreateWindow(...) Methode des Ausgabebereichs aufgerufen
um dessen Eigenschaften anpassen zu können. Nach der Erstellung des Ausgabebereichs wird
wieder in die InitInstance(...) Methode des Anwendungsobjektes zurückgekehrt.
Das Anwendungsobjekt ruft dann die Methode Run(...) auf um in die
Nachrichtenschleife einzutreten. Wird das Fenster geschlossen, so wird die
Nachrichtenschleife verlassen und am Ende noch ExitInstance(...) aufgerufen
um eventuelle Aufräumarbeiten durchführen zu können. Alles doch ganz logisch, oder?
Beenden wir damit diese Lektion und wenden uns in der nächsten Lektion der
Nachrichtenbearbeitung innerhalb der MFC zu.
|