Untergeordnete Fenster

Wenden wir uns jetzt den untergeordneten Fenstern zu und klären zunächst die Frage:  was sind eigentlich untergeordnete Fenster? Die beiden letzten Beispiel bestanden immer aus einem Rahmenfenster und einem eigenem Fenster für den Ausgabebereich. Und dieses Fenster für den Ausgabebereich war dem Rahmenfenster untergeordnet. Folgende drei wichtige Eigenschaften kennzeichnen ein untergeordnetes Fenster:

  • Das untergeordnete Fenster wird automatisch zerstört wenn das übergeordnete Fenster zerstört wird.
  • Das untergeordnete Fenster erscheint niemals in der Taskleiste.
  • Wird das übergeordnete Fenster zum Symbol verkleinert, so wird das untergeordnete Fenster verborgen.

Doch damit das Ganze nicht zu einfach wird gibt es zwei verschiedene Typen von untergeordneten Fenster:

Child-Windows: Sie können nur innerhalb der Client-Area des übergeordneten Fensters bewegt werden. Die Größe ihrer Client-Area ist nicht begrenzt, jedoch ist der maximal sichtbare Bereich auf die Größe der Client-Area des übergeordneten Fenster begrenzt.
Popup-Windows: Sie gehören zwar logisch zu einem übergeordneten Fenster, können jedoch auch außerhalb der Client-Area des übergeordneten Fensters platziert werden. Ihr maximal sichtbarer Bereich ist auf die Größe des Desktops begrenzt

Fangen wir mit dem einfacheren Fall an, dem Child-Window. Als Basisklasse für Child-Windows dient die MFC-Klasse CWnd. Wie Sie bereits erfahren haben enthält die Klasse CWnd die Grundfunktionalität eines jeden Fensters.

Und damit es uns nicht zu langweilig wird, fangen wir auch gleich mit dem Beispiel an.

Starten Sie das Visual Studio und erstellen Sie ein neues MFC Anwendungs-Assistent (exe) Projekt mit dem Namen ChildWnd. Im Schritt 1 stellen Sie als Anwendungsart wieder SDI ohne Doc/View Unterstützung ein. In den nachfolgenden Dialog deaktivieren Sie die ActiveX-Unterstützung und die Symbolleiste. Die Statusleiste müssen Sie aber aktiviert lassen sonst wird die Anwendung wegen des bereits erwähnten Fehlers im Assistent nicht laufen. Haben Sie das Service Pack 3 oder höher installiert können Sie auch die Statusleiste deaktivieren. Alle anderen Einstellung lassen Sie auf ihren Vorgaben. Nachdem der Anwendungs-Assistent seine Arbeit beendet hat, haben Sie die Ausgangsbasis für unsere weiteren Versuche erstellt.

Fügen Sie nun noch zur OnPaint(...) Methode des Ausgabebereichs die nachfolgende angegebene TRACE(...) Anweisung hinzu:

void CChildView::OnPaint()
{
    CPaintDC dc(this); // Gerätekontext zum Zeichnen
   
    // ZU ERLEDIGEN: Fügen Sie hier Ihren Code für die ....
   
    TRACE("CChildView::OnPaint()\n");
}

Sie können das Beispiel nun übersetzen und laufen lassen. Jedes Mal wenn Sie z.B. das Fenster vergrößern oder verkleinern erhalten Sie im Ausgabefenster des Debuggers eine entsprechende Ausgabe.

Fügen wir nun zuerst zum Projekt die Klasse für das Child-Window hinzu. Wie oben bereits erwähnt, werden Child-Windows von der MFC-Klasse CWnd abgeleitet. Und auch hier werden wir wieder die Hilfe das Klassen-Assistenten in Anspruch nehmen.

Falls noch nicht geschehen, öffnen Sie im Arbeitsbereich die Klassenansicht. Klicken Sie mit der rechten Maustaste den Eintrag ChildWnd Klassen an. Im dann angezeigten Kontextmenü wählen Sie den Eintrag Neue Klasse... aus worauf hin folgender Dialog angezeigt wird:

Ein MFC-Klasse hinzufügen

