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:

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:

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.

|
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.

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!
 |
Bevor Sie an dieser Übung verzweifeln kann ich Ihnen auch wieder die
Lösung anzeigen. |
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?

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.
 |
Bevor Sie an dieser Übung verzweifeln kann ich Ihnen auch wieder die
Lösung anzeigen. |
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?

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.

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:

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.
|