/* * Copyright © 2015 David Hall * * Design Notes:- * -------------- * - Maximum size: 150px. Limited by the control itself. * * References: * - http://www.codeproject.com/KB/vista/Vista_TaskDialog_Wrapper.aspx * - http://www.codeproject.com/Articles/21276/Vista-TaskDialog-Wrapper-and-Emulator * - http://www.codeproject.com/Articles/17026/TaskDialog-for-WinForms * * Revision Control:- * ------------------ * Created On: 2007 November 26 * Major updates: 2015 Nov 6 */ using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Design; using System.Drawing.Drawing2D; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Windows.Forms; using Vanara.Extensions; using Vanara.PInvoke; using Vanara.Resources; using static Vanara.PInvoke.ComCtl32; using static Vanara.PInvoke.User32; // ReSharper disable InconsistentNaming namespace Vanara.Windows.Forms { /// Progress bar state. [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")] public enum ProgressBarState { /// Normal. Normal = ProgressState.PBST_NORMAL, /// Error state. Error = ProgressState.PBST_ERROR, /// Paused state. Paused = ProgressState.PBST_PAUSED } /// Indicates how buttons are displayed on a . public enum TaskDialogButtonDisplay { /// Places buttons as a standard buttons along with common buttons. StandardButton, /// Places buttons as command links in primary panel. CommandLink, /// Places buttons as command links with no icons in primary panel. CommandLinkNoIcon } /// The TaskDialog common button flags used to specify the built in buttons to show in the TaskDialog. [Flags] public enum TaskDialogCommonButtons { /// No common buttons. None = 0, /// OK common button. If selected Task Dialog will return DialogResult.OK. Ok = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_OK_BUTTON, /// Yes common button. If selected Task Dialog will return DialogResult.Yes. Yes = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_YES_BUTTON, /// No common button. If selected Task Dialog will return DialogResult.No. No = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_NO_BUTTON, /// /// Cancel common button. If selected Task Dialog will return DialogResult.Cancel. If this button is specified, the dialog box will /// respond to typical cancel actions (Alt-F4 and Escape). /// Cancel = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_CANCEL_BUTTON, /// Retry common button. If selected Task Dialog will return DialogResult.Retry. Retry = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_RETRY_BUTTON, /// Close common button. If selected Task Dialog will return this value. Close = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_CLOSE_BUTTON, } /// The System icons the TaskDialog supports. [SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32")] public enum TaskDialogIcon : uint { /// No Icon. None = 0, /// System warning icon. Warning = PInvoke.ComCtl32.TaskDialogIcon.TD_WARNING_ICON, /// System Error icon. Error = PInvoke.ComCtl32.TaskDialogIcon.TD_ERROR_ICON, /// System Information icon. Information = PInvoke.ComCtl32.TaskDialogIcon.TD_INFORMATION_ICON, /// Shield icon. Shield = PInvoke.ComCtl32.TaskDialogIcon.TD_SHIELD_ICON, /// Shield icon on a blue background. Only available on Windows 8 and later. ShieldBlue = PInvoke.ComCtl32.TaskDialogIcon.TD_SHIELDBLUE_ICON, /// Warning Shield icon on a yellow background. Only available on Windows 8 and later. SecurityWarning = PInvoke.ComCtl32.TaskDialogIcon.TD_SECURITYWARNING_ICON, /// Error Shield icon on a red background. Only available on Windows 8 and later. SecurityError = PInvoke.ComCtl32.TaskDialogIcon.TD_SECURITYERROR_ICON, /// Success Shield icon on a green background. Only available on Windows 8 and later. SecuritySuccess = PInvoke.ComCtl32.TaskDialogIcon.TD_SECURITYSUCCESS_ICON, /// Shield icon on a gray background. Only available on Windows 8 and later. ShieldGray = PInvoke.ComCtl32.TaskDialogIcon.TD_SHIELDGRAY_ICON } /// /// A Task Dialog. This is like a MessageBox but with many more features. For Windows version prior to Vista, an emulated version of the /// system dialog is displayed. /// public class TaskDialog : CommonDialog, IWin32Window { /// /// Returns true if the current operating system supports TaskDialog. If false TaskDialog.Show should not be called as the results /// are undefined but often results in a crash. /// private static readonly bool IsAvailable = Environment.OSVersion.Platform == PlatformID.Win32NT && (Environment.OSVersion.Version.CompareTo(requiredOsVersion) >= 0); // The minimum Windows version needed to support TaskDialog. private static readonly Version requiredOsVersion = new Version(6, 0, 5243); private string content; private Icon customFooterIcon; private Icon customMainIcon; private string expandedInformation; // The otherFlags passed to TaskDialogIndirect. private EnumFlagIndexer flags; private string footer; private TaskDialogIcon footerIcon; // When active, holds the handle of the current window. private HWND handle = HWND.NULL; private TaskDialogIcon mainIcon; private string mainInstruction; /// Initializes a new instance of the class. public TaskDialog() { ProgressBar = new TaskDialogProgressBar(this); Reset(); } /// Occurs when a button is clicked. [Category("Action"), Description("Occurs when a button is clicked.")] public event EventHandler ButtonClicked; /// Occurs when the dialog is closed. [Category("Behavior"), Description("Occurs when the dialog is closed.")] public event EventHandler Closed; /// Occurs when the expando button is clicked and the dialog expands or contracts. [Category("Behavior"), Description("Occurs when the expando button is clicked and the dialog expands or contracts.")] public event EventHandler Expanded; /// Occurs when a link is clicked. [Category("Action"), Description("Occurs when a link is clicked.")] public event LinkClickedEventHandler LinkClicked; /// Occurs before the dialog is displayed for the first time. [Category("Behavior"), Description("Occurs before the dialog is displayed for the first time.")] public event EventHandler Load; /// Occurs when a radio button is clicked. [Category("Action"), Description("Occurs when a radio button is clicked.")] public event EventHandler RadioButtonClicked; /// Occurs when the timer fires. [Category("Action"), Description("Occurs when the timer fires.")] public event EventHandler Timer; /// Occurs when the verification check box is checked or unchecked. [Category("Action"), Description("Occurs when the verification check box is checked or unchecked.")] public event EventHandler VerificationClicked; /// /// Indicates that the dialog should be able to be closed using Alt-F4, Escape and the title bar’s close button even if no cancel /// button is specified in either the CommonButtons or Buttons members. /// [DefaultValue(false)] [Category("Behavior"), Description("Dialog can be closed by keys with no Cancel button.")] public bool AllowDialogCancellation { get => flags[TASKDIALOG_FLAGS.TDF_ALLOW_DIALOG_CANCELLATION]; set => flags[TASKDIALOG_FLAGS.TDF_ALLOW_DIALOG_CANCELLATION] = value; } /// Gets or sets the placement of buttons added to the collection. /// The button placement. [DefaultValue(typeof(TaskDialogButtonDisplay), "StandardButton")] [Category("Appearance"), Description("Determines how Buttons values are displayed")] public TaskDialogButtonDisplay ButtonDisplay { get { if (UseCommandLinksNoIcon) return TaskDialogButtonDisplay.CommandLinkNoIcon; return UseCommandLinks ? TaskDialogButtonDisplay.CommandLink : TaskDialogButtonDisplay.StandardButton; } set { switch (value) { case TaskDialogButtonDisplay.StandardButton: UseCommandLinks = UseCommandLinksNoIcon = false; break; case TaskDialogButtonDisplay.CommandLink: UseCommandLinks = true; UseCommandLinksNoIcon = false; break; case TaskDialogButtonDisplay.CommandLinkNoIcon: UseCommandLinks = false; UseCommandLinksNoIcon = true; break; default: throw new ArgumentOutOfRangeException(nameof(ButtonDisplay)); } } } /// /// Specifies the custom push buttons to display in the dialog. Use CommonButtons member for common buttons; OK, Yes, No, Retry and /// Cancel, and Buttons when you want different text on the push buttons. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [Category("Appearance"), Description("Custom push buttons.")] public TaskDialogButtonCollection Buttons { get; } = new TaskDialogButtonCollection(); /// Indicates that the TaskDialog’s callback should be called approximately every 200 milliseconds. [DefaultValue(false)] [Category("Behavior"), Description("Callback timer should be called every 200 ms.")] public bool CallbackTimer { get => flags[TASKDIALOG_FLAGS.TDF_CALLBACK_TIMER]; set => flags[TASKDIALOG_FLAGS.TDF_CALLBACK_TIMER] = value; } /// /// Indicates that the TaskDialog can be minimized. Works only if there if parent window is null. Will enable cancellation also. /// [DefaultValue(false)] [Category("Behavior"), Description("TaskDialog can be minimized.")] public bool CanBeMinimized { get => flags[TASKDIALOG_FLAGS.TDF_CAN_BE_MINIMIZED]; set => flags[TASKDIALOG_FLAGS.TDF_CAN_BE_MINIMIZED] = value; } /// /// The string to be used to label the button for expanding the expanded information. This member is ignored when the /// ExpandedInformation member is null. If this member is null and the ExpandedControlText is specified, then the /// ExpandedControlText value will be used for this member as well. /// [DefaultValue(null), Localizable(true), Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))] [Category("Appearance"), Description("Button label for expanding the expanded information")] public string CollapsedControlText { get; set; } /// /// Specifies the push buttons displayed in the dialog box. This parameter may be a combination of otherFlags. If no common buttons /// are specified and no custom buttons are specified using the Buttons member, the dialog box will contain the OK button by default. /// [DefaultValue(typeof(TaskDialogCommonButtons), "None")] [Category("Appearance"), Description("Specifies common buttons to display.")] [Editor(typeof(Design.FlagEnumUIEditor), typeof(UITypeEditor))] public TaskDialogCommonButtons CommonButtons { get; set; } /// /// The string to be used for the dialog’s primary content. If the EnableHyperlinks member is true, then this string may contain /// hyper-links in the form: Hyper-link Text. /// WARNING: Enabling hyper-links when using content from an unsafe source may cause security vulnerabilities. /// [DefaultValue(null), Localizable(true), Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))] [Category("Appearance"), Description("Optional text for the primary content area.")] public string Content { get => content; set { if (content == value) return; content = value; if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_SET_ELEMENT_TEXT, (IntPtr)TASKDIALOG_ELEMENTS.TDE_CONTENT, content); } } /// /// Specifies a custom icon for the icon to be displayed in the footer area of the dialog box. If this is set to none and the /// CustomFooterIcon member is null then no footer icon will be displayed. /// [DefaultValue(null)] [Category("Appearance"), Description("")] public Icon CustomFooterIcon { get => customFooterIcon; set { if (customFooterIcon == value) return; customFooterIcon = value; if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_UPDATE_ICON, (IntPtr)TASKDIALOG_ICON_ELEMENTS.TDIE_ICON_FOOTER, customFooterIcon?.Handle ?? (IntPtr)footerIcon); } } /// /// Specifies a custom in icon for the main icon in the dialog. If this is set to none and the CustomMainIcon member is null then no /// main icon will be displayed. /// [DefaultValue(null)] [Category("Appearance"), Description("")] public Icon CustomMainIcon { get => customMainIcon; set { if (customMainIcon == value) return; customMainIcon = value; if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_UPDATE_ICON, (IntPtr)TASKDIALOG_ICON_ELEMENTS.TDIE_ICON_MAIN, customMainIcon?.Handle ?? (IntPtr)mainIcon); } } /// /// Indicates the default button for the dialog. This may be any of the values specified in ButtonId members of one of the /// TaskDialogButton structures in the Buttons array, or one a DialogResult value that corresponds to a buttons specified in the /// CommonButtons Member. If this member is zero or its value does not correspond to any button ID in the dialog, then the first /// button in the dialog will be the default. /// [DefaultValue(0)] [Category("Behavior"), Description("")] public int DefaultButton { get; set; } /// /// Indicates the default radio button for the dialog. This may be any of the values specified in ButtonId members of one of the /// TaskDialogButton structures in the RadioButtons array. If this member is zero or its value does not correspond to any radio /// button ID in the dialog, then the first button in RadioButtons will be the default. The property NoDefaultRadioButton can be set /// to have no default. /// [DefaultValue(0)] [Category("Behavior"), Description("")] public int DefaultRadioButton { get; set; } /// /// Enables hyper-link processing for the strings specified in the Content, ExpandedInformation and FooterText members. When /// enabled, these members may be strings that contain hyper-links in the form: Hyper-link Text. /// WARNING: Enabling hyper-links when using content from an unsafe source may cause security vulnerabilities. /// Note: Task Dialog will not actually execute any hyper-links. Hyper-link execution must be handled in the callback function /// specified by Callback member. /// [DefaultValue(false)] [Category("Behavior"), Description("")] public bool EnableHyperlinks { get => flags[TASKDIALOG_FLAGS.TDF_ENABLE_HYPERLINKS]; set => flags[TASKDIALOG_FLAGS.TDF_ENABLE_HYPERLINKS] = value; } /// /// Indicates that the string specified by the ExpandedInformation member should be displayed when the dialog is initially /// displayed. This flag is ignored if the ExpandedInformation member is null. /// [DefaultValue(false)] [Category("Appearance"), Description("")] public bool ExpandedByDefault { get => flags[TASKDIALOG_FLAGS.TDF_EXPANDED_BY_DEFAULT]; set => flags[TASKDIALOG_FLAGS.TDF_EXPANDED_BY_DEFAULT] = value; } /// /// The string to be used to label the button for collapsing the expanded information. This member is ignored when the /// ExpandedInformation member is null. If this member is null and the CollapsedControlText is specified, then the /// CollapsedControlText value will be used for this member as well. /// [DefaultValue(null), Localizable(true), Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))] [Category("Appearance"), Description("")] public string ExpandedControlText { get; set; } /// /// The string to be used for displaying additional information. The additional information is displayed either immediately below /// the content or below the footer text depending on whether the ExpandFooterArea member is true. If the EnameHyperlinks member is /// true, then this string may contain hyper-links in the form: Hyper-link Text. /// WARNING: Enabling hyper-links when using content from an unsafe source may cause security vulnerabilities. /// [DefaultValue(null), Localizable(true), Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))] [Category("Appearance"), Description("")] public string ExpandedInformation { get => expandedInformation; set { if (expandedInformation == value) return; expandedInformation = value; if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_SET_ELEMENT_TEXT, (IntPtr)TASKDIALOG_ELEMENTS.TDE_EXPANDED_INFORMATION, expandedInformation); } } /// /// Indicates that the string specified by the ExpandedInformation member should be displayed at the bottom of the dialog’s footer /// area instead of immediately after the dialog’s content. This flag is ignored if the ExpandedInformation member is null. /// [DefaultValue(false)] [Category("Appearance"), Description("")] public bool ExpandFooterArea { get => flags[TASKDIALOG_FLAGS.TDF_EXPAND_FOOTER_AREA]; set => flags[TASKDIALOG_FLAGS.TDF_EXPAND_FOOTER_AREA] = value; } /// /// The string to be used in the footer area of the dialog box. If the EnableHyperlinks member is true, then this string may contain /// hyper-links in the /// form: Hyper-link Text. /// WARNING: Enabling hyper-links when using content from an unsafe source may cause security vulnerabilities. /// [DefaultValue(null), Localizable(true), Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))] [Category("Appearance"), Description("")] public string Footer { get => footer; set { if (footer == value) return; footer = value; if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_SET_ELEMENT_TEXT, (IntPtr)TASKDIALOG_ELEMENTS.TDE_FOOTER, footer); } } /// /// Specifies a built in icon for the icon to be displayed in the footer area of the dialog box. If this is set to none and the /// CustomFooterIcon member is null then no footer icon will be displayed. /// [DefaultValue(typeof(TaskDialogIcon), "None")] [Category("Appearance"), Description("")] public TaskDialogIcon FooterIcon { get => footerIcon; set { if (footerIcon == value) return; footerIcon = value; if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_UPDATE_ICON, (IntPtr)TASKDIALOG_ICON_ELEMENTS.TDIE_ICON_FOOTER, (IntPtr)footerIcon); } } /// Gets the image for the configured footer icon. /// The main icon image. [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public Image FooterIconImage => GetSmallImage(customFooterIcon ?? IconFromTaskDialogIcon(footerIcon)); /// Gets the handle for the active dialog. /// The handle. This value will be IntPtr.Zero if no active dialog is being displayed. [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public IntPtr Handle => (IntPtr)handle; /// /// Specifies a built in icon for the main icon in the dialog. If this is set to none and the CustomMainIcon is null then no main /// icon will be displayed. /// [DefaultValue(typeof(TaskDialogIcon), "None")] [Category("Appearance"), Description("")] public TaskDialogIcon MainIcon { get => mainIcon; set { if (mainIcon != value) { mainIcon = value; if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_UPDATE_ICON, (IntPtr)TASKDIALOG_ICON_ELEMENTS.TDIE_ICON_MAIN, (IntPtr)mainIcon); } } } /// Gets the image for the configured main icon. /// The main icon image. [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public Image MainIconImage => (customFooterIcon ?? IconFromTaskDialogIcon(footerIcon)).ToBitmap(); /// The string to be used for the main instruction. [DefaultValue(null), Localizable(true), Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))] [Category("Appearance"), Description("")] public string MainInstruction { get => mainInstruction; set { if (mainInstruction != value) { mainInstruction = value; if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_SET_ELEMENT_TEXT, (IntPtr)TASKDIALOG_ELEMENTS.TDE_MAIN_INSTRUCTION, mainInstruction); } } } /// Indicates that the TaskDialog should have no default radio button. [DefaultValue(false)] [Category("Behavior"), Description("")] public bool NoDefaultRadioButton { get => flags[TASKDIALOG_FLAGS.TDF_NO_DEFAULT_RADIO_BUTTON]; set => flags[TASKDIALOG_FLAGS.TDF_NO_DEFAULT_RADIO_BUTTON] = value; } /// /// Indicates that the TaskDialog should be positioned (centered) relative to the owner window passed when calling Show. If not set /// (or no owner window is passed), the TaskDialog is positioned (centered) relative to the monitor. /// [DefaultValue(false)] [Category("Window Style"), Description("")] public bool PositionRelativeToWindow { get => flags[TASKDIALOG_FLAGS.TDF_POSITION_RELATIVE_TO_WINDOW]; set => flags[TASKDIALOG_FLAGS.TDF_POSITION_RELATIVE_TO_WINDOW] = value; } /// /// The progress bar for the . This will only be visible if the /// property is set to true. /// /// The progress bar. [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [Category("Appearance"), Description("")] public TaskDialogProgressBar ProgressBar { get; } /// Specifies the radio buttons to display in the dialog. [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [Category("Appearance"), Description("")] public TaskDialogButtonCollection RadioButtons { get; } = new TaskDialogButtonCollection(); /// Gets the full set of results for the last showing of the . /// The result set. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public TaskDialogResult Result { get; private set; } /// Indicates that the TaskDialog should have right to left layout. [DefaultValue(false)] [Category("Appearance"), Description("")] public bool RightToLeftLayout { get => flags[TASKDIALOG_FLAGS.TDF_RTL_LAYOUT]; set => flags[TASKDIALOG_FLAGS.TDF_RTL_LAYOUT] = value; } /// /// Indicates that the width of the task dialog is determined by the width of its content area. This flag is ignored if is not set to 0. /// [DefaultValue(false)] [Category("Layout"), Description("")] public bool SizeToContent { get => flags[TASKDIALOG_FLAGS.TDF_SIZE_TO_CONTENT]; set => flags[TASKDIALOG_FLAGS.TDF_SIZE_TO_CONTENT] = value; } /// Gets or sets a value indicating whether the form should be displayed as a topmost form. [DefaultValue(true)] [Category("Window Style"), Description("")] public bool TopMost { get => !flags[TASKDIALOG_FLAGS.TDF_NO_SET_FOREGROUND]; set => flags[TASKDIALOG_FLAGS.TDF_NO_SET_FOREGROUND] = !value; } /// /// Indicates that the verification checkbox in the dialog should be checked when the dialog is initially displayed. This flag is /// ignored if the VerificationText parameter is null. /// [DefaultValue(false)] [Category("Appearance"), Description("")] public bool VerificationFlagChecked { get => flags[TASKDIALOG_FLAGS.TDF_VERIFICATION_FLAG_CHECKED]; set => flags[TASKDIALOG_FLAGS.TDF_VERIFICATION_FLAG_CHECKED] = value; } /// /// The string to be used to label the verification checkbox. If this member is null, the verification checkbox is not displayed in /// the dialog box. /// [DefaultValue(null), Localizable(true), Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))] [Category("Appearance"), Description("")] public string VerificationText { get; set; } /// width of the Task Dialog's client area in DLU's. If 0, Task Dialog will calculate the ideal width. [DefaultValue(0)] [Category("Layout"), Description("")] public int Width { get; set; } /// /// The string to be used for the dialog box title. If this parameter is NULL, the filename of the executable program is used. /// [DefaultValue(null), Localizable(true), Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))] [Category("Appearance"), Description("")] public string WindowTitle { get; set; } /// Indicates that an Marquee Progress Bar should be displayed. [DefaultValue(false)] internal bool ShowMarqueeProgressBar { get => flags[TASKDIALOG_FLAGS.TDF_SHOW_MARQUEE_PROGRESS_BAR]; set => flags[TASKDIALOG_FLAGS.TDF_SHOW_MARQUEE_PROGRESS_BAR] = value; } /// Indicates that a Progress Bar should be displayed. [DefaultValue(false)] internal bool ShowProgressBar { get => flags[TASKDIALOG_FLAGS.TDF_SHOW_PROGRESS_BAR]; set => flags[TASKDIALOG_FLAGS.TDF_SHOW_PROGRESS_BAR] = value; } /// /// Indicates that the buttons specified in the Buttons member should be displayed as command links (using a standard task dialog /// glyph) instead of push buttons. When using command links, all characters up to the first new line character in the ButtonText /// member (of the TaskDialogButton /// structure) will be treated as the command link’s main text, and the remainder will be treated as the command link’s note. This /// flag is ignored if the Buttons member has no entires. /// [DefaultValue(false), Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] internal bool UseCommandLinks { get => flags[TASKDIALOG_FLAGS.TDF_USE_COMMAND_LINKS]; set => flags[TASKDIALOG_FLAGS.TDF_USE_COMMAND_LINKS] = value; } /// /// Indicates that the buttons specified in the Buttons member should be displayed as command links (without a glyph) instead of /// push buttons. When using command links, all characters up to the first new line character in the ButtonText member (of the /// TaskDialogButton structure) will be treated as the command link’s main text, and the remainder will be treated as the command /// link’s note. This flag is ignored if the Buttons member has no entires. /// [DefaultValue(false), Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] internal bool UseCommandLinksNoIcon { get => flags[TASKDIALOG_FLAGS.TDF_USE_COMMAND_LINKS_NO_ICON]; set => flags[TASKDIALOG_FLAGS.TDF_USE_COMMAND_LINKS_NO_ICON] = value; } /// Gets or sets a value indicating whether this instance can raise events. /// true if this instance can raise events; otherwise, false. protected override bool CanRaiseEvents => true; /// /// Displays a task dialog in front of the specified window and with the specified main instruction, content, caption, buttons, and icon. /// /// An implementation of that will own the modal dialog box. /// The text to display as the main instruction. /// The text to show as content below the main instruction. Value can be null. /// The text to display in the title bar. /// /// One or more of the values that specifies which buttons to display in the task dialog. /// /// One of the values that specifies which icon to display in the task dialog. /// One of the values. public static int Show(IWin32Window win32Window, string mainInstruction, string content = null, string caption = "", TaskDialogCommonButtons buttons = TaskDialogCommonButtons.Ok, TaskDialogIcon icon = TaskDialogIcon.None) { if (mainInstruction == null) throw new ArgumentNullException(nameof(mainInstruction)); var dlg = new TaskDialog { MainInstruction = mainInstruction, Content = content, WindowTitle = caption, CommonButtons = buttons, MainIcon = icon }; dlg.PrivateShow(win32Window?.Handle ?? IntPtr.Zero); return dlg.Result.DialogResult; } /// /// Displays a task dialog in front of the specified window and with the specified main instruction, content, caption, buttons, and icon. /// /// An implementation of that will own the modal dialog box. /// The text to display as the main instruction. /// The text to show as content below the main instruction. Value can be null. /// The text to display in the title bar. /// Array of labels for radio buttons. /// /// One or more of the values that specifies which buttons to display in the task dialog. /// /// One of the values that specifies which icon to display in the task dialog. /// /// The 1-based index of the selected radio button, or 0 if no radio button was selected or the task dialog was cancelled. /// public static int Show(IWin32Window win32Window, string mainInstruction, string content, string caption, string[] radioButtons, TaskDialogCommonButtons buttons = TaskDialogCommonButtons.Ok | TaskDialogCommonButtons.Cancel, TaskDialogIcon icon = TaskDialogIcon.None) { if (mainInstruction == null) throw new ArgumentNullException(nameof(mainInstruction)); var dlg = new TaskDialog { MainInstruction = mainInstruction, Content = content, WindowTitle = caption, CommonButtons = buttons, MainIcon = icon }; foreach (var s in radioButtons) dlg.RadioButtons.Add(new TaskDialogRadioButton(s)); dlg.PrivateShow(win32Window?.Handle ?? IntPtr.Zero); return dlg.RadioButtons.FindIndex(b => b.ButtonId == dlg.Result.SelectedRadioButton) + 1; } /// Displays a task dialog with the specified main instruction, content, caption, buttons, and icon. /// The text to display as the main instruction. /// The text to show as content below the main instruction. Value can be null. /// The text to display in the title bar. /// /// One or more of the values that specifies which buttons to display in the task dialog. /// /// One of the values that specifies which icon to display in the task dialog. /// One of the values. public static int Show(string mainInstruction, string content = null, string caption = "", TaskDialogCommonButtons buttons = TaskDialogCommonButtons.Ok, TaskDialogIcon icon = TaskDialogIcon.None) => Show(null, mainInstruction, content, caption, buttons, icon); /// /// Displays a task dialog in front of the specified window and with the specified main instruction, caption, custom buttons, and icon. /// /// An implementation of that will own the modal dialog box. /// The text to display as the main instruction. /// The text to display in the title bar. /// /// One or more strings to display on separate Command Link buttons in the task dialog. To specify a secondary string within a /// button string, include a line-feed "\n" character. /// /// One of the values that specifies which icon to display in the task dialog. /// /// The value of the button clicked starting with 101. Each subsequent button's id will increment by 1. (e.g. Three strings for /// buttons would have the identifiers of 101, 102, and 103.). /// public static int Show(IWin32Window win32Window, string mainInstruction, string caption, string[] buttons, TaskDialogIcon icon = TaskDialogIcon.None) { if (mainInstruction == null) throw new ArgumentNullException(nameof(mainInstruction)); if (buttons == null) throw new ArgumentNullException(nameof(buttons)); if (buttons.Length == 0) throw new ArgumentException(@"At least one string must be supplied for the buttons.", nameof(buttons)); var dlg = new TaskDialog { MainInstruction = mainInstruction, WindowTitle = caption, CommonButtons = TaskDialogCommonButtons.Cancel, ButtonDisplay = TaskDialogButtonDisplay.CommandLink, MainIcon = icon }; var id = 101; foreach (var s in buttons) dlg.Buttons.Add(new TaskDialogButton(s, id++)); dlg.PrivateShow(win32Window?.Handle ?? IntPtr.Zero); return dlg.Result.DialogResult; } /// Displays a task dialog with the specified main instruction, caption, custom buttons, and icon. /// The text to display as the main instruction. /// The text to display in the title bar. /// /// One or more strings to display on separate Command Link buttons in the task dialog. To specify a secondary string within a /// button string, include a line-feed "\n" character. /// /// One of the values that specifies which icon to display in the task dialog. /// /// The value of the button clicked starting with 101. Each subsequent button's id will increment by 1. (e.g. Three strings for /// buttons would have the identifiers of 101, 102, and 103.). /// public static int Show(string mainInstruction, string caption, string[] buttons, TaskDialogIcon icon = TaskDialogIcon.None) => Show(null, mainInstruction, caption, buttons, icon); /// /// Simulate the action of a button click in the TaskDialog. This can be a DialogResult value or the ButtonID set on a /// TasDialogButton set on TaskDialog.Buttons. /// /// Indicates the button ID to be selected. /// If the function succeeds the return value is true. // TDM_CLICK_BUTTON = WM_USER+102, // wParam = Button ID public void PerformButtonClick(int buttonId) { if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_CLICK_BUTTON, (IntPtr)buttonId, IntPtr.Zero); } /// Check or uncheck the verification checkbox in the TaskDialog. /// The checked state to set the verification checkbox. /// True to set the keyboard focus to the checkbox, and false otherwise. public void PerformVerificationClick(bool checkedState, bool setKeyboardFocusToCheckBox) { // TDM_CLICK_VERIFICATION = WM_USER+113, // wParam = 0 (unchecked), 1 (checked), lParam = 1 (set key focus) if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_CLICK_VERIFICATION, (checkedState ? new IntPtr(1) : IntPtr.Zero), (setKeyboardFocusToCheckBox ? new IntPtr(1) : IntPtr.Zero)); } /// Resets the Task Dialog to the state when first constructed, all properties set to their default value. public override void Reset() { Buttons.Clear(); CollapsedControlText = null; CommonButtons = 0; Content = null; CustomFooterIcon = null; CustomMainIcon = null; DefaultButton = 0; DefaultRadioButton = 0; ExpandedControlText = null; ExpandedInformation = null; flags = (TASKDIALOG_FLAGS)0; Footer = null; FooterIcon = TaskDialogIcon.None; MainIcon = TaskDialogIcon.None; MainInstruction = null; ProgressBar.Reset(); RadioButtons.Clear(); VerificationText = null; Width = 0; WindowTitle = null; } internal static Image GetSmallImage(Icon icon) { if (icon == null) return null; var sz = SystemInformation.SmallIconSize; var bmp = new Bitmap(sz.Width, sz.Height); using (var g = Graphics.FromImage(bmp)) { g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.DrawImage(icon.ToBitmap(), new Rectangle(Point.Empty, sz)); } return bmp; } internal static Icon IconFromTaskDialogIcon(TaskDialogIcon icon) { if (Environment.OSVersion.Version >= requiredOsVersion) { var ie = new ResourceFile(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "imageres.dll")); switch (icon) { case TaskDialogIcon.None: return null; case TaskDialogIcon.Warning: return ie.GroupIcons[79]; case TaskDialogIcon.Error: return ie.GroupIcons[93]; case TaskDialogIcon.Information: return ie.GroupIcons[76]; case TaskDialogIcon.SecurityWarning: return ie.GroupIcons[102]; case TaskDialogIcon.SecurityError: return ie.GroupIcons[100]; case TaskDialogIcon.SecuritySuccess: return ie.GroupIcons[101]; default: return ie.GroupIcons[73]; } } switch (icon) { case TaskDialogIcon.None: return null; case TaskDialogIcon.Warning: return SystemIcons.Warning; case TaskDialogIcon.Error: return SystemIcons.Error; case TaskDialogIcon.Information: return SystemIcons.Information; default: return SystemIcons.Shield; } } /// /// Enable or disable a button in the TaskDialog. The passed buttonID is the ButtonID set on a TaskDialogButton set on /// TaskDialog.Buttons or a common button ID. /// /// Indicates the button ID to be enabled or disabled. /// Enable the button if true. Disable the button if false. internal void EnableButton(int buttonId, bool enable) { // TDM_ENABLE_BUTTON = WM_USER+111, // lParam = 0 (disable), lParam != 0 (enable), wParam = Button ID if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_ENABLE_BUTTON, (IntPtr)buttonId, (IntPtr)(enable ? 1 : 0)); } /// /// Enable or disable a radio button in the TaskDialog. The passed buttonID is the ButtonID set on a TaskDialogButton set on TaskDialog.RadioButtons. /// /// Indicates the button ID to be enabled or disabled. /// Enable the button if true. Disable the button if false. internal void EnableRadioButton(int buttonId, bool enable) { // TDM_ENABLE_RADIO_BUTTON = WM_USER+112, // lParam = 0 (disable), lParam != 0 (enable), wParam = Radio Button ID if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_ENABLE_RADIO_BUTTON, (IntPtr)buttonId, (IntPtr)(enable ? 1 : 0)); } internal void InitializeButtonState() { foreach (var b in Buttons) { if (!b.Enabled) EnableButton(b.ButtonId, false); if (b.ElevatedStateRequired) SetButtonElevationRequiredState(b.ButtonId, true); } } /// Raises the event. /// The button identifier. /// true to prevent close; otherwise, false. Ignored for radio buttons. internal virtual bool OnButtonClicked(int id) { var btn = Buttons.Find(b => b.ButtonId == id); var e = new ButtonClickedEventArgs(id, btn); ButtonClicked?.Invoke(this, e); btn?.OnClick(e); return e.Cancel; } /// Raises the event. internal virtual void OnClosed() => Closed?.Invoke(this, EventArgs.Empty); /// Called when the expando button is clicked and the dialog expands or contracts. /// true if dialog is expanded; otherwise false. internal virtual void OnExpanded(bool expanded) => Expanded?.Invoke(this, new ExpandedEventArgs(expanded)); /// Raises the event. /// The URL of the link. internal virtual void OnLinkClicked(string url) => LinkClicked?.Invoke(this, new LinkClickedEventArgs(url)); /// Raises the event. internal virtual void OnLoad() => Load?.Invoke(this, EventArgs.Empty); /// Raises the event. /// The radio button identifier. internal virtual void OnRadioButtonClicked(int id) { var btn = RadioButtons.Find(b => b.ButtonId == id); RadioButtonClicked?.Invoke(this, new ButtonClickedEventArgs(id, btn)); btn?.OnClick(EventArgs.Empty); } /// Raises the event. /// The tick count. /// true to reset tick count; otherwise, false to continue to increment. internal virtual bool OnTimer(int ticks) { var a = new TimerEventArgs(ticks); Timer?.Invoke(this, a); return a.Reset; } /// Called when the verification check box is checked or unchecked. /// true if check box is checked; otherwise false. internal virtual void OnVerificationClicked(bool verificationChecked) => VerificationClicked?.Invoke(this, new VerificationClickedEventArgs(verificationChecked)); /// /// Simulate the action of a radio button click in the TaskDialog. The passed buttonID is the ButtonID set on a TaskDialogButton set /// on TaskDialog.RadioButtons. /// /// Indicates the button ID to be selected. internal void PerformRadioButtonClick(int buttonId) { // TDM_CLICK_RADIO_BUTTON = WM_USER+110, // wParam = Radio Button ID if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_CLICK_RADIO_BUTTON, (IntPtr)buttonId, IntPtr.Zero); } /// Designate whether a given Task Dialog button or command link should have a User Account Control (UAC) shield icon. /// ID of the push button or command link to be updated. /// /// False to designate that the action invoked by the button does not require elevation; true to designate that the action does /// require elevation. /// internal void SetButtonElevationRequiredState(int buttonId, bool elevationRequired) { // TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE = WM_USER+115, // wParam = Button ID, lParam = 0 (elevation not required), lParam != // 0 (elevation required) if (!handle.IsNull) SendMessage(handle, (uint)TaskDialogMessage.TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE, (IntPtr)buttonId, (IntPtr)(elevationRequired ? 1 : 0)); } /// /// Defines the common dialog box hook procedure that is overridden to add specific functionality to a common dialog box. /// /// The handle to the dialog box window. /// The message being received. /// Additional information about the message. /// Additional information about the message. /// /// A zero value if the default dialog box procedure processes the message; a nonzero value if the default dialog box procedure /// ignores the message. /// protected override IntPtr HookProc(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) { //ActiveTaskDialog activeDialog = new ActiveTaskDialog(hWnd); handle = hWnd; switch ((TaskDialogNotification)msg) { case TaskDialogNotification.TDN_CREATED: ProgressBar.Initialize(); InitializeButtonState(); OnLoad(); break; case TaskDialogNotification.TDN_BUTTON_CLICKED: return (IntPtr)(OnButtonClicked((int)wparam) ? 1 : 0); case TaskDialogNotification.TDN_RADIO_BUTTON_CLICKED: OnRadioButtonClicked((int)wparam); break; case TaskDialogNotification.TDN_HELP: OnHelpRequest(EventArgs.Empty); break; case TaskDialogNotification.TDN_HYPERLINK_CLICKED: OnLinkClicked(Marshal.PtrToStringUni(lparam)); break; case TaskDialogNotification.TDN_TIMER: return (IntPtr)(OnTimer((int)wparam) ? 1 : 0); case TaskDialogNotification.TDN_VERIFICATION_CLICKED: OnVerificationClicked(wparam != IntPtr.Zero); break; case TaskDialogNotification.TDN_EXPANDO_BUTTON_CLICKED: OnExpanded(wparam != IntPtr.Zero); break; case TaskDialogNotification.TDN_DESTROYED: OnClosed(); handle = IntPtr.Zero; break; } return base.HookProc(hWnd, msg, wparam, lparam); } /// The required implementation of CommonDialog that shows the Task Dialog. /// Owner window. This can be null. /// /// If this method returns true, then ShowDialog will return DialogResult.OK. If this method returns false, then ShowDialog will /// return DialogResult.Cancel. The user of this class must use the TaskDialogResult member to get more information. /// protected override bool RunDialog(IntPtr hwndOwner) { var res = PrivateShow(hwndOwner); return (res.DialogResult != (int)DialogResult.Cancel); } /// The callback from the native Task Dialog. This prepares the friendlier arguments and calls the simpler callback. /// The window handle of the Task Dialog that is active. /// The notification. A TaskDialogNotification value. /// /// Specifies additional notification information. The contents of this parameter depends on the value of the msg parameter. /// /// /// Specifies additional notification information. The contents of this parameter depends on the value of the msg parameter. /// /// Specifies the application-defined value given in the call to TaskDialogIndirect. /// A HRESULT. It's not clear in the spec what a failed result will do. private HRESULT PrivateCallback([In] HWND hwnd, [In] TaskDialogNotification msg, [In] IntPtr wparam, [In] IntPtr lparam, [In] IntPtr refData) => HookProc((IntPtr)hwnd, (int)msg, wparam, lparam).ToInt32(); /// /// Creates, displays, and operates a task dialog. The task dialog contains application-defined messages, title, verification check /// box, command links and push buttons, plus any combination of predefined icons and push buttons as specified on the other members /// of the class before calling Show. /// /// Owner window the task Dialog will modal to. /// The set of results of the dialog. private TaskDialogResult PrivateShow(HWND hwndOwner) { if (!IsAvailable) { #if TASKDIALOG_EMULATE // Hand it off to emulator. using (var taskDialogEmulate = new EmulateTaskDialog(this)) { taskDialogEmulate.HandleCreated += (s, e) => handle = taskDialogEmulate.Handle; taskDialogEmulate.ShowDialog(); handle = IntPtr.Zero; Result = new TaskDialogResult(taskDialogEmulate.TaskDialogResult, taskDialogEmulate.TaskDialogVerificationFlagChecked, taskDialogEmulate.TaskDialogRadioButtonResult); } return Result; #else throw new PlatformNotSupportedException("TaskDialog requires Windows Vista or later."); #endif } var config = new TASKDIALOGCONFIG { hwndParent = hwndOwner, dwFlags = flags, dwCommonButtons = (TASKDIALOG_COMMON_BUTTON_FLAGS)CommonButtons }; if (!string.IsNullOrEmpty(WindowTitle)) config.WindowTitle = WindowTitle; if (CustomMainIcon != null) { config.dwFlags |= TASKDIALOG_FLAGS.TDF_USE_HICON_MAIN; config.mainIcon = CustomMainIcon.Handle; } else config.mainIcon = (IntPtr)MainIcon; if (!string.IsNullOrEmpty(MainInstruction)) config.MainInstruction = MainInstruction; if (!string.IsNullOrEmpty(Content)) config.Content = Content; foreach (var b in Buttons) b.parent = this; config.pButtons = (IntPtr)Buttons; config.cButtons = (uint)Buttons.Count; config.nDefaultButton = DefaultButton; foreach (var b in RadioButtons) b.parent = this; config.pRadioButtons = (IntPtr)RadioButtons; config.cRadioButtons = (uint)RadioButtons.Count; config.nDefaultRadioButton = DefaultRadioButton; if (!string.IsNullOrEmpty(VerificationText)) config.VerificationText = VerificationText; if (!string.IsNullOrEmpty(ExpandedInformation)) config.ExpandedInformation = ExpandedInformation; if (!string.IsNullOrEmpty(ExpandedControlText)) config.ExpandedControlText = ExpandedControlText; if (!string.IsNullOrEmpty(CollapsedControlText)) config.CollapsedControlText = CollapsedControlText; config.footerIcon = (IntPtr)FooterIcon; if (CustomFooterIcon != null) { config.dwFlags |= TASKDIALOG_FLAGS.TDF_USE_HICON_FOOTER; config.footerIcon = CustomFooterIcon.Handle; } else config.footerIcon = (IntPtr)FooterIcon; if (!string.IsNullOrEmpty(Footer)) config.Footer = Footer; config.pfCallbackProc = PrivateCallback; config.cxWidth = (uint)Width; // Check to see if a button of some kind is defined, otherwise set to OK button. if (config.dwCommonButtons == 0 && config.cButtons == 0) config.dwCommonButtons = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_OK_BUTTON; using (new ComCtl32v6Context()) { TaskDialogResult res; TaskDialogIndirect(config, out res.dialogResult, out res.selectedRadioButton, out res.verificationFlagChecked).ThrowIfFailed(); Result = res; } return Result; } //private bool ShouldSerializeProgressBar() { return ProgressBar != TaskDialogProgressBar.Default; } /// Results from running the . public struct TaskDialogResult { internal int dialogResult, selectedRadioButton; internal bool verificationFlagChecked; internal TaskDialogResult(int result, bool verCheck, int selRadio) { dialogResult = result; verificationFlagChecked = verCheck; selectedRadioButton = selRadio; } /// /// Gets the dialog result. This is the value of one of the enumerate type or the ID of a /// that has been clicked. /// /// The task dialog result. public int DialogResult => dialogResult; /// If a radio button was supplied and selected, gets the value of the selected radio button's identifier. /// /// The selected radio button id. A value of 0 indicates that no radio button was selected or the task dialog was cancelled. /// public int SelectedRadioButton => selectedRadioButton; /// Gets a value indicating whether the verification flag was checked. /// true if verification flag checked; otherwise, false. public bool VerificationFlagChecked => verificationFlagChecked; /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() => $"{dialogResult} : {(verificationFlagChecked ? "checked" : "unchecked")} : {selectedRadioButton}"; } /// Provides data for the and the events. public class ButtonClickedEventArgs : CancelEventArgs { internal ButtonClickedEventArgs(int id, TaskDialogButtonBase button) { ButtonId = id; Button = button; } /// Gets the button that was clicked. /// The button. public TaskDialogButtonBase Button { get; } /// Gets the id of the button clicked. /// The button id. public int ButtonId { get; } } /// Provides data for the event. public class ExpandedEventArgs : EventArgs { internal ExpandedEventArgs(bool exp) => Expanded = exp; /// Gets a value indicating whether the is expanded. /// true if expanded; otherwise, false. public bool Expanded { get; } } /// A custom button for the TaskDialog. [DefaultEvent("Clicked"), DefaultProperty("ButtonId")] public abstract class TaskDialogButtonBase : IEquatable { internal static int idSeed = 101; internal TASKDIALOG_BUTTON nativeButton; internal TaskDialog parent; /// Initializes a new instance of the class. protected TaskDialogButtonBase() : this(null) { } /// Initialize the custom button. /// /// The ID of the button. This value is returned by TaskDialog.Show when the button is clicked. Typically this will be a value /// in the DialogResult enum. /// /// The string that appears on the button. protected TaskDialogButtonBase(string text, int id = -1) { if (id == -1) id = idSeed++; nativeButton.buttonId = id; nativeButton.buttonText = text; } /// The ID of the button. This value is returned by TaskDialog.Show when the button is clicked. [DefaultValue(0)] [Category("Behavior"), Description("")] public int ButtonId { get => nativeButton.buttonId; set => nativeButton.buttonId = value; } /// The string that appears on the button. [DefaultValue(null), Localizable(true), Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))] [Category("Appearance"), Description("")] public string ButtonText { get => nativeButton.buttonText; set => nativeButton.buttonText = value; } /// Gets or sets a value indicating whether this is enabled. /// true if enabled; otherwise, false. [DefaultValue(true)] [Category("Appearance"), Description("")] public virtual bool Enabled { get; set; } = true; /// Determines whether the specified , is equal to this instance. /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object obj) => obj is TaskDialogButtonBase bb ? Equals(bb) : base.Equals(obj); /// Determines whether the specified , is equal to this instance. /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. public bool Equals(TaskDialogButtonBase other) => other.nativeButton.buttonId == nativeButton.buttonId && other.nativeButton.buttonText == nativeButton.buttonText; /// Returns a hash code for this instance. /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. public override int GetHashCode() => new { a = ButtonId, b = ButtonText }.GetHashCode(); /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() => $"{ButtonText} ({ButtonId})"; [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] // Would be unused code as not required for usage. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)] internal struct TASKDIALOG_BUTTON { /// The ID of the button. This value is returned by TaskDialog.Show when the button is clicked. public int buttonId; /// The string that appears on the button. [MarshalAs(UnmanagedType.LPWStr)] public string buttonText; } } /// A collection of elements. public class TaskDialogButtonCollection : List, IDisposable where T : TaskDialogButtonBase { private const int hashSeed = 17; private IntPtr ptr = IntPtr.Zero; private int ptrHash = hashSeed; /// Initializes a new instance of the class. internal TaskDialogButtonCollection() { } /// Explicitly converts the to an in-memory array. /// The instance. /// /// An IntPtr pointing to a marshaled memory pointer of the array created with AllocHGlobal. This does not need to be freed as /// the class maintains it. /// public static explicit operator IntPtr(TaskDialogButtonCollection c) { // Get hash for set var h = hashSeed; unchecked { foreach (var item in c) h = h * 23 + item.GetHashCode(); } // If new has doesn't equal old hash, reset IntPtr if (h != c.ptrHash) { // Clean up old array ((IDisposable)c).Dispose(); // Build new c.ptr = c.Select(b => b.nativeButton).MarshalToPtr(Marshal.AllocHGlobal, out var _); // Set hash to new value c.ptrHash = h; } return c.ptr; } /// Clears this instance. public new void Clear() { base.Clear(); ((IDisposable)this).Dispose(); } /// Releases unmanaged and managed resources. void IDisposable.Dispose() { if (ptr == IntPtr.Zero) return; lock (this) { Marshal.FreeHGlobal(ptr); ptr = IntPtr.Zero; ptrHash = hashSeed; } } } /// Represents the progress bar that can be displayed in a task dialog. [TypeConverter(typeof(BetterExpandableObjectConverter)), Serializable] public class TaskDialogProgressBar { private readonly TaskDialog taskDialog; private short max = 100, min; private int mSpeed = 100, val; private ProgressBarState state = ProgressBarState.Normal; private ProgressBarStyle style = ProgressBarStyle.Continuous; internal TaskDialogProgressBar(TaskDialog td) => taskDialog = td; /// Gets or sets the marquee animation speed. /// The marquee animation speed. [DefaultValue(100)] public int MarqueeAnimationSpeed { get => mSpeed; set { if (mSpeed != value) SetMarqueeSpeed(mSpeed = value); } } /// Gets or sets the maximum progress value. /// The maximum. [DefaultValue((short)100)] public short Maximum { get => max; set { if (max == value) return; max = value; SetProgressBarRange(); } } /// Gets or sets the minimum progress value. /// The minimum. [DefaultValue((short)0)] public short Minimum { get => min; set { if (min == value) return; min = value; SetProgressBarRange(); } } /// Gets or sets the current state of the progress bar. /// The state. [DefaultValue(typeof(ProgressBarState), "Normal")] public ProgressBarState State { get => state; set { if (state != value) SetState(state = value); } } /// Gets or sets the style of the progress bar. /// The style. /// TaskDialog does not support Block style progress bars. [DefaultValue(typeof(ProgressBarStyle), "Continuous")] public ProgressBarStyle Style { get => style; set { if (style != value) { if (value == ProgressBarStyle.Blocks) throw new NotSupportedException("TaskDialog does not support Block style progress bars."); style = value; if (taskDialog == null) return; var cont = (style == ProgressBarStyle.Continuous); taskDialog.ShowProgressBar = cont; taskDialog.ShowMarqueeProgressBar = !cont; if (taskDialog.Handle != IntPtr.Zero) SendMessage(taskDialog.handle, (uint)TaskDialogMessage.TDM_SET_MARQUEE_PROGRESS_BAR, (value == ProgressBarStyle.Marquee ? (IntPtr)1 : IntPtr.Zero), IntPtr.Zero); } } } /// Gets or sets the value of the progress bar. /// The value. [DefaultValue(0)] public int Value { get => val; set { if (val != value) SetValue(val = value); } } /// Gets or sets a value indicating whether this is visible. /// if visible; otherwise, . [DefaultValue(false)] public bool Visible { get => taskDialog != null && (taskDialog.ShowProgressBar || taskDialog.ShowMarqueeProgressBar); set { if (!value || taskDialog == null) return; if (style == ProgressBarStyle.Continuous) taskDialog.ShowProgressBar = true; else taskDialog.ShowMarqueeProgressBar = true; } } internal void Initialize() { if (Visible) { if (Style == ProgressBarStyle.Marquee) { SetMarqueeSpeed(mSpeed); } else { SetProgressBarRange(); SetValue(val); } SetState(state); } } internal void Reset() { mSpeed = max = 100; val = min = 0; state = ProgressBarState.Normal; style = ProgressBarStyle.Continuous; } private static uint MakeLong(short low, short high) => ((ushort)low | (uint)(high << 16)); private void SetMarqueeSpeed(int value) { if (taskDialog != null && taskDialog.Handle != IntPtr.Zero) SendMessage(taskDialog.handle, (uint)TaskDialogMessage.TDM_SET_PROGRESS_BAR_MARQUEE, (IntPtr)(value != 0 ? 1 : 0), (IntPtr)value); } private void SetProgressBarRange() { if (taskDialog != null && taskDialog.Handle != IntPtr.Zero) SendMessage(taskDialog.handle, (uint)TaskDialogMessage.TDM_SET_PROGRESS_BAR_RANGE, IntPtr.Zero, (IntPtr)MakeLong(min, max)); } private void SetState(ProgressBarState value) { if (taskDialog != null && taskDialog.Handle != IntPtr.Zero) SendMessage(taskDialog.handle, (uint)TaskDialogMessage.TDM_SET_PROGRESS_BAR_STATE, (IntPtr)value, IntPtr.Zero); } private void SetValue(int value) { if (taskDialog != null && taskDialog.Handle != IntPtr.Zero) SendMessage(taskDialog.handle, (uint)TaskDialogMessage.TDM_SET_PROGRESS_BAR_POS, (IntPtr)value, IntPtr.Zero); } } /// Provides data for the event. public class TimerEventArgs : EventArgs { internal TimerEventArgs(int ticks) => TickCount = ticks; /// Gets or sets a value indicating whether to reset the tick count. /// true to reset tick count; otherwise, false to continue to increment. public bool Reset { get; set; } = false; /// Gets the tick count for this instance of the . /// The tick count. public int TickCount { get; } } /// Provides data for the event. public class VerificationClickedEventArgs : EventArgs { internal VerificationClickedEventArgs(bool check) => Checked = check; /// Gets a value indicating whether the verification check box is checked. /// true if checked; otherwise, false. public bool Checked { get; } } } /// Represents a button on a task dialog. /// public class TaskDialogButton : TaskDialog.TaskDialogButtonBase { private bool shield; /// Initializes a new instance of the class. public TaskDialogButton() : this(null) { } /// Initialize the custom button. /// The string that appears on the button. /// /// The ID of the button. This value is returned by TaskDialog.Show when the button is clicked. Typically this will be a value in /// the DialogResult enum. Specifying a value of (-1) will insert a potentially non-unique value. /// public TaskDialogButton(string text, int id = -1) : base(text, id) { if (ButtonText == null) ButtonText = $"Button{ButtonId}"; } /// Occurs when the button is clicked. [Category("Action"), Description("")] public event CancelEventHandler Click; /// /// Gets or sets a value indicating whether to close the on click and returns this button's as the result. /// /// true if dialog closes on click; otherwise, false. [DefaultValue(true)] [Category("Behavior"), Description("")] public bool CloseOnClick { get; set; } = true; /// Gets or sets a value indicating whether button shows elevated state shield. /// true if elevated state shield displayed; otherwise, false. [DefaultValue(false)] [Category("Appearance"), Description("")] public bool ElevatedStateRequired { get => shield; set { if (shield != value) { parent?.SetButtonElevationRequiredState(ButtonId, value); shield = value; } } } /// Gets or sets a value indicating whether this is enabled. /// true if enabled; otherwise, false. [DefaultValue(true)] public override bool Enabled { get => base.Enabled; set { if (base.Enabled != value) { parent?.EnableButton(ButtonId, value); base.Enabled = value; } } } /// Raises the event. /// The instance containing the event data. public void OnClick(CancelEventArgs e) { e.Cancel = !CloseOnClick; Click?.Invoke(this, e); } /// Generates a event for the button. public void PerformClick() => parent?.PerformButtonClick(ButtonId); } /// Represents a radio button on a task dialog. /// public class TaskDialogRadioButton : TaskDialog.TaskDialogButtonBase { /// Initializes a new instance of the class. public TaskDialogRadioButton() : this(null) { } /// Initialize the custom radio button. /// The string that appears on the radio button. /// /// The ID of the readio button. This value is returned by TaskDialog.Show when the button is clicked. Specifying a value of (-1) /// will insert a potentially non-unique value. /// public TaskDialogRadioButton(string text, int id = -1) : base(text, id) { if (ButtonText == null) ButtonText = $"RadioButton{ButtonId}"; } /// Occurs when the radio button is clicked. public event EventHandler Click; /// Gets or sets a value indicating whether this is enabled. /// true if enabled; otherwise, false. [DefaultValue(true)] public override bool Enabled { get => base.Enabled; set { if (base.Enabled != value) { parent?.EnableRadioButton(ButtonId, value); base.Enabled = value; } } } /// Raises the event. /// The instance containing the event data. public void OnClick(EventArgs e) => Click?.Invoke(this, e); /// Generates a event for the button. public void PerformClick() => parent?.PerformRadioButtonClick(ButtonId); } }