Im Feld Klassentyp können festlegen, welche Art von Klasse Sie erstellen wollen. Vom Klassen-Assistenten ist hier bereits MFC-Klasse voreingestellt. Als nächstes gilt es den Klassenname festzulegen. Wir werden unserer Child-Window Klasse den Namen CMyChild geben. Geben Sie diesen Namen im Feld Name ein. Beachten Sie bei der Eingabe bitte wie der Klassen-Assistent gleichzeitig den Dateinamen für die Implementierungsdatei der Klasse mit festlegt. Sie könnten den Dateinamen jederzeit abändern, in dem Sie den Button Ändern... anklicken. Zum Schluss muss noch die Basisklasse der neuen Klasse bestimmt werden. Öffnen Sie die Combobox und wählen Sie aus den verfügbaren Klassen die Klasse generic CWnd aus. Damit haben Sie festgelegen, dass die neue Klasse CMyChild public von der MFC-Klasse CWnd abgeleitet wird. Klicken Sie jetzt den OK Button an und der Klassen-Assistent erstellt die Klasse CMyChild und fügt dieser Klasse gleichzeitig einen Konstruktor und Destruktor hinzu.

Um die Eigenschaften des Child-Windows einstellen zu können wird im nächsten Schritt die Methode PreCreateWindow(...) hinzugefügt. Wie Sie sich sicher noch erinnern, wird diese Methode noch vor der Erstellung des Fensters aufgerufen. In dieser Methode können Sie z.B. die Größe und Position des Fensters einstellen aber auch eine eigene WINDOWS Fensterklasse für das Fenster erzeugen. Und genau dies müssen Sie hier nun tun.

Klicken Sie in der Klassenansicht die Klasse CMyChild mit der rechten Maustaste an und wählen aus dem Kontextmenü den Eintrag Virtuelle Funktion hinzufügen... aus. Folgender Dialog wird dann eingeblendet:

Virtuelle Funktionen hinzufügen

Dieser Dialog zeigt alle überschreibbaren Methoden der Child-Window Klasse an. Führen Sie im Feld Neue virtuelle Funktionen einen Doppelklick auf den Eintrag PreCreateWindow aus um diese Methode für die Child-Window Klasse zu überschreiben. Anschließend klicken Sie den OK Button an.

Erweitern Sie nun die vom Assistenten erstellte Methode wie folgt:

BOOL CMyChild::PreCreateWindow(CREATESTRUCT& cs)
{
    // TODO: Speziellen Code hier einfügen und/oder Basisklasse aufrufen

    // Index (Child-ID) abspeichern, wird beim Aufruf der
    // Create(...) Methode definiert

    m_nID = (int)cs.hMenu;
    // Fenster mit Titelzeile versehen
    cs.style |= WS_CAPTION;
    // Eigene WINDOWS Fensterklasse erstellen
    cs.lpszClass =
            AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
                                 ::LoadCursor(NULL, IDC_UPARROW));
    return CWnd::PreCreateWindow(cs);
}

Zuerst wird der Index des Child-Window in einer Membervariable m_nID abgelegt, die wir nachher gleich noch definieren werden. Dieser Index wird später beim Erstellen des Child-Window mit angegeben damit die Child-Windows untereinander unterschieden werden können. Dies brauchen Sie natürlich nur dann tun, wenn Sie mehrere Child-Windows von einer Klasse erstellen, so wir wir es nachher im Beispiel durchführen werden.

Als nächstes erhält das Child-Window den Fensterstil WS_CAPTION, d.h. wir fügen dem Child-Window eine Titelzeile hinzu. Mit Hilfe dieser Titelzeile kann das Fenster beliebig innerhalb der Client-Area des Rahmenfensters verschoben werden. Würden Sie keine Titelzeile hinzufügen, so könnte das Fenster selbstverständlich auch nicht per Mausklick verschoben werden!

Zum Schluss erstellen wir noch eine eigene WINDOWS Fensterklasse für das Child-Window. Das Child-Window verwendet den UPARROW-Cursor damit wir nachher besser erkennen können, wann sich der Cursor innerhalb der Client-Area des Child-Window befindet. Die restlichen Parameter der Funktion AfxRegisterWndClass(...) belassen wir zunächst auf ihrem Defaultwert.

