...someplace, where there isn't any trouble? Do you suppose there is such a place, Toto?

AERO Wizards und MFC - Ein Trauerspiel in zwei Akten...(Erster Akt)

Zwischenzeitlich dürfte ja schon bekannt sein, daß Windows Vista einen neuen Stil für Wizards mitbingt, den AERO Wizard. Nachfolgende Abbildung zeigt so einen Wizard:

Ein AERO Wizard

Anders als der Name suggeriert, hat so ein Wizard nichts damit zu tun, wofür AERO sonst steht, nämlich für die semitransparenten Fenster der Desktop Composition Engine. Daher funktionieren solche Wizards natürlich auch auf Vista-Systemen, auf denen kein AERO läuft, beispielsweise der Home Basic Edition oder in virtuellen Maschinen unter Vista, etwa unter VMWare.

Da ja bekanntermaßen Vista ungefähr zeitgleich mit VS2005 releast wurde, darf es einen deshalb nicht wundern, daß AERO Wizards mit den MFC von VS2005 nicht so wirklich gut funktionieren. So ist es beispielsweise so, daß seit MFC Version 4 alle modalen Fenster gar nicht wirklich modal sind, sondern nur eine modale Loop haben, die das Verhalten modaler Fenster perfekt nachahmt. Das heißt auch, daß Property Sheets (und ein Wizard ist ein Property Sheet) in den MFC in Wahrheit moduslos erzeugt werden und hinterher in der DoModal-Schleife laufen, wenn sie schließlich modal verwendet werden. Dummerweise vertragen sich aber das neue Stilbit für AERO Wizards (PSH_AEROWIZARD) nicht mit moduslosen Property Sheets, was MS so auch dokumentiert. Daß aber die Unterstützung von AERO-Wizards in VS2008, also gut zwei Jahre nach dem Release von Windows Vista, noch immer irgendwo zwischen grottenschlecht und nichtexistent ist, ist nach meinem Dafürhalten schon ein starkes Stück. Aber schauen wir uns einmal die Gründe dafür an:

Um zu verstehen, warum AERO Wizards mit den MFC so schlecht zusammenspielen und um sich Workarounds zu basteln, muß man die Fensterhierarchien von traditionellen Wizards oder dem nun auch schon 10 Jahre alten Wizard97-Stil kennen. Ein Wizard97-Wizard hat ungefähr folgende Fensterhierarchie:

Fensterhierarchie eines traditionellen Wizards

Man erkennt deutlich, daß die einzelnen Seiten des Wizards unmittelbare Kindfenster des Property Sheets sind, und gleichzeit Siblings der Buttons und des Tab Controls (das man aber nur wirklich dann sieht, wenn das Property Sheet *nicht* als Wizard betrieben wird). Alles schön straightforward.

Ganz anders die durchaus barock anmutende Fensterhierarchie bei einem AERO Wizard:

Fensterhierarchie eines AERO Wizards

Man sieht hier, daß das Parent Window jeder Wizard-Seite ein Fenster der Fensterklasse "CtrlNotifySink" ist, dessen Parent dann wiederum ein Fenster der Fensterklasse "DirectUIHWND" ist. Erst dessen Parent Window ist schließlich das Property Sheet. Das Property Sheet ist also nicht mehr das Elternfenster der einzelnen Wizardseiten, sondern gehört, wenn man so will, dessen Urgroßeltern-Generation an. Ausserdem ist erkennbar, daß das Tab Control gar nicht mehr da ist.

Mit diesem Wissen machen wir nun einen Grep in den MFC Sourcen nach "GetTabControl" und "GetParent", genauer gesagt in der Datei dlgprop.cpp. Wir werden dann erkennen, daß die Implementation von CPropertySheet/CPropertyPage in ganz erheblichem Maße auf der Annahme beruht, daß das Parent Window einer Wizard-Seite das PropertySheet ist und daß dieses Property Sheet dann ein Tab Control besitzt. Aufgrund dieser Annahme im Code der MFC funktionieren dann beispielsweise folgende Methoden überhaupt nicht, wenn man bei einem MFC-basierten Wizard ganz naiv das Stilbit PSH_AEROWIZARD verwendet (ohne Anspruch auf allumfassende Vollständigkeit):

void CPropertyPage::CancelToClose()
void CPropertyPage::SetModified(BOOL bChanged)
LRESULT CPropertyPage::QuerySiblings(WPARAM wParam, LPARAM lParam)
BOOL CPropertyPage::OnWizardFinish()
HWND CPropertyPage::OnWizardFinishEx()
LRESULT CPropertyPage::MapWizardResult(LRESULT lToMap)
BOOL CPropertyPage::IsButtonEnabled(int iButton)
void CPropertyPage::EndDialog(int nID)
int CPropertySheet::GetPageCount() const
int CPropertySheet::GetPageIndex(CPropertyPage* pPage)
BOOL CPropertySheet::SetActivePage(CPropertyPage* pPage)
void CPropertySheet::RemovePage(CPropertyPage* pPage)

Und dadurch, daß CPropertyPage::MapWizardResult nicht funktioniert, wird auch jede Redefinition der folgenden virtuellen Methoden

virtual LRESULT CPropertyPage::OnWizardBack()
virtual LRESULT CPropertyPage::OnWizardNext()

in einer eigenen, von CPropertySheet abgeleiteten Klasse, niemals das gewünschte Verhalten zeigen. Es sollte nun eigentlich unmittelbar einleuchten, daß die Verwendung von AERO Wizards mit Bordmitteln von MFC weit mehr ist als das bloße Ergänzen des neuen Stilbits PSH_AEROWIZARD. Insbesondere wenn man von CPropertyPage angeleitete Klassen verwendet und darin das Ergebnis von GetParent() in einen CPropertySheet Pointer castet, wird man böse Überraschungen erleben.

Was kann man also tun um PSH_AEROWIZARD auch mit CPropertySheet/CPropertyPage verwenden zu können, und was für böse Überraschungen lauern denn sonst noch so? Darum wird es im zweiten Akt dieser Mini-Serie gehen. Stay tuned!

(Edit: (09/03/2008 8:49PM: Im vorletzten Absatz muss es heißen "...wenn man von CPropertyPage angeleitete Klassen...")

Trackback address for this post

This is a captcha-picture. It is used to prevent mass-access by robots.
Please enter the characters from the image above. (case insensitive)

2 comments

Comment from: Dlvpmnt [Visitor] Email
DlvpmntGibt's das ganze auch als einfacher Download zum Verstehen?
12/29/08 @ 15:31
Comment from: sku [Member] Email
skuHallo Dlvpmnt,

ein Downloadlink fuer meinen Wrapper findet sich im zweiten Artikel dieser Miniserie.

--
S
12/29/08 @ 16:51

Comments are closed for this post.