added SSCLI 1.0
[windows-sources.git] / sdk / samples / CrossTechnologySamples / VistaBridge / vistabridgelibrary / dialogs / taskdialog.cs
bloba08d5ab61f4dc68316f1cfc9e11451bce86e7467
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Diagnostics;
5 using System.Runtime.InteropServices;
6 using System.Text;
7 using System.Windows;
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
16 /// <summary>
17 /// Encapsulates a new-to-Vista Win32 TaskDialog window
18 /// - a powerful successor to the MessageBox available
19 /// in previous versions of Windows.
20 /// </summary>
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;
47 public Window Owner
49 get { return ownerWindow; }
50 set
52 ThrowIfDialogShowing("Dialog owner cannot be modified while dialog is showing.");
53 ownerWindow = value;
57 // Main content (maps to MessageBox's "message").
58 private string content;
59 public string Content
61 get { return content; }
62 set
64 // Set local value, then update native dialog if showing.
65 content = value;
66 if (NativeDialogShowing)
67 nativeDialog.UpdateContent(content);
71 private string instruction;
72 public string Instruction
74 get { return instruction; }
75 set
77 // Set local value, then update native dialog if showing.
78 instruction = value;
79 if (NativeDialogShowing)
80 nativeDialog.UpdateInstruction(instruction);
84 private string caption;
85 public string Caption
87 get { return caption; }
88 set
90 ThrowIfDialogShowing("Dialog caption can't be set while dialog is showing.");
91 caption = value;
95 private string footerText;
96 public string FooterText
98 get { return footerText; }
99 set
101 // Set local value, then update native dialog if showing.
102 footerText = value;
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;
133 public bool Expanded
135 get { return expanded; }
138 ThrowIfDialogShowing("Expanded state of the dialog can't be modified while dialog is showing.");
139 expanded = value;
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.");
172 cancelable = value;
176 private TaskDialogStandardIcon mainIcon;
177 public TaskDialogStandardIcon MainIcon
179 get { return mainIcon; }
182 // Set local value, then update native dialog if showing.
183 mainIcon = value;
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.
196 footerIcon = value;
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
235 get
237 if (!checkBoxChecked.HasValue)
238 return false;
239 else
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");
279 if (value != null)
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.
285 if (marquee != null)
287 throw new NotSupportedException("Dialog cannot contain marquee progress bar and a regular progress bar.");
289 value.HostingDialog = this;
291 progressBar = value;
294 private TaskDialogMarquee marquee;
295 public TaskDialogMarquee Marquee
297 get { return marquee; }
300 ThrowIfDialogShowing("Marquee can't be changed while dialog is showing.");
301 if (value != null)
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;
313 marquee = value;
316 #endregion
318 #region Constructors
320 // Constructors.
323 public TaskDialog()
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>();
337 #endregion
339 #region Static Show Methods
341 // STATIC SHOW() METHODS -- these all forward to ShowCoreStatic()
342 public static TaskDialogResult Show(string msg)
344 return ShowCoreStatic(
345 msg,
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);
361 #endregion
363 #region Instance Show Methods
364 // INSTANCE SHOW() METHODS -- these all forward to ShowCore()
366 public TaskDialogResult Show()
368 return ShowCore();
370 #endregion
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(
381 string msg,
382 string mainInstruction,
383 string caption)
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);
424 // Show the dialog.
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;
437 finally
439 CleanUp();
440 nativeDialog = null;
443 return result;
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);
501 if (button == null)
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);
510 if (button == null)
511 throw new InvalidOperationException("Received bad control ID from Win32 callback.");
512 radioButton = button.Name;
514 isCheckBoxChecked = native.CheckBoxChecked;
516 return new TaskDialogResult(
517 standardButton,
518 customButton,
519 radioButton,
520 isCheckBoxChecked);
523 public void Close()
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.
534 #endregion
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;
587 if (cancelable)
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;
593 if (expanded)
594 options |= SafeNativeMethods.TASKDIALOG_FLAGS.TDF_EXPANDED_BY_DEFAULT;
595 if (Tick != null)
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)
604 // the content area.
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.
620 if (marquee != null)
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
633 // unmanaged heap.
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 =
658 defaultRadioButton;
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)
688 if (control.Default)
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.");
693 return control.Id;
696 return found;
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)
755 if (buttons == null)
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)
768 if (marquee != null)
769 throw new InvalidOperationException(
770 "Can't have more than one marquee in dialog.");
771 marquee = (TaskDialogMarquee)control;
773 else
775 throw new ArgumentException("Unknown dialog control type.");
780 #endregion
782 #region Helpers
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;
813 default:
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);
842 #endregion
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
850 // the dialogs.
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)
886 canChange = true;
887 else
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.
894 case "Text":
895 case "Default":
896 canChange = false;
897 break;
899 // Properties that CAN be changed while dialog is showing.
900 case "ShowElevationIcon":
901 case "Enabled":
902 canChange = true;
903 break;
904 default:
905 Debug.Assert(true, "Unknown property name coming through property changing handler");
906 break;
909 return canChange;
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);
929 else
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)
939 case "State":
940 nativeDialog.UpdateProgressBarState(progressBar.State);
941 break;
942 case "Value":
943 nativeDialog.UpdateProgressBarValue(progressBar.Value);
944 break;
945 case "Minimum":
946 case "Maximum":
947 nativeDialog.UpdateProgressBarRange();
948 break;
949 default:
950 Debug.Assert(true, "Unknown property being set");
951 break;
954 else if (control is TaskDialogButton)
956 TaskDialogButton button = (TaskDialogButton)control;
957 switch (propertyName)
959 case "ShowElevationIcon":
960 nativeDialog.UpdateElevationIcon(button.Id, button.ShowElevationIcon);
961 break;
962 case "Enabled":
963 if (control is TaskDialogRadioButton)
965 nativeDialog.UpdateRadioButtonEnabled(button.Id, button.Enabled);
967 else
969 nativeDialog.UpdateButtonEnabled(button.Id, button.Enabled);
971 break;
972 default:
973 Debug.Assert(true, "Unknown property being set");
974 break;
977 else
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");
985 return;
988 #endregion
990 #region Event Percolation Methods
992 // All Raise*() methods are called by the
993 // NativeTaskDialog when various pseudo-controls
994 // are triggered.
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
1003 if (button != null)
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.
1046 handler(this, e);
1047 if (e.Cancel)
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));
1075 #endregion
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)
1091 marquee.Reset();
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)
1099 buttons.Clear();
1100 if (commandLinks != null)
1101 commandLinks.Clear();
1102 if (radioButtons != null)
1103 radioButtons.Clear();
1104 progressBar = null;
1105 marquee = null;
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()
1120 Dispose(true);
1121 GC.SuppressFinalize(this);
1123 ~TaskDialog()
1125 Dispose(false);
1128 protected virtual void Dispose(bool disposing)
1130 if (!disposed)
1132 disposed = true;
1134 if (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();
1149 #endregion