Fügen wir jetzt noch die fehlende Membervariable m_nID zur Fensterklasse des Child-Window hinzu. Klicken Sie erneut mit der rechten Maustaste die Klasse CMyChild an und wählen jetzt aber aus dem Kontextmenü den Eintrag Membervariable hinzufügen... aus. Nehmen Sie dann im Dialog die nachfolgenden Eingaben vor und klicken zum Schluss den OK Button an. Beachten Sie dabei bitte den einzustellenden Zugriffsstatus unten im Dialog.

Eine Membervariable wird definiert

Damit wäre die Klasse für das Child-Window fertig. Aber ein Child-Window ohne Ausgabe ist doch relativ nutzlos. Was unserem untergeordneten Fenster noch fehlt ist die Behandlung der WM_PAINT Nachricht. Sie erinnern sich doch noch? In der Regel stellen Fenster ihren Inhalt als Reaktion auf diese Nachricht neu dar. Fügen wir also unserem Fenster noch den Nachrichtenbearbeiter für die WM_PAINT Nachricht hinzu.

Klicken Sie jetzt in der Klassenansicht mit der rechten Maustaste die Klasse CMyChild an und wählen aus dem Kontextmenü den Eintrag Behandlungsroutine für Windows-Nachricht hinzufügen... aus. Im dann dargestellten Dialog führen Sie im Feld Neue Windows-Nachrichten/-Ereignisse: einen Doppelklick auf den Eintrag WM_PAINT aus und klicken dann den OK Button an.

WM_PAINT Nachricht des Child-Windows

Der Klassen-Assistent fügt nun die Methode OnPaint(...) zur Klasse hinzu die Sie auch gleich wie folgt erweitern:

void CMyChild::OnPaint()
{
    CPaintDC dc(this); // device context for painting
   
    // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen
   
    TRACE("CMyChild%d: OnPaint()\n",m_nID);
}

Jedes Mal wenn das Child-Window seinen Fensterinhalt neu darstellen muss erfolgt eine entsprechende Ausgabe im Debuggerfenster. Diese Ausgabe enthält auch den erwähnten Index des Child-Windows.

Damit ist sozusagen der erste Teil der Aufgabe erledigt. Was jetzt noch fehlt ist die eigentliche Erstellung der Child-Windows. Zuerst müssen die Fensterobjekte für die Child-Windows definiert werden. Hierbei stellt sich die Frage, wo die Fensterobjekte letztendlich definiert werden. Nun, da die Child-Windows untergeordnete Fenster des Rahmenfensters sind, werden Sie als Member des Rahmenfensters definiert.

Fügen Sie jetzt wieder über den Klassen-Assistenten zwei Membervariable vom Typ CMyChild zum Rahmenfenster CMainFrame hinzu. Geben Sie den Membervariablen die Namen m_wndChild1 und m_wndChild2.

Im zweiten Schritt können jetzt die Fenster der Child-Windows erstellt werden.

Versuchen Sie jetzt einmal selbst die Fenster für die Child-Windows zu erstellen. Als kleiner Tipp hierzu: sehen Sie sich den vom Anwendungs-Assistenten erstellten Code einmal an und suchen Sie die Stelle, an der der Ausgabebereich der Anwendung erstellt wird. Und noch ein wichtiger Hinweis: achten Sie unbedingt auch auf den richtigen Fensterstil!

Lösung zur Erstellung der Child-Windows Fenster

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    ....
    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
    }

    m_wndChild1.Create(NULL,"MyChild1",WS_CHILD|WS_VISIBLE,
                       CRect(10,10,300,200),this,1,NULL);
    m_wndChild2.Create(NULL,"MyChild2",WS_CHILD|WS_VISIBLE,
                       CRect(50,50,340,240),this,2,NULL);

    return 0;
}

