2 using System
.Collections
.Generic
;
3 using System
.ComponentModel
;
4 using System
.Diagnostics
;
5 using System
.Runtime
.InteropServices
;
8 using System
.Windows
.Interop
;
9 using System
.Windows
.Markup
;
10 using Microsoft
.SDK
.Samples
.VistaBridge
.Library
;
11 using Microsoft
.SDK
.Samples
.VistaBridge
.Interop
;
12 using System
.Security
.Permissions
;
14 namespace Microsoft
.SDK
.Samples
.VistaBridge
.Library
17 /// Encapsulates a new-to-Vista Win32 TaskDialog window
18 /// - a powerful successor to the MessageBox available
19 /// in previous versions of Windows.
21 [SecurityPermissionAttribute(SecurityAction
.InheritanceDemand
, Flags
= SecurityPermissionFlag
.UnmanagedCode
)]
22 [ContentProperty("Controls")]
23 public class TaskDialog
: IDialogControlHost
, IDisposable
25 // Global instance of TaskDialog, to be used by static Show() method.
26 // As most parameters of a dialog created via static Show() will have
27 // identical parameters, we'll create one TaskDialog and treat it
28 // as a NativeTaskDialog generator for all static Show() calls.
29 private static TaskDialog staticDialog
;
31 // Main current native dialog.
32 private NativeTaskDialog nativeDialog
;
34 private List
<TaskDialogButtonBase
> buttons
;
35 private List
<TaskDialogButtonBase
> radioButtons
;
36 private List
<TaskDialogButtonBase
> commandLinks
;
37 private Window ownerWindow
;
39 #region Public Properties
41 public event EventHandler
<TaskDialogTickEventArgs
> Tick
;
42 public event EventHandler
<TaskDialogHyperlinkClickedEventArgs
> HyperlinkClick
;
43 public event EventHandler
<TaskDialogClosingEventArgs
> Closing
;
44 public event EventHandler HelpInvoked
;
45 public event EventHandler Opened
;
49 get { return ownerWindow; }
52 ThrowIfDialogShowing("Dialog owner cannot be modified while dialog is showing.");
57 // Main content (maps to MessageBox's "message").
58 private string content
;
61 get { return content; }
64 // Set local value, then update native dialog if showing.
66 if (NativeDialogShowing
)
67 nativeDialog
.UpdateContent(content
);
71 private string instruction
;
72 public string Instruction
74 get { return instruction; }
77 // Set local value, then update native dialog if showing.
79 if (NativeDialogShowing
)
80 nativeDialog
.UpdateInstruction(instruction
);
84 private string caption
;
87 get { return caption; }
90 ThrowIfDialogShowing("Dialog caption can't be set while dialog is showing.");
95 private string footerText
;
96 public string FooterText
98 get { return footerText; }
101 // Set local value, then update native dialog if showing.
103 if (NativeDialogShowing
)
104 nativeDialog
.UpdateFooterText(footerText
);
108 private string checkBoxText
;
109 public string CheckBoxText
111 get { return checkBoxText; }
114 ThrowIfDialogShowing("Checkbox text can't be set while dialog is showing.");
115 checkBoxText
= value;
119 private string expandedText
;
120 public string ExpandedText
122 get { return expandedText; }
125 // Set local value, then update native dialog if showing.
126 expandedText
= value;
127 if (NativeDialogShowing
)
128 nativeDialog
.UpdateExpandedText(expandedText
);
132 private bool expanded
;
135 get { return expanded; }
138 ThrowIfDialogShowing("Expanded state of the dialog can't be modified while dialog is showing.");
143 private string expandedControlText
;
144 public string ExpandedControlText
146 get { return expandedControlText; }
149 ThrowIfDialogShowing("Expanded control text can't be set while dialog is showing.");
150 expandedControlText
= value;
154 private string collapsedControlText
;
155 public string CollapsedControlText
157 get { return collapsedControlText; }
160 ThrowIfDialogShowing("Collapsed control text can't be set while dialog is showing.");
161 collapsedControlText
= value;
165 private bool cancelable
;
166 public bool Cancelable
168 get { return cancelable; }
171 ThrowIfDialogShowing("Cancelable can't be set while dialog is showing.");
176 private TaskDialogStandardIcon mainIcon
;
177 public TaskDialogStandardIcon MainIcon
179 get { return mainIcon; }
182 // Set local value, then update native dialog if showing.
184 if (NativeDialogShowing
)
185 nativeDialog
.UpdateMainIcon(mainIcon
);
189 private TaskDialogStandardIcon footerIcon
;
190 public TaskDialogStandardIcon FooterIcon
192 get { return footerIcon; }
195 // Set local value, then update native dialog if showing.
197 if (NativeDialogShowing
)
198 nativeDialog
.UpdateFooterIcon(footerIcon
);
202 private TaskDialogStandardButtons standardButtons
= TaskDialogStandardButtons
.None
;
203 public TaskDialogStandardButtons StandardButtons
205 get { return standardButtons; }
208 ThrowIfDialogShowing("Standard buttons can't be set while dialog is showing.");
209 standardButtons
= value;
213 private DialogControlCollection
<TaskDialogControl
> controls
;
214 public DialogControlCollection
<TaskDialogControl
> Controls
216 // "Show protection" provided by collection itself,
217 // as well as individual controls.
218 get { return controls; }
221 private bool hyperlinksEnabled
;
222 public bool HyperlinksEnabled
224 get { return hyperlinksEnabled; }
227 ThrowIfDialogShowing("Hyperlinks can't be enabled/disabled while dialog is showing.");
228 hyperlinksEnabled
= value;
232 private bool? checkBoxChecked
= null;
233 public bool? CheckBoxChecked
237 if (!checkBoxChecked
.HasValue
)
240 return checkBoxChecked
;
244 // Set local value, then update native dialog if showing.
245 checkBoxChecked
= value;
246 if (NativeDialogShowing
)
247 nativeDialog
.UpdateCheckBoxChecked(checkBoxChecked
.Value
);
251 private TaskDialogExpandedInformationLocation expansionMode
;
252 public TaskDialogExpandedInformationLocation ExpansionMode
254 get { return expansionMode; }
257 ThrowIfDialogShowing("Expanded information mode can't be set while dialog is showing.");
258 expansionMode
= value;
262 private TaskDialogStartupLocation startupLocation
;
263 public TaskDialogStartupLocation StartupLocation
265 get { return startupLocation; }
268 ThrowIfDialogShowing("Startup location can't be changed while dialog is showing.");
269 startupLocation
= value;
272 private TaskDialogProgressBar progressBar
;
273 public TaskDialogProgressBar ProgressBar
275 get { return progressBar; }
278 ThrowIfDialogShowing("Progress bar can't be changed while dialog is showing");
281 if (value.HostingDialog
!= null)
282 throw new InvalidOperationException("Progress bar cannot be hosted in multiple dialogs.");
284 // Cannot have progress bar and marquee in same dialog.
287 throw new NotSupportedException("Dialog cannot contain marquee progress bar and a regular progress bar.");
289 value.HostingDialog
= this;
294 private TaskDialogMarquee marquee
;
295 public TaskDialogMarquee Marquee
297 get { return marquee; }
300 ThrowIfDialogShowing("Marquee can't be changed while dialog is showing.");
303 if (value.HostingDialog
!= null)
304 throw new InvalidOperationException("Marquee cannot be hosted in multiple dialogs.");
306 // Cannot have progress bar and marquee in same dialog.
307 if (progressBar
!= null)
309 throw new NotSupportedException("Dialog cannot contain marquee progress bar and a regular progress bar.");
311 value.HostingDialog
= this;
325 if (!Helpers
.RunningOnVista
)
326 throw new PlatformNotSupportedException("Task Dialog requires Windows Vista or later.");
328 // Initialize various data structs.
329 controls
= new DialogControlCollection
<TaskDialogControl
>(this);
330 buttons
= new List
<TaskDialogButtonBase
>();
331 radioButtons
= new List
<TaskDialogButtonBase
>();
332 commandLinks
= new List
<TaskDialogButtonBase
>();
339 #region Static Show Methods
341 // STATIC SHOW() METHODS -- these all forward to ShowCoreStatic()
342 public static TaskDialogResult
Show(string msg
)
344 return ShowCoreStatic(
346 TaskDialogDefaults
.MainInstruction
,
347 TaskDialogDefaults
.Caption
);
350 public static TaskDialogResult
Show(string msg
, string mainInstruction
)
352 return ShowCoreStatic(
353 msg
, mainInstruction
,
354 TaskDialogDefaults
.Caption
);
357 public static TaskDialogResult
Show(string msg
, string mainInstruction
, string caption
)
359 return ShowCoreStatic(msg
, mainInstruction
, caption
);
363 #region Instance Show Methods
364 // INSTANCE SHOW() METHODS -- these all forward to ShowCore()
366 public TaskDialogResult
Show()
372 #region Core Show Logic
374 // CORE SHOW METHODS:
375 // All static Show() calls forward here -
376 // it is responsible for retrieving
377 // or creating our cached TaskDialog instance, getting it configured,
378 // and in turn calling the appropriate instance Show.
380 private static TaskDialogResult
ShowCoreStatic(
382 string mainInstruction
,
385 // If no instance cached yet, create it.
386 if (staticDialog
== null)
388 // New TaskDialog will automatically pick up defaults when
389 // a new config structure is created as part of ShowCore().
390 staticDialog
= new TaskDialog();
393 // Set the few relevant properties,
394 // and go with the defaults for the others.
395 staticDialog
.content
= msg
;
396 staticDialog
.instruction
= mainInstruction
;
397 staticDialog
.caption
= caption
;
399 return staticDialog
.Show();
402 private TaskDialogResult
ShowCore()
404 TaskDialogResult result
;
408 // Populate control lists, based on current
409 // contents - note we are somewhat late-bound
410 // on our control lists, to support XAML scenarios.
411 SortDialogControls();
413 // First, let's make sure it even makes
414 // sense to try a show.
415 ValidateCurrentDialogSettings();
417 // Create settings object for new dialog,
418 // based on current state.
419 NativeTaskDialogSettings settings
=
420 new NativeTaskDialogSettings();
421 ApplyCoreSettings(settings
);
422 ApplySupplementalSettings(settings
);
425 // NOTE: this is a BLOCKING call; the dialog proc callbacks
426 // will be executed by the same thread as the
427 // Show() call before the thread of execution
428 // contines to the end of this method.
429 nativeDialog
= new NativeTaskDialog(settings
, this);
430 nativeDialog
.NativeShow();
432 // Build and return dialog result to public API - leaving it
433 // null after an exception is thrown is fine in this case
434 result
= ConstructDialogResult(nativeDialog
);
435 checkBoxChecked
= result
.CheckBoxChecked
;
446 // Helper that looks at the current state of the TaskDialog and verifies
447 // that there aren't any abberant combinations of properties.
448 // NOTE that this method is designed to throw
449 // rather than return a bool.
450 private void ValidateCurrentDialogSettings()
452 if (checkBoxChecked
.HasValue
&&
453 checkBoxChecked
.Value
== true &&
454 String
.IsNullOrEmpty(checkBoxText
))
455 throw new InvalidOperationException(
456 "Checkbox text must be provided to enable the dialog checkbox.");
458 // Progress bar validation.
459 // Make sure we don't have both a progress bar and marquee,
460 // and that the progress bar values are valid.
461 // the Win32 API will valiantly try to rationalize
462 // bizarre min/max/value combinations, but we'll save
463 // it the trouble by validating.
464 if (progressBar
!= null && marquee
!= null)
465 throw new NotSupportedException(
466 "Can't display both a progress bar and a marquee in same dialog.");
467 if (progressBar
!= null)
468 if (!progressBar
.HasValidValues
)
469 throw new ArgumentException(
470 "Progress bar must have a value between the minimum and maxium values.");
472 // Validate Buttons collection.
473 // Make sure we don't have buttons AND
474 // command-links - the Win32 API treats them as different
475 // flavors of a single button struct.
476 if (buttons
.Count
> 0 && commandLinks
.Count
> 0)
477 throw new NotSupportedException(
478 "Dialog cannot display both non-standard buttons and command links.");
479 if (buttons
.Count
> 0 && standardButtons
!= TaskDialogStandardButtons
.None
)
480 throw new NotSupportedException(
481 "Dialog cannot display both non-standard buttons and standard buttons.");
484 // Analyzes the final state of the NativeTaskDialog instance and creates the
485 // final TaskDialogResult that will be returned from the public API
486 private TaskDialogResult
ConstructDialogResult(NativeTaskDialog native
)
488 Debug
.Assert(native
.ShowState
== NativeDialogShowState
.Closed
, "dialog result being constructed for unshown dialog.");
490 string customButton
= null;
491 string radioButton
= null;
492 bool isCheckBoxChecked
= false;
493 TaskDialogButtonBase button
;
495 TaskDialogStandardButton standardButton
= MapButtonIdToStandardButton(native
.SelectedButtonID
);
497 // If returned ID isn't a standard button, let's fetch
498 if (standardButton
== TaskDialogStandardButton
.None
)
500 button
= GetButtonForId(native
.SelectedButtonID
);
502 throw new InvalidOperationException("Received bad control ID from Win32 callback.");
503 customButton
= button
.Name
;
506 // If there were radio buttons and one was selected, figure out which one
507 if (radioButtons
.Count
> 0 && native
.SelectedRadioButtonID
!= SafeNativeMethods
.NO_DEFAULT_BUTTON_SPECIFIED
)
509 button
= GetButtonForId(native
.SelectedRadioButtonID
);
511 throw new InvalidOperationException("Received bad control ID from Win32 callback.");
512 radioButton
= button
.Name
;
514 isCheckBoxChecked
= native
.CheckBoxChecked
;
516 return new TaskDialogResult(
525 if (!NativeDialogShowing
)
526 throw new InvalidOperationException(
527 "Attempting to close a non-showing dialog.");
529 nativeDialog
.NativeClose();
530 // TaskDialog's own cleanup code -
531 // which runs post show - will handle disposal of native dialog.
536 #region Configuration Construction
538 private void ApplyCoreSettings(NativeTaskDialogSettings settings
)
540 ApplyGeneralNativeConfiguration(settings
.NativeConfiguration
);
541 ApplyTextConfiguration(settings
.NativeConfiguration
);
542 ApplyOptionConfiguration(settings
.NativeConfiguration
);
543 ApplyControlConfiguration(settings
);
546 private void ApplyGeneralNativeConfiguration(SafeNativeMethods
.TASKDIALOGCONFIG dialogConfig
)
548 // If an owner wasn't specifically specified,
549 // we'll use the app's main window.
550 Window currentOwner
= ownerWindow
;
551 if (currentOwner
== null)
552 currentOwner
= Helpers
.GetDefaultOwnerWindow();
554 if (currentOwner
!= null)
555 dialogConfig
.hwndParent
= (new WindowInteropHelper(currentOwner
)).Handle
;
557 // Other miscellaneous sets.
558 dialogConfig
.MainIcon
=
559 new SafeNativeMethods
.TASKDIALOGCONFIG_ICON_UNION((int)mainIcon
);
560 dialogConfig
.FooterIcon
=
561 new SafeNativeMethods
.TASKDIALOGCONFIG_ICON_UNION((int)footerIcon
);
562 dialogConfig
.dwCommonButtons
=
563 (SafeNativeMethods
.TASKDIALOG_COMMON_BUTTON_FLAGS
)standardButtons
;
566 private void ApplyTextConfiguration(SafeNativeMethods
.TASKDIALOGCONFIG dialogConfig
)
568 // Set important text properties -
569 // note that nulls or empty strings are fine here.
570 // TODO: Rationalize default handling -
571 // do we even need to set defaults first?
572 dialogConfig
.pszContent
= content
;
573 dialogConfig
.pszWindowTitle
= caption
;
574 dialogConfig
.pszMainInstruction
= instruction
;
575 dialogConfig
.pszExpandedInformation
= expandedText
;
576 dialogConfig
.pszExpandedControlText
= expandedControlText
;
577 dialogConfig
.pszCollapsedControlText
= collapsedControlText
;
578 dialogConfig
.pszFooter
= footerText
;
579 dialogConfig
.pszVerificationText
= checkBoxText
;
583 private void ApplyOptionConfiguration(SafeNativeMethods
.TASKDIALOGCONFIG dialogConfig
)
585 // Handle options - start with no options set.
586 SafeNativeMethods
.TASKDIALOG_FLAGS options
= SafeNativeMethods
.TASKDIALOG_FLAGS
.NONE
;
588 options
|= SafeNativeMethods
.TASKDIALOG_FLAGS
.TDF_ALLOW_DIALOG_CANCELLATION
;
589 if (checkBoxChecked
.HasValue
&& checkBoxChecked
.Value
)
590 options
|= SafeNativeMethods
.TASKDIALOG_FLAGS
.TDF_VERIFICATION_FLAG_CHECKED
;
591 if (hyperlinksEnabled
)
592 options
|= SafeNativeMethods
.TASKDIALOG_FLAGS
.TDF_ENABLE_HYPERLINKS
;
594 options
|= SafeNativeMethods
.TASKDIALOG_FLAGS
.TDF_EXPANDED_BY_DEFAULT
;
596 options
|= SafeNativeMethods
.TASKDIALOG_FLAGS
.TDF_CALLBACK_TIMER
;
597 if (startupLocation
== TaskDialogStartupLocation
.CenterOwner
)
598 options
|= SafeNativeMethods
.TASKDIALOG_FLAGS
.TDF_POSITION_RELATIVE_TO_WINDOW
;
600 // Note: no validation required, as we allow this to
601 // be set even if there is no expanded information
602 // text because that could be added later.
603 // Default for Win32 API is to expand into (and after)
605 if (expansionMode
== TaskDialogExpandedInformationLocation
.ExpandFooter
)
606 options
|= SafeNativeMethods
.TASKDIALOG_FLAGS
.TDF_EXPAND_FOOTER_AREA
;
608 // Finally, apply options to config.
609 dialogConfig
.dwFlags
= options
;
612 // Builds the actual configuration
613 // that the NativeTaskDialog (and underlying Win32 API)
614 // expects, by parsing the various control
615 // lists, marshalling to the unmanaged heap, etc.
617 private void ApplyControlConfiguration(NativeTaskDialogSettings settings
)
619 // Deal with progress bars/marquees.
621 settings
.NativeConfiguration
.dwFlags
|= SafeNativeMethods
.TASKDIALOG_FLAGS
.TDF_SHOW_MARQUEE_PROGRESS_BAR
;
622 else if (progressBar
!= null)
623 settings
.NativeConfiguration
.dwFlags
|= SafeNativeMethods
.TASKDIALOG_FLAGS
.TDF_SHOW_PROGRESS_BAR
;
625 // Build the native struct arrays that NativeTaskDialog
626 // needs - though NTD will handle
627 // the heavy lifting marshalling to make sure
628 // all the cleanup is centralized there.
629 if (buttons
.Count
> 0 || commandLinks
.Count
> 0)
631 // These are the actual arrays/lists of
632 // the structs that we'll copy to the
634 List
<TaskDialogButtonBase
> sourceList
= (
635 buttons
.Count
> 0 ? buttons
: commandLinks
);
636 settings
.Buttons
= BuildButtonStructArray(sourceList
);
638 // Apply option flag that forces all
639 // custom buttons to render as command links.
640 if (commandLinks
.Count
> 0)
641 settings
.NativeConfiguration
.dwFlags
|=
642 SafeNativeMethods
.TASKDIALOG_FLAGS
.TDF_USE_COMMAND_LINKS
;
644 // Set default button and add elevation icons
645 // to appropriate buttons.
646 settings
.NativeConfiguration
.nDefaultButton
=
647 FindDefaultButtonId(sourceList
);
649 ApplyElevatedIcons(settings
, sourceList
);
651 if (radioButtons
.Count
> 0)
653 settings
.RadioButtons
= BuildButtonStructArray(radioButtons
);
655 // Set default radio button - radio buttons don't support.
656 int defaultRadioButton
= FindDefaultButtonId(radioButtons
);
657 settings
.NativeConfiguration
.nDefaultRadioButton
=
660 if (defaultRadioButton
==
661 SafeNativeMethods
.NO_DEFAULT_BUTTON_SPECIFIED
)
662 settings
.NativeConfiguration
.dwFlags
|= SafeNativeMethods
.TASKDIALOG_FLAGS
.TDF_NO_DEFAULT_RADIO_BUTTON
;
666 private static SafeNativeMethods
.TASKDIALOG_BUTTON
[] BuildButtonStructArray(List
<TaskDialogButtonBase
> controls
)
668 SafeNativeMethods
.TASKDIALOG_BUTTON
[] buttonStructs
;
669 TaskDialogButtonBase button
;
671 int totalButtons
= controls
.Count
;
672 buttonStructs
= new SafeNativeMethods
.TASKDIALOG_BUTTON
[totalButtons
];
673 for (int i
= 0; i
< totalButtons
; i
++)
675 button
= controls
[i
];
676 buttonStructs
[i
] = new SafeNativeMethods
.TASKDIALOG_BUTTON(button
.Id
, button
.ToString());
678 return buttonStructs
;
681 // Searches list of controls and returns the ID of
682 // the default control, or null if no default was specified.
683 private static int FindDefaultButtonId(List
<TaskDialogButtonBase
> controls
)
685 int found
= SafeNativeMethods
.NO_DEFAULT_BUTTON_SPECIFIED
;
686 foreach (TaskDialogButtonBase control
in controls
)
690 // Check if we've found a default in this list already.
691 if (found
!= SafeNativeMethods
.NO_DEFAULT_BUTTON_SPECIFIED
)
692 throw new InvalidOperationException("Can't have more than one default button of a given type.");
699 private static void ApplyElevatedIcons(NativeTaskDialogSettings settings
, List
<TaskDialogButtonBase
> controls
)
701 foreach (TaskDialogButton control
in controls
)
703 if (control
.ShowElevationIcon
)
705 if (settings
.ElevatedButtons
== null)
706 settings
.ElevatedButtons
= new List
<int>();
707 settings
.ElevatedButtons
.Add(control
.Id
);
712 private void ApplySupplementalSettings(NativeTaskDialogSettings settings
)
714 if (progressBar
!= null)
716 settings
.ProgressBarMinimum
= progressBar
.Minimum
;
717 settings
.ProgressBarMaximum
= progressBar
.Maximum
;
718 settings
.ProgressBarState
= progressBar
.State
;
719 settings
.ProgressBarValue
= progressBar
.Value
;
721 else if (marquee
!= null)
722 settings
.ProgressBarState
= marquee
.State
;
724 if (HelpInvoked
!= null)
725 settings
.InvokeHelp
= true;
728 // Here we walk our controls collection and
729 // sort the various controls by type.
730 private void SortDialogControls()
732 foreach (TaskDialogControl control
in controls
)
734 if (control
is TaskDialogButtonBase
&& String
.IsNullOrEmpty(((TaskDialogButtonBase
)control
).Text
))
736 if (control
is TaskDialogCommandLink
&& String
.IsNullOrEmpty(((TaskDialogCommandLink
)control
).Instruction
))
737 throw new InvalidOperationException(
738 "Button text must be non-empty");
741 // Loop through child controls
742 // and sort the controls based on type.
743 if (control
is TaskDialogCommandLink
)
745 commandLinks
.Add((TaskDialogCommandLink
)control
);
747 else if (control
is TaskDialogRadioButton
)
749 if (radioButtons
== null)
750 radioButtons
= new List
<TaskDialogButtonBase
>();
751 radioButtons
.Add((TaskDialogRadioButton
)control
);
753 else if (control
is TaskDialogButtonBase
)
756 buttons
= new List
<TaskDialogButtonBase
>();
757 buttons
.Add((TaskDialogButtonBase
)control
);
759 else if (control
is TaskDialogProgressBar
)
761 if (progressBar
!= null)
762 throw new InvalidOperationException(
763 "Can't have more than one progress bar in dialog.");
764 progressBar
= (TaskDialogProgressBar
)control
;
766 else if (control
is TaskDialogMarquee
)
769 throw new InvalidOperationException(
770 "Can't have more than one marquee in dialog.");
771 marquee
= (TaskDialogMarquee
)control
;
775 throw new ArgumentException("Unknown dialog control type.");
784 // Helper to map the standard button IDs returned by
785 // TaskDialogIndirect to the standard button ID enum -
786 // note that we can't just cast, as the Win32
787 // typedefs differ incoming and outgoing.
789 private static TaskDialogStandardButton
MapButtonIdToStandardButton(int id
)
791 switch ((SafeNativeMethods
.TASKDIALOG_COMMON_BUTTON_RETURN_ID
)id
)
793 case SafeNativeMethods
.TASKDIALOG_COMMON_BUTTON_RETURN_ID
.IDOK
:
794 return TaskDialogStandardButton
.Ok
;
795 case SafeNativeMethods
.TASKDIALOG_COMMON_BUTTON_RETURN_ID
.IDCANCEL
:
796 return TaskDialogStandardButton
.Cancel
;
797 case SafeNativeMethods
.TASKDIALOG_COMMON_BUTTON_RETURN_ID
.IDABORT
:
798 // Included for completeness in API -
799 // we can't pass in an Abort standard button.
800 return TaskDialogStandardButton
.None
;
801 case SafeNativeMethods
.TASKDIALOG_COMMON_BUTTON_RETURN_ID
.IDRETRY
:
802 return TaskDialogStandardButton
.Retry
;
803 case SafeNativeMethods
.TASKDIALOG_COMMON_BUTTON_RETURN_ID
.IDIGNORE
:
804 // Included for completeness in API -
805 // we can't pass in an Ignore standard button.
806 return TaskDialogStandardButton
.None
;
807 case SafeNativeMethods
.TASKDIALOG_COMMON_BUTTON_RETURN_ID
.IDYES
:
808 return TaskDialogStandardButton
.Yes
;
809 case SafeNativeMethods
.TASKDIALOG_COMMON_BUTTON_RETURN_ID
.IDNO
:
810 return TaskDialogStandardButton
.No
;
811 case SafeNativeMethods
.TASKDIALOG_COMMON_BUTTON_RETURN_ID
.IDCLOSE
:
812 return TaskDialogStandardButton
.Close
;
814 return TaskDialogStandardButton
.None
;
818 private void ThrowIfDialogShowing(string message
)
820 if (NativeDialogShowing
)
821 throw new NotSupportedException(message
);
824 private bool NativeDialogShowing
828 return (nativeDialog
!= null)
829 && (nativeDialog
.ShowState
== NativeDialogShowState
.Showing
||
830 nativeDialog
.ShowState
== NativeDialogShowState
.Closing
);
834 // NOTE: we are going to require names be unique
835 // across both buttons and radio buttons,
836 // even though the Win32 API allows them to be separate.
837 private TaskDialogButtonBase
GetButtonForId(int id
)
839 return (TaskDialogButtonBase
)controls
.GetControlbyId(id
);
844 #region IDialogControlHost Members
846 // We're explicitly implementing this interface
847 // as the user will never need to know about it
848 // or use it directly - it is only for the internal
849 // implementation of "pseudo controls" within
852 // Called whenever controls are being added
853 // to or removed from the dialog control collection.
854 bool IDialogControlHost
.IsCollectionChangeAllowed()
856 // Only allow additions to collection if dialog is NOT showing.
857 return !NativeDialogShowing
;
860 // Called whenever controls have been added or removed.
861 void IDialogControlHost
.ApplyCollectionChanged()
863 // If we're showing, we should never get here -
864 // the changing notification would have thrown and the
865 // property would not have been changed.
866 Debug
.Assert(!NativeDialogShowing
,
867 "Collection changed notification received despite show state of dialog");
870 // Called when a control currently in the collection
871 // has a property changing - this is
872 // basically to screen out property changes that
873 // cannot occur while the dialog is showing
874 // because the Win32 API has no way for us to
875 // propagate the changes until we re-invoke the Win32 call.
876 bool IDialogControlHost
.IsControlPropertyChangeAllowed(string propertyName
, DialogControl control
)
878 Debug
.Assert(control
is TaskDialogControl
,
879 "Property changing for a control that is not a TaskDialogControl-derived type");
880 Debug
.Assert(propertyName
!= "Name",
881 "Name changes at any time are not supported - public API should have blocked this");
883 bool canChange
= false;
885 if (!NativeDialogShowing
)
889 // If the dialog is showing, we can only
890 // allow some properties to change.
891 switch (propertyName
)
893 // Properties that CAN'T be changed while dialog is showing.
899 // Properties that CAN be changed while dialog is showing.
900 case "ShowElevationIcon":
905 Debug
.Assert(true, "Unknown property name coming through property changing handler");
912 // Called when a control currently in the collection
913 // has a property changed - this handles propagating
914 // the new property values to the Win32 API.
915 // If there isn't a way to change the Win32 value, then we
916 // should have already screened out the property set
917 // in NotifyControlPropertyChanging.
918 void IDialogControlHost
.ApplyControlPropertyChange(string propertyName
, DialogControl control
)
920 // We only need to apply changes to the
921 // native dialog when it actually exists.
922 if (NativeDialogShowing
)
924 // One of the progress bar flavors?
925 if (control
is TaskDialogMarquee
)
927 if (propertyName
== "State")
928 nativeDialog
.UpdateProgressBarState(marquee
.State
);
930 Debug
.Assert(true, "Unknown property being set");
932 else if (control
is TaskDialogProgressBar
)
934 if (!progressBar
.HasValidValues
)
935 throw new ArgumentException(
936 "Progress bar must have a value between Minimum and Maximum.");
937 switch (propertyName
)
940 nativeDialog
.UpdateProgressBarState(progressBar
.State
);
943 nativeDialog
.UpdateProgressBarValue(progressBar
.Value
);
947 nativeDialog
.UpdateProgressBarRange();
950 Debug
.Assert(true, "Unknown property being set");
954 else if (control
is TaskDialogButton
)
956 TaskDialogButton button
= (TaskDialogButton
)control
;
957 switch (propertyName
)
959 case "ShowElevationIcon":
960 nativeDialog
.UpdateElevationIcon(button
.Id
, button
.ShowElevationIcon
);
963 if (control
is TaskDialogRadioButton
)
965 nativeDialog
.UpdateRadioButtonEnabled(button
.Id
, button
.Enabled
);
969 nativeDialog
.UpdateButtonEnabled(button
.Id
, button
.Enabled
);
973 Debug
.Assert(true, "Unknown property being set");
979 // Do nothing with property change -
980 // note that this shouldn't ever happen, we should have
981 // either thrown on the changing event, or we handle above.
982 Debug
.Assert(true, "Control property changed notification not handled properly - being ignored");
990 #region Event Percolation Methods
992 // All Raise*() methods are called by the
993 // NativeTaskDialog when various pseudo-controls
995 internal void RaiseButtonClickEvent(int id
)
997 // First check to see if the ID matches a custom button.
998 TaskDialogButtonBase button
= GetButtonForId(id
);
1000 // If a custom button was found,
1001 // raise the event - if not, it's a standard button, and
1002 // we don't support custom event handling for the standard buttons
1004 button
.RaiseClickEvent();
1007 internal void RaiseHyperlinkClickEvent(string link
)
1009 EventHandler
<TaskDialogHyperlinkClickedEventArgs
> handler
= HyperlinkClick
;
1010 if (handler
!= null)
1012 handler(this, new TaskDialogHyperlinkClickedEventArgs(link
));
1016 // Gives event subscriber a chance to prevent
1017 // the dialog from closing, based on
1018 // the current state of the app and the button
1019 // used to commit. Note that we don't
1020 // have full access at this stage to
1021 // the full dialog state.
1022 internal int RaiseClosingEvent(int id
)
1024 EventHandler
<TaskDialogClosingEventArgs
> handler
= Closing
;
1025 if (handler
!= null)
1028 TaskDialogButtonBase customButton
= null;
1029 TaskDialogClosingEventArgs e
= new TaskDialogClosingEventArgs();
1031 // Try to identify the button - is it a standard one?
1032 e
.StandardButton
= MapButtonIdToStandardButton(id
);
1034 // If not, it had better be a custom button...
1035 if (e
.StandardButton
== TaskDialogStandardButton
.None
)
1037 customButton
= GetButtonForId(id
);
1039 // ... or we have a problem.
1040 if (customButton
== null)
1041 throw new InvalidOperationException("Bad button ID in closing event.");
1042 e
.CustomButton
= customButton
.Name
;
1045 // Raise the event and determine how to proceed.
1048 return (int)HRESULT
.S_FALSE
;
1050 // It's okay to let the dialog close.
1051 return (int)HRESULT
.S_OK
;
1054 internal void RaiseHelpInvokedEvent()
1056 EventHandler handler
= HelpInvoked
;
1057 if (handler
!= null)
1058 handler(this, EventArgs
.Empty
);
1061 internal void RaiseOpenedEvent()
1063 EventHandler handler
= Opened
;
1064 if (handler
!= null)
1065 handler(this, EventArgs
.Empty
);
1068 internal void RaiseTickEvent(int ticks
)
1070 EventHandler
<TaskDialogTickEventArgs
> handler
= Tick
;
1071 if (handler
!= null)
1072 handler(this, new TaskDialogTickEventArgs(ticks
));
1077 #region Cleanup Code
1079 // Cleans up data and structs from a single
1080 // native dialog Show() invocation.
1081 private void CleanUp()
1083 // Reset values that would be considered
1084 // 'volatile' in a given instance.
1085 if (progressBar
!= null)
1087 progressBar
.Reset();
1089 if (marquee
!= null)
1094 // Clean out sorted control lists -
1095 // though we don't of course clear the main controls collection,
1096 // so the controls are still around; we'll
1097 // resort on next show, since the collection may have changed.
1098 if (buttons
!= null)
1100 if (commandLinks
!= null)
1101 commandLinks
.Clear();
1102 if (radioButtons
!= null)
1103 radioButtons
.Clear();
1107 // Have the native dialog clean up the rest.
1108 if (nativeDialog
!= null)
1109 nativeDialog
.Dispose();
1113 // Dispose pattern - cleans up data and structs for
1114 // a) any native dialog currently showing, and
1115 // b) anything else that the outer TaskDialog has.
1116 private bool disposed
;
1118 public void Dispose()
1121 GC
.SuppressFinalize(this);
1128 protected virtual void Dispose(bool disposing
)
1136 // Clean up managed resources.
1137 if (nativeDialog
.ShowState
==
1138 NativeDialogShowState
.Showing
)
1139 nativeDialog
.NativeClose();
1142 // Clean up unmanaged resources SECOND, NTD counts on
1143 // being closed before being disposed.
1144 if (nativeDialog
!= null)
1145 nativeDialog
.Dispose();