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

Neuer Codeproject Artikel

Link: http://www.codeproject.com/KB/install/sfxinst.aspx

Auf Codeproject steht ein neuer Artikel von mir. Vote da, wer da kann und mag mit guten Noten für diesen Artikel.

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

Im letzten Posting habe ich auf einige prinzipielle Besonderheiten von AERO Wizards hingewiesen, die mit MFCs CPropertySheet/CPropertyPage erstellt wurden. In diesem Posting will ich auf ein paar Spezialitäten hinweisen, die weitere Erwähnung verdient haben und außerdem meinen Workaround aus dem ganzen Dilemma vorstellen.

Zunächst einmal ist es wichtig zu wissen, daß CPropertySheet::DoModal einen anderen Rückgabewert hat, wenn man ihn als AERO-Wizard betreibt. Drückt der geneigte Benutzer den Finish-Button, dann geben herkömmliche Wizards, einschließlich Wizard97-basierte Wizards, den Wert ID_WIZFINISH von DoModal zurück. Ein AERO-basierter Wizard liefert den Wert IDOK zurück.

Um einzelne Wizardbuttons zu enabeln oder zu disablen, benutzt man bei AERO-Wizards die neue Nachricht PSM_ENABLEWIZBUTTONS (WM_USER + 139) oder das Makro PropSheet_EnableWizButtons. Für hekömmliche Wizards benutzt man PSM_SETWIZBUTTONS (WM_USER + 112). Natürlich verwendet CPropertySheet::SetWizardButtons ausschliesslich PSM_SETWIZBUTTONS und weiß an dieser Stelle nichts von AERO Wizards. Dann gibt es da noch drei neue Nachrichten nur für AERO-Wizards namens PSM_SHOWWIZBUTTONS (zeigt oder verbirgt einzelne Wizard-Buttons), PSM_SETBUTTONTEXT (setzt Button-Texte) und PSM_SETNEXTTEXT (setzt den Text des Next-Buttons), für die es keine Pendants für herkömmliche Wizards gibt. Zum Ausgleich funktioniert dafür so rund die Hälfte der herkömmlichen Wizard-Nachrichten mit AERO-Wizards gar nimmer, als da wären:

PSM_APPLY
PSM_CANCELTOCLOSE
PSM_CHANGED
PSM_GETCURRENTPAGEHWND
PSM_GETRESULT
PSM_GETTABCONTROL
PSM_INSERTPAGE
PSM_ISDIALOGMESSAGE
PSM_PRESSBUTTON (in Teilen)
PSM_REBOOTSYSTEM
PSM_RECALCPAGESIZES
PSM_RESTARTWINDOWS
PSM_SETHEADERBITMAP
PSM_SETHEADERBITMAPRESOURCE
PSM_SETHEADERSUBTITLE
PSM_SETTITLE
PSM_UNCHANGED

Und das sind nun erstmal nur diejenigen Nachrichten, für die auch dokumentiert ist, daß sie mit AERO-Wizards nicht funktionieren. Wenn man also einen bestehenden Wizard in einen AERO-Wizard umwandeln will, sollte man also zuerst einmal abklären, ob unter der Haube irgendwo eine dieser Nachrichten oder seine Verkapselung in Methoden von CPropertySheet/CPropertyPage verwendet wird. Ich für meinen Teil habe begonnen, mir Klassen zu schreiben, die die Eigentümlichkeiten der AERO-Wizards kapseln und mir eine Auswahl des Stils, also Wizard97 oder AERO, zum Instanziierungszeitpunkt gestatten. Die Klassen, CWiz97AeroPropertySheet und CWiz97AeroPropertyPage, haben bisher nur das, was ich bis jetzt brauche und werden sicherlich im Lauf der nächsten Jahre noch anwachsen. Man setzt sie einfach statt CPropertySheet/CPropertyPage ein und kann sie hier downloaden.

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

PREfast für ganz, ganz Arme

Vor ziemlich genau zwei Jahren habe ich hier beschrieben, wie man sich PREfast zu VS2005 Professional ergänzen kann. Nochmal Zur Erinnerung: PREfast ist ein statisches Codeanalyzetool, das seit VS2005 bei den etwas teureren Ausgaben mitshippt. Bei der Professional Edition von VS2005/VS2008 ist es nicht dabei, erst in der "Team System 2008 Developer Edition" und der "Team System 2008 Team Suite" ist es enthalten. Aber man kann es mit ein paar einfachen Handgriffen nachrüsten und an dieser Stelle zeige ich nun wie das geht. Die hier beschriebene Vorgehensweise funktioniert mit der Express Edition von VS2008 und der Professional, also nehme ich mal an, sie geht auf allen anderen auch, bei denen PREfast nicht mit enthalten ist.

Am allereinfachsten geht das Nachrüsten von PREfast durch Installation des Server 2008 Windows SDKs. Die Installation des SDKs kopiert einfach die notwendigen Dateien an die richtige Stelle und schon funktioniert die Sache. Wer aber das gigantische Server 2008 SDK nicht downloaden und installieren will (VS2008 enthält das Vista SDK), weil ihm die 4 GB, die das Ding braucht zuviel sind oder eine einfachere Lösung haben will, sollte jetzt weiterlesen. Zuerst mal brauchen wir die eine cab-Datei, in der die nachzurüstenden Dateien allesamt drinstecken. Dazu laden wir die Datei vc_stdx86.cab von folgendem URL herunter: http://download.microsoft.com/download/2/3/F/23F86204-39EE-4CD7-9A51-DB19C9A8F8C4/vc_stdx86.cab. Das sind etwa 40MB und was sind schon 40 MB unter Freunden? So, die haben wir jetzt heruntergeladen und machen dann auf die Datei einen rechten Mausklick so dass das Kontextmenü erscheint, wie in nachfolgendem Screenshot:

Wir wählen den Punkt "Explore" an, woraufhin sich ein Explorerfenster mit dem Inhalt der cab-Datei öffnet wie im nächsten Screenshot:

Dort wählen wir, wie in dem Screenshot dargestellt, die Dateien FL_c1ast_dll_93380_93380_x86_ln.3643236F_FC70_11D3_A536_0090278A1BB8 und FL_c1xxast_dll_93381_93381_x86_ln.3643236F_FC70_11D3_A536_0090278A1BB8 an und wählen im Kontextmnenü nach dem rechten Mausklick den Punkt "Extract". Daraufhin wählt man ein zuvor angelegtes temporäres Verzeichnis an, wohin man die Dateien extrahiert. Dasselbe macht man nun für die Dateien FL_mspft80ui_dll_133655_133655_x86_enu.3643236F_FC70_11D3_A536_0090278A1BB8 und FL_mspft80_dll_93382_93382_x86_ln.3643236F_FC70_11D3_A536_0090278A1BB8 wie im nächsten Screenshot:

Diese Dateien extrahieren wir in dasselbe Verzeichnis wie die beiden anderen zuvor und benennen nun die Dateien um:

  • FL_c1ast_dll_93380_93380_x86_ln.3643236F_FC70_11D3_A536_0090278A1BB8 wird zu c1ast.dll
  • FL_c1xxast_dll_93381_93381_x86_ln.3643236F_FC70_11D3_A536_0090278A1BB8 wird zu c1xxast.dll
  • FL_mspft80ui_dll_133655_133655_x86_enu.3643236F_FC70_11D3_A536_0090278A1BB8 wird zu mspft80ui.dll
  • FL_mspft80_dll_93382_93382_x86_ln.3643236F_FC70_11D3_A536_0090278A1BB8 wird zu mspft80.dll

Nachdem die Dateien nun handliche Namen haben, sucht man sich nun das Bin-Verzeichnis der VC-Installation in VS2008 und kopiert da hinein die Dateien c1ast.dll, c1xxast.dll und mspft80.dll, so wie inm nächsten Screenshot:

Als nächstes legen wir, sofern noch nicht vorhanden, ein Unterverzeichnis namens "1033" in diesem Verzeichnis an und kopieren die letzte der Dateien, mspft80ui.dll, dort hinein. Bei einer deutschen VS-Installation ist ein Unterverzeichnis "1033" nicht vorhanden, stattdessen ein Verzeichnis "1031". In dem Fall muss aber trotzdem das Verzeichnis 1033 angelegt werden, die Datei darf nicht im Verzeichnis "1031" landen.

Nun ist eigentlich alles schon fertig und zum Beweis, dass PREfast jetzt tut, legen wir jetzt mal ein einfaches Projekt mit einem Fehler an, den PREfast finden kann. Ich wähle dazu eine Kommandozeilenapplikation. Um einen PREfast-Lauf beim Übersetzen zu machen, muss man in der Kommandozeile bei den C++-Settings nun händisch "/analyze" ergänzen, so wie im nächsten Screenshot:

Wenn wir nun das Projekt übersetzen, dessen Sourcecode man im nächsten Screenshot auch sieht, erhält man eine wunderbare Warnung von PREfast mit dem Codepfad, der zu dem potentiellen Fehler führt, grau hinterlegt:

War das nicht einfach?

Ein Gutsle in VS2008

Heute habe ich ein echtes Gutsle in VS2008 entdeckt: Die Visual Studio 2008 Image Library. Man findet sie unter <vs>\common7\VS2008ImageLibrary, darunter in der englischen Version in einem Unterverzeichnis namens 1033. Darin sind zig Standardicons und -bitmaps und auch Animationen, die man in den eigenen Programmen verwenden darf. Man darf das, wenn man mindestens eine Visual Studio 2008 Standard Edition besitzt, die Express Versionen haben das nicht. Für meine Pet Projects wird das sicherlich sukzessive meine Verwendung der silk icons ablösen.

Ansonsten habe ich natürlich auch eine hochprofessionelle Grafik-Toolchain aus irfanview zum Ankucken und Skalieren, aus dem GIMP zum Bearbeiten und aus inkscape um svg-Dateien zu Modifizieren und Exportieren. Teilweise sehr hochwertige svg-Dateien unter der Creative-Commons-Lizenz bekommt man von der Open Clipart Library. Wenn man die runterskaliert erhält man teilweise sehr schöne Icons. Mit mspaint kann man anders als mit irfanview sehr gut Bitmaps verschieben und in der Größe ändern, also gehört das auch zu meinen Lieblingstools. Irfanview hat dafür sehr mächtige Skalierungsfunktionen und man kann damit sehr gut Paletten bearbeiten. GIMP hingegen kann praktisch alles, ist aber mitunter schwierig zu bedienen. Besonders stark ist natürlich GIMP bei den Bitmaps mit Alphakanal, wie man sie heute für MFCNext braucht.

<< 1 ... 17 18 19 20 21 22 23 24 25 26 27 ... 47 >>