Die Fenster der Child-Windows werden mit der bekannten Methode Create(...) erstellt. Für den WINDOWS Fensterklassenname wird hier NULL angegeben da die Child-Windows eine eigene WINDOWS Fensterklasse verwenden. Danach kommt die Titelzeile des Fensters. Der 3. Parameter ist von entscheidender Bedeutung für die untergeordneten Fenster. Sie müssen für Child-Windows hier immer den Stil WS_CHILD angegeben. Soll das Fenster auch noch angezeigt werden, so dürfen Sie nicht vergessen zusätzlich WS_VISIBLE anzugeben. Danach folgt die Fensterposition und -größe in Client-Koordinaten des übergeordneten Fensters. Als nächstes ist der Zeiger auf das übergeordnete Fenster anzugeben, also hier der Zeiger auf das Rahmenfenster. Der vorletzte Parameter spezifiziert den Index des untergeordneten Fensters. Dieser Index wird u.a. an die Methode PreCreateWindow(...) innerhalb der Struktur CREATESTRUCT übergeben und wurde vorhin bereits auch schon ausgewertet. Den letzten Parameter sollten Sie bis auf weiteres auf NULL belassen.

Ende der Lösung

Übersetzen und starten Sie anschließend das Beispiel.

Wenn Sie alles richtig eingegeben haben, so sollten jetzt die beiden Child-Windows erscheinen. Sieht doch eigentlich schon recht vielversprechend aus, oder?

Child-Windows 1. Versuch

Aber leider ist im Leben nicht alles so wie es scheint. Versuchen Sie jetzt einmal eines der Child-Windows zu verschieben. Es wird einfach nicht gelingen! Außerdem scheinen die Rahmen der Child-Windows noch durch. Doch was läuft hier schief? Sehen Sie sich dazu zunächst einmal die Erzeugung der WINDOWS Fensterklasse der Child-Windows an. Die Fenster sollten eigentlich den Cursor UPARROW verwenden. Wenn Sie mit dem Cursor jedoch über die Fenster fahren so bleibt der normale Cursor erhalten! Der Grund hierfür liegt in der so genannten Z-Order der Fenster. Die Z-Order gibt die Reihenfolge an, in der ein Fenster ein anderes überlagert. So wie Sie z.B. bei einem Kartenspiel die Karten übereinander stapeln. In unserem Fall ist es nun so, dass der Ausgabebereich der Anwendung die beiden Child-Windows überlagert und damit diese quasi verdeckt. Doch wieso sind die Child-Windows dann überhaupt sichtbar. Sehen Sie sich dazu einmal die Ausgaben der OnPaint(...) Methoden im Debuggerfenster an:

CChildView::OnPaint()
CMyChild1: OnPaint()
CMyChild2: OnPaint()

Zuerst wird der Ausgabebereich dargestellt und dann die beiden Child-Windows. Deshalb sind sie zwar sichtbar, aber auf Grund der Z-Order nicht veränderbar.

Wir benötigen nun eine Methode die es uns erlaubt, die Z-Order zu verändern.

Versuchen Sie jetzt einmal selbst die entsprechende Methode zu finden. Sie sollten unbedingt lernen mit der Online-Hilfe des Compilers umzugehen. Als kleiner Tipp noch: die gesuchte Methode gehört zu der Klasse, die die generellen Fenstereigenschaften festlegt, also zur Klasse CWnd.

Wenn Sie die entsprechende Methode gefunden haben, fügen Sie sie auch gleich ins Beispiel ein.

Lösung zur Z-Order der Child-Windows

