15. Jul 2019
Lesedauer 13 Min.
MessageBox 2.0
Nutzung des TaskDialog-API aus .NET-Programmen
Taskdialoge sind die deutlich leistungsfähigere Alternative zur MessageBox. Per P/Invoke können auch .NET-Programme auf das Win32-API zum Aufruf der Dialoge zugreifen.

Die MessageBox-Funktion gibt es seit grauer Windows-Vorzeit. Eher grau ist auch ihr Erscheinungsbild, an dem sich über die Jahre wenig geändert hat. Das Icon können Programmierer seit Windows-95-Zeiten anpassen (MessageBoxIndirect), und eine mit Windows XP eingeführte, undokumentierte Variante (MessageBoxTimeOut) verschwindet automatisch, wenn der Anwender in der vorgegebenen Zeit keine Aktion durchführt [1]. Die MessageBox-Funktion im .NET Framework ist weitestgehend ein Abbild ihres Gegenstücks aus dem Windows-API ohne erweiterte Funktionalität.Mit Windows Vista und Server 2008 hat Microsoft den Taskdialog eingeführt, den es in zwei Varianten gibt: Zum einen ist dies die einfache Funktion TaskDialog, die im Unterschied zur MessageBox vor allem die zusätzliche Möglichkeit bietet, den eigentlichen Nachrichtentext um einen Text in einer kleineren Schriftart zu ergänzen, wie Bild 1 zeigt [2]. Im Vergleich zur Standard-MessageBox bietet diese Variante des Taskdialogs sogar weniger Funktionalität: Enthält der Dialog mehr als eine Schaltfläche, so kann man keine andere als die ganz linke als selektierte Schaltfläche auswählen (Parameter MB_DEFBUTTON für die Standard-MessageBox). Auch bietet die Standard-MessageBox die Option, sie durch Angabe des Parameters MB_SYSTEMMODAL als TopMost, das heißt permanent im Vordergrund stehend, zu definieren.

Die einfache Formdes Taskdialogs ähnelt der MessageBox, kann aber zusätzlichen Text anzeigen(Bild 1)
Autor
Den vollen Leistungsumfang der Taskdialoge bietet der Aufruf mit der Funktion TaskDialogIndirect [3]. Durch das Setzen von Feldern in der Struktur TaskDialogConfig, die an TaskDialogIndirect übergeben wird, lässt sich der Dialog auf vielfältige Weise anpassen [4]. So kann ein Taskdialog über benutzerdefinierte Schaltflächen in verschiedenen Formen, Fortschrittsbalken, eine Checkbox sowie Comboboxen zur Auswahl von Optionen verfügen. Auch lassen sich etwa in Fehlermeldungen auf Wunsch erweiterte technische Informationen anzeigen, wie in Bild 2 zu sehen. Über eine Callback-Funktion kann das Programm auf Benutzeraktionen im Taskdialog reagieren und zum Beispiel Text dynamisch ändern oder einzelne Kontrollelemente deaktivieren und aktivieren. Hat man schließlich zu viel Text oder Optionen für die zur Verfügung stehende vertikale Bildschirmauflösung im Taskdialog platziert, so fügt Windows automatisch eine Scrollbar ein. Einzig auf die Möglichkeit, ein Eingabefeld für Text zu definieren, muss man als Programmierer verzichten. So bieten sich Taskdialoge auch als schmalspurige Oberfläche für (Hilfs-)Programme mit überschaubarem Funktionsumfang an. Der Kasten Die Taskdialog-Elemente in der Übersicht zeigt und beschreibt die verschiedenen Elemente.

