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.

MFC Anwendungs-Assistent

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.

Die Anwendungsart

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.

Datenbankunterstützung durch die MFC

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.

COM Unterstützung

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

Fensterlayout

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.

Code- und Linkeroptionen

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

Die Ziellinie

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:

Die erstellten MFC-Klassen

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.

Rahmenfenster mit untergeordnetem Ausgabebereich

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 verwendeten Ressourcen

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.

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 Aufruf-Reihenfolge

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.



Copyright © 2004

Senden Sie Emails mit Fragen oder Kommentaren zu dieser Website an: mailto:info@cpp-tutor.de
 Wolfgang Schröder, Lerchenweg 23, D-72805 Lichtenstein. Tel: +49 7129 6470