Die gesuchte Methode ist die Methode SetWindowPos(...). Sie erlaubt außer dem Festlegen der Fensterposition und -größe auch die Bestimmung der Z-Order durch den ersten Parameter der Methode.

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    ....
    m_wndChild1.Create(NULL,"MyChild1",WS_CHILD|WS_VISIBLE,
                       CRect(10,10,300,200),this,1,NULL);
    m_wndChild2.Create(NULL,"MyChild2",WS_CHILD|WS_VISIBLE,
                       CRect(50,50,340,240),this,2,NULL);
    // Child-Windows in den Vordergrund bringen!
    m_wndChild1.SetWindowPos(&wndTop,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
    m_wndChild2.SetWindowPos(&wndTop,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
    return 0;
}

Beide Child-Windows werden nacheinander in der Vordergrund gebracht. Dadurch überlagert das Fenster m_wndChild2 das Fenster m_wndChild1 und dieses den Ausgabebereich der Anwendung CChildView.

Ende der Lösung

Übersetzen und starten Sie anschließend das Beispiel. Was sehen Sie dann?

Nun, auf den ersten Blick vermutlich nicht mehr viel. Die Child-Windows sind auf einmal verschwunden. Wenn Sie jedoch mit dem Cursor in die linke obere Hälfte des Rahmenfensters fahren wird sich der Cursor ändern. Was ist jetzt passiert? Sehen Sie sich wieder die Ausgaben der OnPaint(...) Methode im Debuggerfenster an.

CMyChild2: OnPaint()
CMyChild1: OnPaint()
CChildView::OnPaint()

Die Reihenfolge der Aufrufe hat sich umgedreht. Jetzt werden zuerst die Child-Windows gezeichnet und zum Schluss der Ausgabebereich der Anwendung. Dadurch werden die Child-Windows jetzt einfach 'übermalt'. Untergeordnete Fenster auf gleicher Stufe in der Fensterhierarchie, auch Siblings genannt, können in den Bereich des jeweils anderen Fenster hineinzeichnen. Und alle drei untergeordnete Fensters, sowohl der Ausgabebereich wie auch die beiden Child-Windows, stehen auf der gleichen Hierarchiestufe. Sie sind direkt dem Rahmenfenster untergeordnet. Aber keine Angst, auch hierfür gibt es selbstverständlich eine Lösungen. Wird bei einem untergeordneten Fenster der Fensterstil WS_CLIPSIBLINGS gesetzt, so kann es nicht mehr in den Bereich eines anderen Fensters hineinzeichnen.

Fügen Sie jetzt nur einmal zum Ausgabebereich der Anwendung diesen Fensterstil hinzu. Beachten Sie dabei aber bitte unbedingt, dass Sie den zusätzlichen Fensterstil mit den bestehenden Fensterstilen verodern und niemals eine Zuweisung hier einsetzen. Sie würden sonst nämlich alle anderen Fensterstile löschen!
BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)
{
    ....
    cs.dwExStyle |= WS_EX_CLIENTEDGE;
    cs.style &= ~WS_BORDER;
    cs.style |= WS_CLIPSIBLINGS;
    ....
    return TRUE;
}

Übersetzen und starten Sie das Programm nun. Was sehen Sie jetzt?

Ihre Child-Windows erscheinen auf einmal wieder. Sie können Sie jetzt auch über die Titelleiste verschieben. Doch was ist mit dem Fensterinhalt passiert?

Child-Windows 2. Versuch

Es hat den Anschein, als ob man durch die Child-Windows hindurch sieht. Und noch etwas merkwürdiges geht hier vonstatten. Führen Sie einmal folgenden Versuch durch:

Verschieben Sie unmittelbar nach dem Starten der Anwendung einmal das erste Child-Window. Der überlappende Teil des zweiten Child-Windows scheint ebenfalls mit verschoben zu werden.

Verkleinern Sie jetzt das Rahmenfenster zum Symbol und vergrößern es anschließend sofort wieder. Jetzt scheinen die Teile der Fenster hindurch, die sich unmittelbar unter den Child-Windows befinden.

Die Lösung diese Problems ist, wie so oft im Leben, ganz einfach wenn man's weiß. Sehen Sie sich nochmals die Anweisung zur Registrierung der WINDOWS Fensterklasse für die Child-Windows in der Methode PreCreateWindow(...) an. Außer dem Fensterstil und dem Cursor haben wir hier nichts weiter vorgegeben. D.h. für den Fensterhintergrund wird der Standard-Brush verwendet. Und dies ist leider der leere Brush der überhaupt nichts zeichnet.

Korrigieren Sie jetzt den Aufruf der Funktion AfxRegisterWndClass(...) wie folgt um für die Child-Windows einen hellgrauen Hintergrund zu erzeugen:
BOOL CMyChild::PreCreateWindow(CREATESTRUCT& cs)
{
    ....
    cs.lpszClass =
               AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
                                    ::LoadCursor(NULL, IDC_UPARROW),
                                   HBRUSH(::GetStockObject(LTGRAY_BRUSH)),
                                   NULL);

    return CWnd::PreCreateWindow(cs);
}

Übersetzen und starten Sie das Beispiel erneut.

Nun sieht's doch recht gut aus! Aber... verschieben Sie jetzt einmal die Child-Windows so, dass diese sich abwechselnd überlagern.

Child-Windows 3. Versuch