Über ein- und ausgeblendeten Textzeigt der Taskdialog erweiterte Informationen an(Bild 2)
Autor
Taskdialoge und .NET
Zur Zeit der Einführung von Windows 7 hat Microsoft das Windows API Code Pack veröffentlicht, das .NET-Assemblies zur einfachen Nutzung von neuen Vista- und Windows-7-Funktionalitäten bereitstellte, die sonst nur durch direkte API-Aufrufe oder als COM-Objekte zur Verfügung stehen. Unter anderem bietet das Code Pack Klassen zum Zugriff auf Sensoren, die Neuerung der Taskleiste und eben auch zur einfachen Anzeige von Taskdialogen. Auf den MSDN-Seiten mit der Dokumentation zu Taskdialogen finden sich noch Verweise auf das Code Pack, das sind aber nur tote Links; Microsoft hat sowohl den Quellcode als auch die kompilierten Bibliotheken entfernt [5]. Im Netz finden sich noch Kopien der alten Archive, die zum Teil aber nur noch nach einer Reihe von Anpassungen unter aktuellen Windows-Versionen laufen. Auch kann man das API Code Pack über NuGet beziehen [6].Da der Autor zu Zeiten von Windows Vista für die inzwischen eingestellte Zeitschrift Toolbox einen Artikel zur Nutzung von Taskdialogen mit Delphi geschrieben hat, lag es nahe, den damaligen Quellcode nach .NET zu übertragen, wenn möglich mit RemObjects Oxygene, einem Pascal-Compiler für .NET [7]. Abgesehen von den zur Nutzung des .NET Frameworks nötigen Änderungen ging das auch mit überschaubarem Aufwand von der Hand. Zu diesem Artikel gehört daher eine mit RemObjects Oxygene geschriebene Assembly zur Nutzung von Taskdialogen aus .NET-Programmen. Das Beispielprogramm ist in C# geschrieben, es zeigt die Flexibilität der Dialoge und auch, wie man die Möglichkeiten der Assembly nutzt. Das Programm erzeugt dazu eine Vielzahl verschiedener Taskdialoge, wie in Bild 3 gezeigt.
Das Beispielprogrammzum Artikel erzeugt eine Vielzahl unterschiedlicher Taskdialoge(Bild 3)
Autor
Das Taskdialog-API
Um alle Möglichkeiten eines Taskdialogs nutzen zu können, ist der Aufruf der Win32-Funktion TaskDialogIndirect nötig. Einer ihrer Parameter ist eine Struktur vom Typ TASKDIALOGCONFIG mit mehr als 20 Feldern, deren Belegung das Aussehen und die Funktionalität des Taskdialogs definiert. In Win32-Manier kommuniziert das Betriebssystem mit dem Taskdialog zur Laufzeit über Botschaften, die ein Programm über eine Callback-Funktion empfangen und auf die es reagieren kann. Auch das Beeinflussen des Taskdialogs durch das Programm erfolgt zur Laufzeit über eine Reihe von Nachrichten („Notifications“, deutsch „Benachrichtigungen“), die man an das Dialogfenster schickt. In der zum Windows-API gehörigen Header-Datei commctrl.h sind 15 mit dem Präfix TDM_ beginnende Nachrichten zur Steuerung von Taskdialogen definiert. Zusätzlich finden sich in der Datei elf Benachrichtigungs-Botschaften (TDN_xxx), die Windows beim Eintreffen von Ereignissen wie dem Klick auf eine Schaltfläche oder beim Erzeugen und Abbauen des Dialogs verschickt.Die Taskdialog-Elemente in der Übersicht
Die folgenden, in <span class="text-bildnachweis">Bild 4</span> dargestellten Elemente kann der Programmierer anpassen; in der Mehrzahl auch zur Laufzeit. In Klammern finden Sie die dazugehörige Eigenschaft in der Klasse <em>TTaskDialog</em>:
Will ein Programm Taskdialoge anzeigen, so muss es zwingend eine Manifest-Datei enthalten, die Metadaten für das Programm definiert. Darin muss die Nutzung der Windows Common Controls in Version 6 aktiviert sein. Das gilt für alle Arten von Windows-Programmen, die einen Taskdialog darstellen wollen, ganz gleich ob managed oder native. Fehlt die Manifest-Datei oder enthält sie keinen Eintrag für die Common Controls, so schlägt der Aufruf der Taskdialog-APIs mit einer Fehlermeldung fehl. Eine solche Manifest-Datei kann man dem Programm entweder als externe gleichnamige Datei mit dem Namen MeinProgramm.exe.manifest beifügen oder sie direkt in die EXE-Datei integrieren. Visual Studio fügt automatisch ein Manifest zu einem Projekt hinzu, wenn in den Projektoptionen unter Security die Option Enable ClickOnce security settings aktiviert ist. Daraufhin wird das Projekt um eine Datei app.manifest erweitert. Die Einstellung für ClickOnce security kann man direkt danach wieder deaktivieren. Alternativ können Sie im Projekt Add | New Item aufrufen und im Dialog Add New Item unter General den Eintrag Application Manifest File auswählen.Unabhängig davon, auf welchem Weg Sie die Manifest-Datei erzeugen, sollten Sie sicherstellen, dass in den Eigenschaften der Datei unter Build Action der Eintrag Embedded Resource angewählt ist. Das bewirkt, dass die Manifest-Datei in die EXE-Datei integriert wird.In der von Visual Studio generierten Manifest-Datei findet sich ein Kommentar <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->. Die darauf folgende Kommentaranweisung um den nachfolgenden Block mit dem Eintrag name=“Microsoft.Windows.Common-Controls“ muss entfernt werden, um die Einstellung für die Common Controls zu aktivieren.
Mittler zwischen den Welten
Von Anfang an hat das .NET Framework P/Invoke („Platform Invoke“) als Kommunikationsmechanismus zwischen der verwalteten Welt des Frameworks und dem nicht verwalteten Windows-API geboten. Die Nutzung dieser Schnittstelle ist auch zwingend notwendig, um auf das Taskdialog-API zuzugreifen. Voraussetzung dafür ist allerdings das Übertragen der verschiedenen Definitionen für Konstanten, Strukturen und Funktionen aus den Header-Dateien in eine Form, mit der der .NET-Compiler arbeiten kann. Die Deklaration der meisten Typen lässt sich mehr oder weniger direkt übernehmen: Int sowie UInt existieren auch im .NET Framework, aus DWord wird UInt und BOOL zu Boolean. Für Handle-Typen wie HICON, HWND und HINSTANCE sollte man das Plattform-agnostische IntPtr verwenden, da sich dahinter Zeiger verbergen, die abhängig von der Plattform 32 oder 64 Bit groß sind. Ist eine Variable als PCWSTR deklariert, also als Pointer to (Wide)String, so kann man in der Übertragung nach .NET den Typ als String deklarieren, muss dann aber das Attribut [MarshalAs(UnmanagedType.LPWStr)] vorstellen. Das zeigt dem Framework an, dass es in der Struktur einen Zeiger auf das vom .NET Framework verwaltete String-Objekt anlegen muss. Bei der Deklaration von Strukturen ist auch wichtig, ihnen das Attribut StructLayout voranzustellen. Das legt über den Eintrag Pack = 4 fest, dass die einzelnen Elemente der Struktur korrekt im Arbeitsspeicher ausgerichtet werden.Das Übertragen der C-Deklarationen von Strukturen und Funktionen in eine .NET-Sprache ist häufig mühsam und fehleranfällig. Auf Webseiten wie P/Invoke.net haben Programmierer die Deklarationen vieler Win32-APIs und Strukturen in C# und VB.NET eingestellt, die man – nach Prüfung auf Fehlerfreiheit – in eigene Programme übernehmen kann [8].Auch führen oft mehrere Wege zum Ziel. So ist die Funktion TaskDialogIndirect in commctrl.h wie folgt deklariert:<span class="hljs-function">HRESULT <span class="hljs-title">TaskDialogIndirect</span><span class="hljs-params">( </span></span>
<span class="hljs-function"><span class="hljs-params"> <span class="hljs-keyword">const</span> TASKDIALOGCONFIG *pTaskConfig, </span></span>
<span class="hljs-function"><span class="hljs-params"> <span class="hljs-keyword">int</span> *pnButton, </span></span>
<span class="hljs-function"><span class="hljs-params"> <span class="hljs-keyword">int</span> *pnRadioButton, </span></span>
<span class="hljs-function"><span class="hljs-params"> BOOL *pfVerificationFlagChecked </span></span>
<span class="hljs-function"><span class="hljs-params">)</span></span>;
Die ersten zwei Parameter müssen immer mit Werten belegt werden. Die folgenden zwei Pointer-Variablen, pnRadioButton sowie pfVerificationFlagChecked, können aber auch NULL sein. Speziell bei der letzten Variable, pfVerificationFlagChecked, hat der Wert NULL eine wahrnehmbare Konsequenz: Enthält der Taskdialog eine Checkbox, so führt der Wert NULL dazu, dass sie ausgegraut ist.Auf P/Invoke.net findet sich folgende Deklaration [9]:
[DllImport(<span class="hljs-string">"comctl32.dll"</span>, SetLastError=<span class="hljs-literal">true</span>)]
<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">extern</span> <span class="hljs-keyword">int</span> <span class="hljs-title">TaskDialogIndirect</span>(<span class="hljs-params"> </span></span>
<span class="hljs-function"><span class="hljs-params"> [In] TASKDIALOGCONFIG pTaskConfig, </span></span>
<span class="hljs-function"><span class="hljs-params"> [Out] <span class="hljs-keyword">out</span> <span class="hljs-keyword">int</span> pnButton, </span></span>
<span class="hljs-function"><span class="hljs-params"> [Out] <span class="hljs-keyword">out</span> <span class="hljs-keyword">int</span> pnRadioButton, </span></span>
<span class="hljs-function"><span class="hljs-params"> [MarshalAs( UnmanagedType.Bool </span>), Out] </span>
<span class="hljs-function"> <span class="hljs-keyword">out</span> <span class="hljs-keyword">bool</span> pfverificationFlagChecked)</span>;
Die Deklaration ist korrekt, ein Sperren des Checkbox-Feldes ist aber nicht möglich, da der letzte Parameter nie NULL ist – er zeigt immer auf eine Speicherstelle. Um das zu korrigieren, müsste der Parameter pfverificationFlagChecked als IntPtr deklariert sein. In diesem Fall kann man aber nicht einfach einen Zeiger auf die verwaltete boolesche Variable übergeben, das funktioniert unter .NET nicht.Hier erweisen sich die vom .NET Framework im Namensraum System.Runtime.InteropServices gebotenen Methoden aus der Klasse Marshal als sehr hilfreich. So allokiert Marshal.AllocHGlobal einen Bereich im nicht verwalteten Speicher und gibt einen Zeiger darauf zurück. Diesen Zeiger kann man nun als letzten Parameter an TaskDialogIndirect übergeben. Nach dem Ausführen der Funktion erlaubt es ein Aufruf von Marshal.ReadByte, den zurückgegebenen Wert in eine verwaltete boolesche Variable zurückzuschreiben. Zum Abschluss ist dann der belegte Speicherplatz durch einen Aufruf von Marshal.FreeHGlobal wieder freizugeben, da die .NET-eigene Speicherverwaltung an dieser Stelle nicht greift.Die Marshal-Klasse bietet eine Vielzahl von Methoden, um eine Brücke zwischen dem von .NET verwalteten und dem vom Win32-API- und von COM-Objekten verwendeten nicht verwalteten Speicher zu bauen. Das hilft auch an anderer Stelle in der Methode TTaskDlgButtons.GetButtons: Um in einem Taskdialog selbst definierte Schaltflächen anzeigen zu können, müssen Sie in der Struktur TASKDIALOGCONFIG im Feld pButtons einen Zeiger auf ein Array vom Typ TASKDIALOG_BUTTON übergeben. TASKDIALOG_BUTTON ist eine weitere Struktur, die aus zwei Feldern besteht: einem Integer-Wert, der die ID der Schaltfläche enthält, und einem Zeiger auf den Text, der auf der Schaltfläche angezeigt werden soll [10]. In CommCtrl.h ist die Struktur wie folgt definiert:
<span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> _<span class="hljs-title">TASKDIALOG_BUTTON</span> </span>
{
int nButtonID;
PCWSTR pszButtonText;
} TASKDIALOG_BUTTON;
Die Übertragung nach Oxygene bildet das ab:
[StructLayout(LayoutKind.Sequential, CharSet =
CharSet.Auto, Pack = <span class="hljs-number">4</span>)]
TASKDIALOG_BUTTON = <span class="hljs-keyword">public</span> <span class="hljs-keyword">record</span>
nButtonID: Int32;
[MarshalAs(UnmanagedType.LPWStr)]
pszButtonText: <span class="hljs-keyword">String</span>;
<span class="hljs-keyword">end</span>;
Über das MarshalAs-Attribut kann man sich erneut die automatische Umwandlung von System.String nach Pointer auf WideChar durch das .NET Framework zunutze machen. Die eigentliche Umwandlung aus der .NET-Klasse in ein Array aus Zeigern erfolgt dann über den Code in Listing 1.
Listing 1: Listenelemente umwandeln
<span class="hljs-regexp">//</span> Anlegen des Button-Arrays <br/>aTaskDlgButtons := new TTaskDialogButton <br/> [ButtonList.Count]; <br/><br/><span class="hljs-regexp">//</span> Größe eines Elements <br/>iSize := Marshal.SizeOf (typeOf <br/> (TTaskDialogButton)); <br/><span class="hljs-regexp">//</span> Nicht verwalteten Speicher anfordern <br/>pTaskDlgBtn := Marshal.AllocHGlobal (iSize * <br/> ButtonList.Count); <br/><br/><span class="hljs-regexp">//</span> Zeiger auf den Speicherbereich zurückgeben <br/>result := IntPtr (pTaskDlgBtn); <br/><br/><span class="hljs-keyword">for</span> iIndex: Int32 := <span class="hljs-number">0</span> to ButtonList.Count - <span class="hljs-number">1</span> <span class="hljs-keyword">do</span> begin <br/> <span class="hljs-regexp">//</span> Werte im Array belegen <br/> aTaskDlgButtons [iIndex].nButtonID := ButtonList <br/> [iIndex].ButtonID; <br/> aTaskDlgButtons [iIndex].pszButtonText := <br/> ButtonList [iIndex].ButtonText; <br/><br/> <span class="hljs-regexp">//</span> Zeiger darauf schreiben <br/> Marshal.StructureToPtr (aTaskDlgButtons [iIndex], <br/> pTaskDlgBtn + (iSize * iIndex), false); <br/>end; { <span class="hljs-keyword">for</span> }
Klickt der Anwender im Taskdialog auf eine Zeichenkette, die einen Hyperlink enthält, so sendet Windows eine Benachrichtigung TDN_HYPERLINK_CLICKED an die Callback-Funktion. Diese enthält als letzten Parameter einen Zeiger auf den angeklickten URL. Das Umwandeln dieses Zeigers in ein System.String-Objekt übernimmt die Funktion Marshal.PtrToStringUni. Die Funktion erzeugt das String-Objekt und kopiert die vom Zeiger referenzierte Zeichenkette hinein.Eine weitere Herausforderung ist, aus dem Managed Code heraus Nachrichten an den Taskdialog zu senden. Dazu dient die SendMessage-Funktion, die in Winuser.h so deklariert ist:
LRESULT SendMessage(
<span class="hljs-keyword">HWND</span> <span class="hljs-keyword">hWnd</span>,
UINT Msg,
<span class="hljs-keyword">WPARAM</span> <span class="hljs-keyword">wParam</span>,
<span class="hljs-keyword">LPARAM</span> <span class="hljs-keyword">lParam</span>)<span class="hljs-comment">; </span>
Nachrichten, die mit der SendMessage-API-Funktion an den Taskdialog geschickt werden, können zwei Arten von Parametern enthalten: zum einen Zahlen, was kein Problem darstellt, zum anderen aber auch Zeiger auf Zeichenketten, zum Beispiel um mittels der Nachricht TDM_SET_ELEMENT_TEXT den angezeigten Text zur Laufzeit zu ändern.Die Nachricht TDM_SET_ELEMENT_TEXT erwartet im letzten Parameter einen Zeiger auf den Text, der angezeigt werden soll. Hier könnten Sie auf die Methode Marshal.StringToHGlobalUni zurückgreifen, um eine Kopie der Zeichenkette im nicht verwalteten Speicher anzulegen und einen Zeiger darauf zu übergeben. Sie können sich aber auch vom .NET Framework Arbeit abnehmen lassen, indem Sie eine Funktion überladen, also mit gleichem Namen, aber unterschiedlichen Parametern deklarieren. In User32_ImportU
.pas ist die SendMessage-Funktion zweimal deklariert:
.pas ist die SendMessage-Funktion zweimal deklariert:
<span class="hljs-selector-attr">[DllImport("user32.dll", SetLastError := true, CharSet </span>
<span class="hljs-selector-attr"> := CharSet.Auto)]</span>
<span class="hljs-selector-tag">class</span> <span class="hljs-selector-tag">function</span> <span class="hljs-selector-tag">SendMessage</span> (<span class="hljs-attribute">hwnd</span>: IntPtr; <span class="hljs-attribute">msg</span>: UInt32;
wParam, <span class="hljs-attribute">lParam</span>: IntPtr) : <span class="hljs-selector-tag">IntPtr</span>; <span class="hljs-selector-tag">external</span>;
<span class="hljs-selector-attr">[DllImport("user32.dll", SetLastError := true, </span>
<span class="hljs-selector-attr"> CharSet := CharSet.Auto)]</span>
<span class="hljs-selector-tag">class</span> <span class="hljs-selector-tag">function</span> <span class="hljs-selector-tag">SendMessage</span> (<span class="hljs-attribute">hwnd</span>: IntPtr; <span class="hljs-attribute">msg</span>: UInt32; <span class="hljs-attribute">wParam</span>: IntPtr; <span class="hljs-attribute">lParam</span>: String) : <span class="hljs-selector-tag">IntPtr</span>; <span class="hljs-selector-tag">external</span>;
Die zweite Variante deklariert lParam als Typ System.String. Die Umwandlung in einen Zeiger auf String erledigt das .NET Framework beim Aufruf der Funktion automatisch.
HashSet statt Set
Die Sprache Pascal bietet mit Set einen eleganten Weg, um mit Mengen umzugehen. Sie kann einen Aufzähltyp als<span class="hljs-keyword">type</span> <span class="hljs-type">TNumber</span> = (<span class="hljs-type">One</span>, <span class="hljs-type">Two</span>, <span class="hljs-type">Three</span>);
definieren und ihn durch die Zeile
<span class="hljs-keyword">type</span> <span class="hljs-type">TNumbers </span>= set <span class="hljs-keyword">of</span> TNumber;
in einen Mengentyp umwandeln. Die Menge kann leer sein, aber auch alle drei Elemente aus TNumber je einmal enthalten. Das Hinzufügen und Entfernen geschieht über „+“ und „-“. Über eine Abfrage wie
<span class="hljs-keyword">var</span> Numbers = TNumbers;
<span class="hljs-keyword">if</span> (One <span class="hljs-keyword">in</span> Numbers) <span class="hljs-keyword">then</span>
lässt sich überprüfen, ob ein Element in der Menge enthalten ist. Die in der ursprünglichen Delphi-Version der TTaskDialog-Klasse genutzten SETs waren die einzigen Konstrukte, deren Nutzung aus C# heraus sich als schwierig erwies. Die Lösung ist, die ab dem .NET Framework 4 im Namensraum System.Collections.Generic verfügbare Klasse HashSet zu verwenden [11]. Mit ihr kann man solche Mengen und Operationen darauf auch unter .NET sehr einfach und sprachübergreifend abbilden, wie Listing 2 zeigt.
Listing 2: Mit HashSet Operationen abbilden
Type TTaskDlgButton = public <span class="hljs-class"><span class="hljs-keyword">enum</span> (<span class="hljs-title">tcbOk</span>, <span class="hljs-title">tcbYes</span>, </span><br/><span class="hljs-class"> <span class="hljs-title">tcbNo</span>);</span> <br/>Type TTaskDlgButtons = public <br/> HashSet&lt;TTaskDlgButton&gt;; <br/>Var TaskDlgButtons : TTaskDlgButtons := new <br/> TTaskDlgButtons; <br/>TaskDlgButtons.Add (TTaskDlgButton.tcbOK); <br/>If (TaskDlgButtons.Contains (tcbOK)) <span class="hljs-keyword">then</span> <br/>...
TTaskDialog im Detail
Die Klasse TTaskDialog definiert eine Fülle von Eigenschaften, Methoden und Ereignissen, um die Integration von Taskdialogen in eigene Programme so einfach wie möglich zu machen. In den Downloads zum Heft finden Sie ein Bespielprogramm TASKDIALOGDEMO.EXE, das die vielfältigen Möglichkeiten der Dialoge aufzeigt.Über die Eigenschaft DlgFlags legt man fest, welche Elemente der Taskdialog anzeigen soll:- tdfAllowDialogCancellation: Der Dialog kann durch Drücken von [ESC], [Alt]+[F4] oder Klick auf die Schließen-Schaltfläche in der Titelleiste geschlossen werden, auch wenn kein Abbrechen- oder Cancel-Button angezeigt wird.
- tdfCallbackTimer: Die Callback-Funktion erhält ungefähr alle 0,2 Sekunden eine TDN_TIMER-Nachricht (Ereignis OnTimer).
- tdfCanBeMinimized: Der Dialog kann vom Andwender minimiert werden.
- tdfEnableHyperlinks: Die Zeichenketten in der Feldern Content, ExpandedText und FooterText können Hyperlinks im Format <A HREF=“Befehlszeile“>Hyperlink Text</A> enthalten. Wird der Hyperlink angeklickt, löst das eine TDN_HYPERLINK_CLICKED-Botschaft an die Callback-Funktion aus. Dort kann das Programm den Text auswerten und beliebige Operationen ausführen, also zum Beispiel auch ein Programm starten, einen Ordner öffnen oder eben den URL an den Browser übergeben.
- tdfExpandFooterArea: Legt die Position fest, an der ExpandedText im Dialog erscheint: entweder direkt nach Content oder im unteren Teil des Dialogs.
- tdfExpandedByDefault: Führt dazu, dass ExpandedText sofort angezeigt wird.
- tdfNoDefaultRadioButton: Verfügt der Dialog über Radiobuttons, so ist keiner davon beim Dialogstart ausgewählt.
- tdfPositionRelativeToWindow: Gibt an, dass der Dialog zentriert über dem Elternfenster angezeigt wird. Gibt es kein Elternfenster oder ist die Option nicht aktiv, erscheint der Dialog in der Mitte des Monitors.
- tdfRtlLayout: Zeigt an, dass der Text von rechts nach links dargestellt wird, zum Beispiel für Arabisch und Hebräisch.
- tdfShowProgressBar: Der Dialog enthält einen Fortschrittsbalken.
- tdfShowMarqueeProgressBar: Der Fortschrittsbalken hat den Laufband-Stil.
- tdfUseCommandLinks, tdfUseCommandLinksNoIcon: Die Schaltflächen, die über die Methode AddButton zum Dialog hinzugefügt werden, werden als Kommandoschaltfläche (Command Link) angezeigt; entweder mit vorangestelltem Pfeilsymbol (tdfUseCommandLinks) oder ohne (tdfUseCommandLinksNoIcon).
- tdfUseHIconMain, tdfUseHIconFooter: Verwendet zur Anzeige der Icons im Dialogkopf (tdfUseHIconMain) und -fuß (tdfUseHIconFooter) jeweils das Objekt, dessen Handle im Feld MainIcon oder FooterIcon übergeben wurde.
- tdfVerificationFlagChecked: Wird eine Checkbox angezeigt (VerificationText ist mit einem Wert belegt), dann ist sie beim Öffnen des Dialogs angehakt.
Zusammenspiel mit WinForms und WPF
Taskdialoge „kennen“ GUI-Frameworks wie WinForms oder WPF nicht. Das Zusammenspiel zwischen .NET-Programmen mit WinForms- oder WPF-basiertem GUI und Taskdialogen ist aber kein Problem: Wird dem Taskdialog beim Aufruf das Fenster-Handle des Elternfensters übergeben, so wird automatisch das Elternfenster deaktiviert, solange der Taskdialog aktiv ist; der Taskdialog wird also modal dargestellt.Aus diesem Grund existiert die Methode TTaskDialog.Execute auch in drei Varianten: Der Aufruf ohne Parameter oder mit einem Fenster-Handle entspricht dem Standardvorgehen für API-basierte Programme. Beim Aufruf ohne Fenster-Handle wird der Windows-Desktop zum Elternfenster, das durch einen Aufruf der API-Funktion GetDesktopWindow in TTaskDialog.ShowTaskDialog ermittelt wird.Alternativ kann aber ein Objekt vom Typ System.Windows.Forms.IWin32Window (WinForms) oder System.Windows.Window (WPF) übergeben werden. In diesem Fall ermittelt die jeweilige Methode das Fenster-Handle des Elternfensters und übergibt es an den Taskdialog.Fazit
Taskdialoge können dem Programmierer einige Arbeit abnehmen, wenn es um die Gestaltung komplexerer Dialoge geht. Mithilfe der hier vorgestellten Assembly können auch .NET-Programme auf recht komfortable Weise von dieser Arbeitserleichterung profitieren. Probieren Sie es bei Ihrem nächsten Projekt aus!Fussnoten
- Beschreibung von MessageBoxTimeOut, http://www.dotnetpro.de/SL1908TaskDialog1
- Die TaskDialog-Funktion, http://www.dotnetpro.de/SL1908TaskDialog2
- Die TaskDialogIndirect-Funktion, http://www.dotnetpro.de/SL1908TaskDialog3
- Die TASKDIALOGCONFIG-Struktur, http://www.dotnetpro.de/SL1908TaskDialog4
- Doku zu Taskdialogen mit Verweis auf das Windows API Code Pack, http://www.dotnetpro.de/SL1908TaskDialog5
- Windows API Code Pack bei NuGet, http://www.dotnetpro.de/SL1908TaskDialog6
- RemObjects Elements Compiler, http://www.dotnetpro.de/SL1908TaskDialog7
- PInvoke.net, .NET-Deklarationen von Windows-APIs, http://www.pinvoke.net
- Die TaskDialogIndirect-Funktion nach C# übertragen, http://www.dotnetpro.de/SL1908TaskDialog8
- Die TASKDIALOG_BUTTON-Struktur, http://www.dotnetpro.de/SL1908TaskDialog9
- Die Klasse HashSet
, http://www.dotnetpro.de/SL1908TaskDialog10