Irgendwie werden die Rahmen und Titelzeilen der Fenster nicht richtig dargestellt. So wie es aussieht, malt hier wieder ein Fenster in den Bereich des anderen hinein. Und dies hatten wird doch vorher schon einmal! Sie müssen, damit die Child-Windows sich nicht gegenseitig ihre Bereiche übermalen, den Fensterstil WS_CLIPSIBLINGS auch bei den Child-Windows explizit setzen!

Erweitern Sie jetzt den Fensterstil für die Child-Windows in deren Methode PreCreateWindow(...) entsprechend. Beachten Sie auch hier bitte, dass der neue Fensterstil verodert und nicht zugewiesen wird!
BOOL CMyChild::PreCreateWindow(CREATESTRUCT& cs)
{
    ....
    cs.style |= WS_CAPTION;
    cs.style |= WS_CLIPSIBLINGS;
    // Eigene WINDOWS Fensterklasse erstellen
    ....
}

Wenn Sie jetzt das Beispiel übersetzen so sollte es endlich das Verhalten aufzeigen, wie Sie es sich von Anfang an vorgestellt haben.

Das fertig Beispiel finden Sie im Programmverzeichnis unter 03EPMMFC\ChildWnd.

Sie sehen, die Erstellung eines untergeordneten Fensters enthält zahlreiche Fallen. Fassen wir deshalb die einzelnen Schritte nochmals kurz zusammen:

  • Erstellen der Fensterklasse für das untergeordnete Fensters.
  • Überschreiben der Methode PreCreateWindow(...) für das untergeordnete Fenster und setzen des Fensterstil WS_CLIPSIBLINGS. Außerdem muss eine eigene WINDOWS Fensterklasse mit einem Hintergrund-Brush für das untergeordnete Fenster erstellt werden.
  • Setzen des Fensterstils WS_CLIPSIBLINGS für den Ausgabebereich in dessen PreCreateWindow(...) Methode.
  • Erstellen des Child-Window in der Methode OnCreate(...) des Rahmenfensters mittels Create(...). Hierbei ist zu beachten, dass für das untergeordnete Fenster die Stile WS_CHILD und WS_VISIBLE gesetzt werden.
  • Das Child-Window in der Z-Order mittels der Methode SetWindowPos(...) in den Vordergrund bringen.

Das war's auch schon. Wenn Sie sich an diesen Ablauf halten so dürfte Ihnen die Erstellung von Child-Windows in Zukunft keine Schwierigkeit mehr bereiten.

Sehen wir uns nun die zweite Art von untergeordneten Fenstern an, die Popup-Windows. Diese unterscheiden sich von Child-Windows darin, dass sie beliebig auf dem Desktop positioniert werden können. Aber gehen wir auch hier gleich zur Praxis über.

Als Ausgangsbasis für die nachfolgenden Übungen dient das vorhin erstellte Beispiel. Kopieren Sie sich aus dem Programmverzeichnis das Projekt 03EPMMFC\ChildWnd ins Arbeitsverzeichnis oder verwenden Sie einfach die vorherige Übung.

Um ein Popup-Window zu erstellen ersetzen wir zunächst bei der Erstellung des untergeordneten Fensters einfach den Fensterstil WS_CHILD durch WS_POPUP.

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    ....

    m_wndChild1.Create(NULL,"MyChild1",WS_POPUP|WS_VISIBLE,
                       CRect(10,10,300,200),this,1,NULL);
    m_wndChild2.Create(NULL,"MyChild2",WS_POPUP|WS_VISIBLE,
                       CRect(50,50,340,240),this,1,NULL);
    // Child-Windows in den Vordergrund bringen!
    ....
}

Übersetzen und starten Sie jetzt das Programm.

Mit relativ großer Sicherheit wird das Programm nicht laufen. Vielmehr werden Sie folgende Meldung erhalten:

Die ASSERT Fehlermeldung

Was ist passiert? Nun, in der Datei wincore.cpp wurde in der Zeile 736 mit dem Makro ASSERT ein Fehler ausgelöst.

ASSERT(...) ist eines der vielen kleinen Makros die einem MFC Programmierer das Leben erleichtern sollen. ASSERT prüft im Debug-Fall (und nur dort!) ab, ob die Auswertung des Ausdrucks FALSE ergibt und bricht dann die Programmausführung mit der oben angegebenen Meldung ab.
ACHTUNG! Da ASSERT(...) nur im Debug-Fall ausgeführt wird dürfen Sie als Ausdruck keine Berechnungen durchführen die Sie in jedem Fall benötigen. Das gleiche gilt auch für den Aufruf von Funktionen/Methoden. Tun Sie dies trotzdem, so wird Ihr Programm in der Debug-Version funktionieren und in der Release-Version nicht mehr!

Wenn Sie jetzt den Button Wiederholen anklicken gelangen Sie zu der Stelle, die den Fehler ausgelöst hat. Sehen wir uns die Stelle in der Datei wincore.cpp an. Sie sollte etwa wie folgt aussehen:

    // can't use for desktop or pop-up windows (use CreateEx instead)
    ASSERT(pParentWnd != NULL);
    ASSERT((dwStyle & WS_POPUP) == 0);

Der Kommentar gibt den Grund für den Laufzeitfehler an: Sie können mit der Create(...) Methode der Klasse CWnd kein Popup-Window erstellen. Aber der Kommentar gibt auch einen Hinweis was in diesem Fall zu tun ist: anstelle von Create(...) sollte die Methode CreateEx(...) aufgerufen werden. Es gibt aber auch noch einen anderen Weg. Entfernen Sie bei der Erstellung des untergeordneten Fenster den Stil WS_POPUP und setzen Sie ihn erst in der PreCreateMethode(...) des untergeordneten Fensters.

Entfernen Sie nun zunächst den Stil WS_POPUP bei der Erstellung des untergeordneten Fensters. Dadurch wird der MFC in die irrige Annahme versetzt, dass Sie ein 'normales' Fenster erstellen wollen. Die beiden Aufruf der Methoden SetWindowPos(...) können Sie in diesem Zuge auch entfernen, da Popup-Windows wie eigenständige Fenster behandelt werden.
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{

    m_wndChild1.Create(NULL,"MyChild1",WS_VISIBLE,
                       CRect(10,10,300,200),this,1,NULL);
    m_wndChild2.Create(NULL,"MyChild2",WS_VISIBLE,
                       CRect(50,50,340,240),this,2,NULL);

    return 0;
}

Als nächstes fügen Sie den Stil WS_POPUP in der PreCreateMethode(...) des untergeordneten Fensters wieder hinzu. Ebenfalls muss nun noch die Menü-ID auf 0 gesetzt werden da sonst versucht wird das entsprechende Menü aus der Ressource zu laden.

BOOL CMyChild::PreCreateWindow(CREATESTRUCT& cs)
{
    // TODO: Speziellen Code hier einfügen und/oder Basisklasse aufrufen
    // Index (Child-ID) abspeichern, wird beim Aufruf der
    // Create(...) Methode definiert
    m_nID = (int)cs.hMenu;
    // Fenster mit Titelzeile versehen
    cs.style |= WS_CAPTION;
    // Window in Popup-Window umwandeln
    cs.style |= WS_POPUP;
    // Menue-ID muss jetzt auf 0 gesetzt werden!
    cs.hMenu = 0;
    // Eigene WINDOWS Fensterklasse erstellen
    cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
                                       ::LoadCursor(NULL, IDC_UPARROW),
                                      HBRUSH(::GetStockObject(LTGRAY_BRUSH)),
                                      NULL);
    return CWnd::PreCreateWindow(cs);
}

Beachten Sie bitte, dass der Fensterstil WS_CLIPSIBLINGS ebenfalls entfernt wurde. Popup-Windows zeichnen niemals in die Bereiche andere Fensters.

Zusätzlich können Sie für den Ausgabebereich ebenfalls den Stil WS_CLIPSIBLINGS entfernen.

Übersetzen und starten Sie das Programm.

Sie erhalten nun anstelle von zwei Child-Windows zwei Popup-Windows angezeigt die Sie beliebig auf dem Desktop positionieren können. Beachten Sie bitte, dass die Popup-Windows immer über dem Rahmenfenster dargestellt werden.

Damit beenden wir die Behandlung der untergeordneten Fenster und wenden uns in der nächsten Lektion der Leerlauf-Verarbeitung 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