/*
 * 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
{
	/// <summary>Progress bar state.</summary>
	[SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")]
	public enum ProgressBarState
	{
		/// <summary>Normal.</summary>
		Normal = ProgressState.PBST_NORMAL,

		/// <summary>Error state.</summary>
		Error = ProgressState.PBST_ERROR,

		/// <summary>Paused state.</summary>
		Paused = ProgressState.PBST_PAUSED
	}

	/// <summary>Indicates how buttons are displayed on a <see cref="TaskDialog"/>.</summary>
	public enum TaskDialogButtonDisplay
	{
		/// <summary>Places buttons as a standard buttons along with common buttons.</summary>
		StandardButton,

		/// <summary>Places buttons as command links in primary panel.</summary>
		CommandLink,

		/// <summary>Places buttons as command links with no icons in primary panel.</summary>
		CommandLinkNoIcon
	}

	/// <summary>The TaskDialog common button flags used to specify the built in buttons to show in the TaskDialog.</summary>
	[Flags]
	public enum TaskDialogCommonButtons
	{
		/// <summary>No common buttons.</summary>
		None = 0,

		/// <summary>OK common button. If selected Task Dialog will return DialogResult.OK.</summary>
		Ok = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_OK_BUTTON,

		/// <summary>Yes common button. If selected Task Dialog will return DialogResult.Yes.</summary>
		Yes = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_YES_BUTTON,

		/// <summary>No common button. If selected Task Dialog will return DialogResult.No.</summary>
		No = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_NO_BUTTON,

		/// <summary>
		/// 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).
		/// </summary>
		Cancel = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_CANCEL_BUTTON,

		/// <summary>Retry common button. If selected Task Dialog will return DialogResult.Retry.</summary>
		Retry = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_RETRY_BUTTON,

		/// <summary>Close common button. If selected Task Dialog will return this value.</summary>
		Close = TASKDIALOG_COMMON_BUTTON_FLAGS.TDCBF_CLOSE_BUTTON,
	}

	/// <summary>The System icons the TaskDialog supports.</summary>
	[SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32")]
	public enum TaskDialogIcon : uint
	{
		/// <summary>No Icon.</summary>
		None = 0,

		/// <summary>System warning icon.</summary>
		Warning = PInvoke.ComCtl32.TaskDialogIcon.TD_WARNING_ICON,

		/// <summary>System Error icon.</summary>
		Error = PInvoke.ComCtl32.TaskDialogIcon.TD_ERROR_ICON,

		/// <summary>System Information icon.</summary>
		Information = PInvoke.ComCtl32.TaskDialogIcon.TD_INFORMATION_ICON,

		/// <summary>Shield icon.</summary>
		Shield = PInvoke.ComCtl32.TaskDialogIcon.TD_SHIELD_ICON,

		/// <summary>Shield icon on a blue background. Only available on Windows 8 and later.</summary>
		ShieldBlue = PInvoke.ComCtl32.TaskDialogIcon.TD_SHIELDBLUE_ICON,

		/// <summary>Warning Shield icon on a yellow background. Only available on Windows 8 and later.</summary>
		SecurityWarning = PInvoke.ComCtl32.TaskDialogIcon.TD_SECURITYWARNING_ICON,

		/// <summary>Error Shield icon on a red background. Only available on Windows 8 and later.</summary>
		SecurityError = PInvoke.ComCtl32.TaskDialogIcon.TD_SECURITYERROR_ICON,

		/// <summary>Success Shield icon on a green background. Only available on Windows 8 and later.</summary>
		SecuritySuccess = PInvoke.ComCtl32.TaskDialogIcon.TD_SECURITYSUCCESS_ICON,

		/// <summary>Shield icon on a gray background. Only available on Windows 8 and later.</summary>
		ShieldGray = PInvoke.ComCtl32.TaskDialogIcon.TD_SHIELDGRAY_ICON
	}

	/// <summary>
	/// 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.
	/// </summary>
	public class TaskDialog : CommonDialog, IWin32Window
	{
		/// <summary>
		/// 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.
		/// </summary>
		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<TASKDIALOG_FLAGS> 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;

		/// <summary>Initializes a new instance of the <see cref="TaskDialog"/> class.</summary>
		public TaskDialog()
		{
			ProgressBar = new TaskDialogProgressBar(this);
			Reset();
		}

		/// <summary>Occurs when a button is clicked.</summary>
		[Category("Action"), Description("Occurs when a button is clicked.")]
		public event EventHandler<ButtonClickedEventArgs> ButtonClicked;

		/// <summary>Occurs when the dialog is closed.</summary>
		[Category("Behavior"), Description("Occurs when the dialog is closed.")]
		public event EventHandler Closed;

		/// <summary>Occurs when the expando button is clicked and the dialog expands or contracts.</summary>
		[Category("Behavior"), Description("Occurs when the expando button is clicked and the dialog expands or contracts.")]
		public event EventHandler<ExpandedEventArgs> Expanded;

		/// <summary>Occurs when a link is clicked.</summary>
		[Category("Action"), Description("Occurs when a link is clicked.")]
		public event LinkClickedEventHandler LinkClicked;

		/// <summary>Occurs before the dialog is displayed for the first time.</summary>
		[Category("Behavior"), Description("Occurs before the dialog is displayed for the first time.")]
		public event EventHandler Load;

		/// <summary>Occurs when a radio button is clicked.</summary>
		[Category("Action"), Description("Occurs when a radio button is clicked.")]
		public event EventHandler<ButtonClickedEventArgs> RadioButtonClicked;

		/// <summary>Occurs when the timer fires.</summary>
		[Category("Action"), Description("Occurs when the timer fires.")]
		public event EventHandler<TimerEventArgs> Timer;

		/// <summary>Occurs when the verification check box is checked or unchecked.</summary>
		[Category("Action"), Description("Occurs when the verification check box is checked or unchecked.")]
		public event EventHandler<VerificationClickedEventArgs> VerificationClicked;

		/// <summary>
		/// 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.
		/// </summary>
		[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;
		}

		/// <summary>Gets or sets the placement of buttons added to the <see cref="Buttons"/> collection.</summary>
		/// <value>The button placement.</value>
		[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));
				}
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		[Category("Appearance"), Description("Custom push buttons.")]
		public TaskDialogButtonCollection<TaskDialogButton> Buttons { get; } = new TaskDialogButtonCollection<TaskDialogButton>();

		/// <summary>Indicates that the TaskDialog’s callback should be called approximately every 200 milliseconds.</summary>
		[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;
		}

		/// <summary>
		/// Indicates that the TaskDialog can be minimized. Works only if there if parent window is null. Will enable cancellation also.
		/// </summary>
		[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;
		}

		/// <summary>
		/// 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.
		/// </summary>
		[DefaultValue(null), Localizable(true),
		 Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]
		[Category("Appearance"), Description("Button label for expanding the expanded information")]
		public string CollapsedControlText { get; set; }

		/// <summary>
		/// 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.
		/// </summary>
		[DefaultValue(typeof(TaskDialogCommonButtons), "None")]
		[Category("Appearance"), Description("Specifies common buttons to display.")]
		[Editor(typeof(Design.FlagEnumUIEditor<TaskDialogCommonButtons>), typeof(UITypeEditor))]
		public TaskDialogCommonButtons CommonButtons { get; set; }

		/// <summary>
		/// 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: <A HREF="executablestring">Hyper-link Text</A>.
		/// WARNING: Enabling hyper-links when using content from an unsafe source may cause security vulnerabilities.
		/// </summary>
		[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);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		[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);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		[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);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		[DefaultValue(0)]
		[Category("Behavior"), Description("")]
		public int DefaultButton { get; set; }

		/// <summary>
		/// 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.
		/// </summary>
		[DefaultValue(0)]
		[Category("Behavior"), Description("")]
		public int DefaultRadioButton { get; set; }

		/// <summary>
		/// 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: <A HREF="executablestring">Hyper-link Text</A>.
		/// 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.
		/// </summary>
		[DefaultValue(false)]
		[Category("Behavior"), Description("")]
		public bool EnableHyperlinks
		{
			get => flags[TASKDIALOG_FLAGS.TDF_ENABLE_HYPERLINKS];
			set => flags[TASKDIALOG_FLAGS.TDF_ENABLE_HYPERLINKS] = value;
		}

		/// <summary>
		/// 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.
		/// </summary>
		[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;
		}

		/// <summary>
		/// 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.
		/// </summary>
		[DefaultValue(null), Localizable(true),
		 Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]
		[Category("Appearance"), Description("")]
		public string ExpandedControlText { get; set; }

		/// <summary>
		/// 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: <A HREF="executablestring">Hyper-link Text</A>.
		/// WARNING: Enabling hyper-links when using content from an unsafe source may cause security vulnerabilities.
		/// </summary>
		[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);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		[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;
		}

		/// <summary>
		/// 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: <A HREF="executablestring">Hyper-link Text</A>.
		/// WARNING: Enabling hyper-links when using content from an unsafe source may cause security vulnerabilities.
		/// </summary>
		[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);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		[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);
			}
		}

		/// <summary>Gets the image for the configured footer icon.</summary>
		/// <value>The main icon image.</value>
		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
		public Image FooterIconImage => GetSmallImage(customFooterIcon ?? IconFromTaskDialogIcon(footerIcon));

		/// <summary>Gets the handle for the active dialog.</summary>
		/// <value>The handle. This value will be <c>IntPtr.Zero</c> if no active dialog is being displayed.</value>
		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
		public IntPtr Handle => (IntPtr)handle;

		/// <summary>
		/// 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.
		/// </summary>
		[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);
				}
			}
		}

		/// <summary>Gets the image for the configured main icon.</summary>
		/// <value>The main icon image.</value>
		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
		public Image MainIconImage => (customFooterIcon ?? IconFromTaskDialogIcon(footerIcon)).ToBitmap();

		/// <summary>The string to be used for the main instruction.</summary>
		[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);
				}
			}
		}

		/// <summary>Indicates that the TaskDialog should have no default radio button.</summary>
		[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;
		}

		/// <summary>
		/// 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.
		/// </summary>
		[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;
		}

		/// <summary>
		/// The progress bar for the <see cref="TaskDialog"/>. This will only be visible if the <see cref="TaskDialogProgressBar.Visible"/>
		/// property is set to <c>true</c>.
		/// </summary>
		/// <value>The progress bar.</value>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		[Category("Appearance"), Description("")]
		public TaskDialogProgressBar ProgressBar { get; }

		/// <summary>Specifies the radio buttons to display in the dialog.</summary>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		[Category("Appearance"), Description("")]
		public TaskDialogButtonCollection<TaskDialogRadioButton> RadioButtons { get; } =
			new TaskDialogButtonCollection<TaskDialogRadioButton>();

		/// <summary>Gets the full set of results for the last showing of the <see cref="TaskDialog"/>.</summary>
		/// <value>The result set.</value>
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public TaskDialogResult Result { get; private set; }

		/// <summary>Indicates that the TaskDialog should have right to left layout.</summary>
		[DefaultValue(false)]
		[Category("Appearance"), Description("")]
		public bool RightToLeftLayout
		{
			get => flags[TASKDIALOG_FLAGS.TDF_RTL_LAYOUT];
			set => flags[TASKDIALOG_FLAGS.TDF_RTL_LAYOUT] = value;
		}

		/// <summary>
		/// Indicates that the width of the task dialog is determined by the width of its content area. This flag is ignored if <see
		/// cref="Width"/> is not set to 0.
		/// </summary>
		[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;
		}

		/// <summary>Gets or sets a value indicating whether the form should be displayed as a topmost form.</summary>
		[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;
		}

		/// <summary>
		/// 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.
		/// </summary>
		[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;
		}

		/// <summary>
		/// 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.
		/// </summary>
		[DefaultValue(null), Localizable(true),
		 Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]
		[Category("Appearance"), Description("")]
		public string VerificationText { get; set; }

		/// <summary>width of the Task Dialog's client area in DLU's. If 0, Task Dialog will calculate the ideal width.</summary>
		[DefaultValue(0)]
		[Category("Layout"), Description("")]
		public int Width { get; set; }

		/// <summary>
		/// The string to be used for the dialog box title. If this parameter is NULL, the filename of the executable program is used.
		/// </summary>
		[DefaultValue(null), Localizable(true),
		 Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]
		[Category("Appearance"), Description("")]
		public string WindowTitle { get; set; }

		/// <summary>Indicates that an Marquee Progress Bar should be displayed.</summary>
		[DefaultValue(false)]
		internal bool ShowMarqueeProgressBar
		{
			get => flags[TASKDIALOG_FLAGS.TDF_SHOW_MARQUEE_PROGRESS_BAR]; set => flags[TASKDIALOG_FLAGS.TDF_SHOW_MARQUEE_PROGRESS_BAR] = value;
		}

		/// <summary>Indicates that a Progress Bar should be displayed.</summary>
		[DefaultValue(false)]
		internal bool ShowProgressBar
		{
			get => flags[TASKDIALOG_FLAGS.TDF_SHOW_PROGRESS_BAR];
			set => flags[TASKDIALOG_FLAGS.TDF_SHOW_PROGRESS_BAR] = value;
		}

		/// <summary>
		/// 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.
		/// </summary>
		[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;
		}

		/// <summary>
		/// 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.
		/// </summary>
		[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;
		}

		/// <summary>Gets or sets a value indicating whether this instance can raise events.</summary>
		/// <value><c>true</c> if this instance can raise events; otherwise, <c>false</c>.</value>
		protected override bool CanRaiseEvents => true;

		/// <summary>
		/// Displays a task dialog in front of the specified window and with the specified main instruction, content, caption, buttons, and icon.
		/// </summary>
		/// <param name="win32Window">An implementation of <see cref="IWin32Window"/> that will own the modal dialog box.</param>
		/// <param name="mainInstruction">The text to display as the main instruction.</param>
		/// <param name="content">The text to show as content below the main instruction. Value can be <c>null</c>.</param>
		/// <param name="caption">The text to display in the title bar.</param>
		/// <param name="buttons">
		/// One or more of the <see cref="TaskDialogCommonButtons"/> values that specifies which buttons to display in the task dialog.
		/// </param>
		/// <param name="icon">One of the <see cref="TaskDialogIcon"/> values that specifies which icon to display in the task dialog.</param>
		/// <returns>One of the <see cref="DialogResult"/> values.</returns>
		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;
		}

		/// <summary>
		/// Displays a task dialog in front of the specified window and with the specified main instruction, content, caption, buttons, and icon.
		/// </summary>
		/// <param name="win32Window">An implementation of <see cref="IWin32Window"/> that will own the modal dialog box.</param>
		/// <param name="mainInstruction">The text to display as the main instruction.</param>
		/// <param name="content">The text to show as content below the main instruction. Value can be <c>null</c>.</param>
		/// <param name="caption">The text to display in the title bar.</param>
		/// <param name="radioButtons">Array of labels for radio buttons.</param>
		/// <param name="buttons">
		/// One or more of the <see cref="TaskDialogCommonButtons"/> values that specifies which buttons to display in the task dialog.
		/// </param>
		/// <param name="icon">One of the <see cref="TaskDialogIcon"/> values that specifies which icon to display in the task dialog.</param>
		/// <returns>
		/// The 1-based index of the selected radio button, or <c>0</c> if no radio button was selected or the task dialog was cancelled.
		/// </returns>
		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;
		}

		/// <summary>Displays a task dialog with the specified main instruction, content, caption, buttons, and icon.</summary>
		/// <param name="mainInstruction">The text to display as the main instruction.</param>
		/// <param name="content">The text to show as content below the main instruction. Value can be <c>null</c>.</param>
		/// <param name="caption">The text to display in the title bar.</param>
		/// <param name="buttons">
		/// One or more of the <see cref="TaskDialogCommonButtons"/> values that specifies which buttons to display in the task dialog.
		/// </param>
		/// <param name="icon">One of the <see cref="TaskDialogIcon"/> values that specifies which icon to display in the task dialog.</param>
		/// <returns>One of the <see cref="DialogResult"/> values.</returns>
		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);

		/// <summary>
		/// Displays a task dialog in front of the specified window and with the specified main instruction, caption, custom buttons, and icon.
		/// </summary>
		/// <param name="win32Window">An implementation of <see cref="IWin32Window"/> that will own the modal dialog box.</param>
		/// <param name="mainInstruction">The text to display as the main instruction.</param>
		/// <param name="caption">The text to display in the title bar.</param>
		/// <param name="buttons">
		/// 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.
		/// </param>
		/// <param name="icon">One of the <see cref="TaskDialogIcon"/> values that specifies which icon to display in the task dialog.</param>
		/// <returns>
		/// 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.).
		/// </returns>
		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;
		}

		/// <summary>Displays a task dialog with the specified main instruction, caption, custom buttons, and icon.</summary>
		/// <param name="mainInstruction">The text to display as the main instruction.</param>
		/// <param name="caption">The text to display in the title bar.</param>
		/// <param name="buttons">
		/// 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.
		/// </param>
		/// <param name="icon">One of the <see cref="TaskDialogIcon"/> values that specifies which icon to display in the task dialog.</param>
		/// <returns>
		/// 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.).
		/// </returns>
		public static int Show(string mainInstruction, string caption, string[] buttons,
			TaskDialogIcon icon = TaskDialogIcon.None) =>
				Show(null, mainInstruction, caption, buttons, icon);

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="buttonId">Indicates the button ID to be selected.</param>
		/// <returns>If the function succeeds the return value is true.</returns>
		// 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);
		}

		/// <summary>Check or uncheck the verification checkbox in the TaskDialog.</summary>
		/// <param name="checkedState">The checked state to set the verification checkbox.</param>
		/// <param name="setKeyboardFocusToCheckBox">True to set the keyboard focus to the checkbox, and false otherwise.</param>
		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));
		}

		/// <summary>Resets the Task Dialog to the state when first constructed, all properties set to their default value.</summary>
		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;
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="buttonId">Indicates the button ID to be enabled or disabled.</param>
		/// <param name="enable">Enable the button if true. Disable the button if false.</param>
		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));
		}

		/// <summary>
		/// Enable or disable a radio button in the TaskDialog. The passed buttonID is the ButtonID set on a TaskDialogButton set on TaskDialog.RadioButtons.
		/// </summary>
		/// <param name="buttonId">Indicates the button ID to be enabled or disabled.</param>
		/// <param name="enable">Enable the button if true. Disable the button if false.</param>
		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);
			}
		}

		/// <summary>Raises the <see cref="ButtonClicked"/> event.</summary>
		/// <param name="id">The button identifier.</param>
		/// <returns><c>true</c> to prevent close; otherwise, <c>false</c>. Ignored for radio buttons.</returns>
		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;
		}

		/// <summary>Raises the <see cref="Closed"/> event.</summary>
		internal virtual void OnClosed() => Closed?.Invoke(this, EventArgs.Empty);

		/// <summary>Called when the expando button is clicked and the dialog expands or contracts.</summary>
		/// <param name="expanded"><c>true</c> if dialog is expanded; otherwise <c>false</c>.</param>
		internal virtual void OnExpanded(bool expanded) => Expanded?.Invoke(this, new ExpandedEventArgs(expanded));

		/// <summary>Raises the <see cref="LinkClicked"/> event.</summary>
		/// <param name="url">The URL of the link.</param>
		internal virtual void OnLinkClicked(string url) => LinkClicked?.Invoke(this, new LinkClickedEventArgs(url));

		/// <summary>Raises the <see cref="Load"/> event.</summary>
		internal virtual void OnLoad() => Load?.Invoke(this, EventArgs.Empty);

		/// <summary>Raises the <see cref="RadioButtonClicked"/> event.</summary>
		/// <param name="id">The radio button identifier.</param>
		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);
		}

		/// <summary>Raises the <see cref="Timer"/> event.</summary>
		/// <param name="ticks">The tick count.</param>
		/// <returns><c>true</c> to reset tick count; otherwise, <c>false</c> to continue to increment.</returns>
		internal virtual bool OnTimer(int ticks)
		{
			var a = new TimerEventArgs(ticks);
			Timer?.Invoke(this, a);
			return a.Reset;
		}

		/// <summary>Called when the verification check box is checked or unchecked.</summary>
		/// <param name="verificationChecked"><c>true</c> if check box is checked; otherwise <c>false</c>.</param>
		internal virtual void OnVerificationClicked(bool verificationChecked) => VerificationClicked?.Invoke(this, new VerificationClickedEventArgs(verificationChecked));

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="buttonId">Indicates the button ID to be selected.</param>
		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);
		}

		/// <summary>Designate whether a given Task Dialog button or command link should have a User Account Control (UAC) shield icon.</summary>
		/// <param name="buttonId">ID of the push button or command link to be updated.</param>
		/// <param name="elevationRequired">
		/// False to designate that the action invoked by the button does not require elevation; true to designate that the action does
		/// require elevation.
		/// </param>
		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));
		}

		/// <summary>
		/// Defines the common dialog box hook procedure that is overridden to add specific functionality to a common dialog box.
		/// </summary>
		/// <param name="hWnd">The handle to the dialog box window.</param>
		/// <param name="msg">The message being received.</param>
		/// <param name="wparam">Additional information about the message.</param>
		/// <param name="lparam">Additional information about the message.</param>
		/// <returns>
		/// A zero value if the default dialog box procedure processes the message; a nonzero value if the default dialog box procedure
		/// ignores the message.
		/// </returns>
		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);
		}

		/// <summary>The required implementation of CommonDialog that shows the Task Dialog.</summary>
		/// <param name="hwndOwner">Owner window. This can be null.</param>
		/// <returns>
		/// 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.
		/// </returns>
		protected override bool RunDialog(IntPtr hwndOwner)
		{
			var res = PrivateShow(hwndOwner);
			return (res.DialogResult != (int)DialogResult.Cancel);
		}

		/// <summary>The callback from the native Task Dialog. This prepares the friendlier arguments and calls the simpler callback.</summary>
		/// <param name="hwnd">The window handle of the Task Dialog that is active.</param>
		/// <param name="msg">The notification. A TaskDialogNotification value.</param>
		/// <param name="wparam">
		/// Specifies additional notification information. The contents of this parameter depends on the value of the msg parameter.
		/// </param>
		/// <param name="lparam">
		/// Specifies additional notification information. The contents of this parameter depends on the value of the msg parameter.
		/// </param>
		/// <param name="refData">Specifies the application-defined value given in the call to TaskDialogIndirect.</param>
		/// <returns>A HRESULT. It's not clear in the spec what a failed result will do.</returns>
		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();

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="hwndOwner">Owner window the task Dialog will modal to.</param>
		/// <returns>The set of results of the dialog.</returns>
		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; }

		/// <summary>Results from running the <see cref="TaskDialog"/>.</summary>
		public struct TaskDialogResult
		{
			internal int dialogResult, selectedRadioButton;
			internal bool verificationFlagChecked;

			internal TaskDialogResult(int result, bool verCheck, int selRadio)
			{
				dialogResult = result;
				verificationFlagChecked = verCheck;
				selectedRadioButton = selRadio;
			}

			/// <summary>
			/// Gets the dialog result. This is the value of one of the <see cref="TaskDialogCommonButtons"/> enumerate type or the ID of a
			/// <see cref="TaskDialogButton"/> that has been clicked.
			/// </summary>
			/// <value>The task dialog result.</value>
			public int DialogResult => dialogResult;

			/// <summary>If a radio button was supplied and selected, gets the value of the selected radio button's identifier.</summary>
			/// <value>
			/// The selected radio button id. A value of <c>0</c> indicates that no radio button was selected or the task dialog was cancelled.
			/// </value>
			public int SelectedRadioButton => selectedRadioButton;

			/// <summary>Gets a value indicating whether the verification flag was checked.</summary>
			/// <value><c>true</c> if verification flag checked; otherwise, <c>false</c>.</value>
			public bool VerificationFlagChecked => verificationFlagChecked;

			/// <summary>Returns a <see cref="string"/> that represents this instance.</summary>
			/// <returns>A <see cref="string"/> that represents this instance.</returns>
			public override string ToString()
				=> $"{dialogResult} : {(verificationFlagChecked ? "checked" : "unchecked")} : {selectedRadioButton}";
		}

		/// <summary>Provides data for the <see cref="ButtonClicked"/> and the <see cref="RadioButtonClicked"/> events.</summary>
		public class ButtonClickedEventArgs : CancelEventArgs
		{
			internal ButtonClickedEventArgs(int id, TaskDialogButtonBase button)
			{
				ButtonId = id; Button = button;
			}

			/// <summary>Gets the button that was clicked.</summary>
			/// <value>The button.</value>
			public TaskDialogButtonBase Button { get; }

			/// <summary>Gets the id of the button clicked.</summary>
			/// <value>The button id.</value>
			public int ButtonId { get; }
		}

		/// <summary>Provides data for the <see cref="Expanded"/> event.</summary>
		public class ExpandedEventArgs : EventArgs
		{
			internal ExpandedEventArgs(bool exp) => Expanded = exp;

			/// <summary>Gets a value indicating whether the <see cref="TaskDialog"/> is expanded.</summary>
			/// <value><c>true</c> if expanded; otherwise, <c>false</c>.</value>
			public bool Expanded { get; }
		}

		/// <summary>A custom button for the TaskDialog.</summary>
		[DefaultEvent("Clicked"), DefaultProperty("ButtonId")]
		public abstract class TaskDialogButtonBase : IEquatable<TaskDialogButtonBase>
		{
			internal static int idSeed = 101;
			internal TASKDIALOG_BUTTON nativeButton;
			internal TaskDialog parent;

			/// <summary>Initializes a new instance of the <see cref="TaskDialogButtonBase"/> class.</summary>
			protected TaskDialogButtonBase() : this(null) { }

			/// <summary>Initialize the custom button.</summary>
			/// <param name="id">
			/// 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.
			/// </param>
			/// <param name="text">The string that appears on the button.</param>
			protected TaskDialogButtonBase(string text, int id = -1)
			{
				if (id == -1) id = idSeed++;
				nativeButton.buttonId = id;
				nativeButton.buttonText = text;
			}

			/// <summary>The ID of the button. This value is returned by TaskDialog.Show when the button is clicked.</summary>
			[DefaultValue(0)]
			[Category("Behavior"), Description("")]
			public int ButtonId
			{
				get => nativeButton.buttonId; set => nativeButton.buttonId = value;
			}

			/// <summary>The string that appears on the button.</summary>
			[DefaultValue(null), Localizable(true),
			 Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]
			[Category("Appearance"), Description("")]
			public string ButtonText
			{
				get => nativeButton.buttonText; set => nativeButton.buttonText = value;
			}

			/// <summary>Gets or sets a value indicating whether this <see cref="TaskDialogButtonBase"/> is enabled.</summary>
			/// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
			[DefaultValue(true)]
			[Category("Appearance"), Description("")]
			public virtual bool Enabled { get; set; } = true;

			/// <summary>Determines whether the specified <see cref="object"/>, is equal to this instance.</summary>
			/// <param name="obj">The <see cref="object"/> to compare with this instance.</param>
			/// <returns><c>true</c> if the specified <see cref="object"/> is equal to this instance; otherwise, <c>false</c>.</returns>
			public override bool Equals(object obj) => obj is TaskDialogButtonBase bb ? Equals(bb) : base.Equals(obj);

			/// <summary>Determines whether the specified <see cref="TaskDialogButtonBase"/>, is equal to this instance.</summary>
			/// <param name="other">The <see cref="TaskDialogButtonBase"/> to compare with this instance.</param>
			/// <returns><c>true</c> if the specified <see cref="TaskDialogButtonBase"/> is equal to this instance; otherwise, <c>false</c>.</returns>
			public bool Equals(TaskDialogButtonBase other)
				=> other.nativeButton.buttonId == nativeButton.buttonId && other.nativeButton.buttonText == nativeButton.buttonText;

			/// <summary>Returns a hash code for this instance.</summary>
			/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
			public override int GetHashCode() => new { a = ButtonId, b = ButtonText }.GetHashCode();

			/// <summary>Returns a <see cref="string"/> that represents this instance.</summary>
			/// <returns>A <see cref="string"/> that represents this instance.</returns>
			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
			{
				/// <summary>The ID of the button. This value is returned by TaskDialog.Show when the button is clicked.</summary>
				public int buttonId;

				/// <summary>The string that appears on the button.</summary>
				[MarshalAs(UnmanagedType.LPWStr)] public string buttonText;
			}
		}

		/// <summary>A collection of <see cref="TaskDialogButton"/> elements.</summary>
		public class TaskDialogButtonCollection<T> : List<T>, IDisposable where T : TaskDialogButtonBase
		{
			private const int hashSeed = 17;
			private IntPtr ptr = IntPtr.Zero;
			private int ptrHash = hashSeed;

			/// <summary>Initializes a new instance of the <see cref="TaskDialogButtonCollection{T}"/> class.</summary>
			internal TaskDialogButtonCollection() { }

			/// <summary>Explicitly converts the <see cref="TaskDialogButtonCollection{T}"/> to an in-memory array.</summary>
			/// <param name="c">The <see cref="TaskDialogButtonCollection{T}"/> instance.</param>
			/// <returns>
			/// 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.
			/// </returns>
			public static explicit operator IntPtr(TaskDialogButtonCollection<T> 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<TaskDialogButtonBase.TASKDIALOG_BUTTON>(Marshal.AllocHGlobal, out var _);
					// Set hash to new value
					c.ptrHash = h;
				}

				return c.ptr;
			}

			/// <summary>Clears this instance.</summary>
			public new void Clear()
			{
				base.Clear();
				((IDisposable)this).Dispose();
			}

			/// <summary>Releases unmanaged and managed resources.</summary>
			void IDisposable.Dispose()
			{
				if (ptr == IntPtr.Zero) return;
				lock (this)
				{
					Marshal.FreeHGlobal(ptr);
					ptr = IntPtr.Zero;
					ptrHash = hashSeed;
				}
			}
		}

		/// <summary>Represents the progress bar that can be displayed in a task dialog.</summary>
		[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;

			/// <summary>Gets or sets the marquee animation speed.</summary>
			/// <value>The marquee animation speed.</value>
			[DefaultValue(100)]
			public int MarqueeAnimationSpeed
			{
				get => mSpeed; set { if (mSpeed != value) SetMarqueeSpeed(mSpeed = value); }
			}

			/// <summary>Gets or sets the maximum progress value.</summary>
			/// <value>The maximum.</value>
			[DefaultValue((short)100)]
			public short Maximum
			{
				get => max; set
				{
					if (max == value) return;
					max = value;
					SetProgressBarRange();
				}
			}

			/// <summary>Gets or sets the minimum progress value.</summary>
			/// <value>The minimum.</value>
			[DefaultValue((short)0)]
			public short Minimum
			{
				get => min; set
				{
					if (min == value) return;
					min = value;
					SetProgressBarRange();
				}
			}

			/// <summary>Gets or sets the current state of the progress bar.</summary>
			/// <value>The state.</value>
			[DefaultValue(typeof(ProgressBarState), "Normal")]
			public ProgressBarState State
			{
				get => state; set { if (state != value) SetState(state = value); }
			}

			/// <summary>Gets or sets the style of the progress bar.</summary>
			/// <value>The style.</value>
			/// <exception cref="System.NotSupportedException">TaskDialog does not support Block style progress bars.</exception>
			[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);
					}
				}
			}

			/// <summary>Gets or sets the value of the progress bar.</summary>
			/// <value>The value.</value>
			[DefaultValue(0)]
			public int Value
			{
				get => val; set { if (val != value) SetValue(val = value); }
			}

			/// <summary>Gets or sets a value indicating whether this <see cref="TaskDialogProgressBar"/> is visible.</summary>
			/// <value><see langword="true"/> if visible; otherwise, <see langword="false"/>.</value>
			[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);
			}
		}

		/// <summary>Provides data for the <see cref="Timer"/> event.</summary>
		public class TimerEventArgs : EventArgs
		{
			internal TimerEventArgs(int ticks) => TickCount = ticks;

			/// <summary>Gets or sets a value indicating whether to reset the tick count.</summary>
			/// <value><c>true</c> to reset tick count; otherwise, <c>false</c> to continue to increment.</value>
			public bool Reset { get; set; } = false;

			/// <summary>Gets the tick count for this instance of the <see cref="TaskDialog"/>.</summary>
			/// <value>The tick count.</value>
			public int TickCount { get; }
		}

		/// <summary>Provides data for the <see cref="VerificationClicked"/> event.</summary>
		public class VerificationClickedEventArgs : EventArgs
		{
			internal VerificationClickedEventArgs(bool check) => Checked = check;

			/// <summary>Gets a value indicating whether the verification check box is checked.</summary>
			/// <value><c>true</c> if checked; otherwise, <c>false</c>.</value>
			public bool Checked { get; }
		}
	}

	/// <summary>Represents a button on a task dialog.</summary>
	/// <seealso cref="Vanara.Windows.Forms.TaskDialog.TaskDialogButtonBase"/>
	public class TaskDialogButton : TaskDialog.TaskDialogButtonBase
	{
		private bool shield;

		/// <summary>Initializes a new instance of the <see cref="TaskDialogButton"/> class.</summary>
		public TaskDialogButton() : this(null) { }

		/// <summary>Initialize the custom button.</summary>
		/// <param name="text">The string that appears on the button.</param>
		/// <param name="id">
		/// 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.
		/// </param>
		public TaskDialogButton(string text, int id = -1) : base(text, id)
		{
			if (ButtonText == null) ButtonText = $"Button{ButtonId}";
		}

		/// <summary>Occurs when the button is clicked.</summary>
		[Category("Action"), Description("")]
		public event CancelEventHandler Click;

		/// <summary>
		/// Gets or sets a value indicating whether to close the <see cref="TaskDialog"/> on click and returns this button's <see
		/// cref="TaskDialog.TaskDialogButtonBase.ButtonId"/> as the result.
		/// </summary>
		/// <value><c>true</c> if dialog closes on click; otherwise, <c>false</c>.</value>
		[DefaultValue(true)]
		[Category("Behavior"), Description("")]
		public bool CloseOnClick { get; set; } = true;

		/// <summary>Gets or sets a value indicating whether button shows elevated state shield.</summary>
		/// <value><c>true</c> if elevated state shield displayed; otherwise, <c>false</c>.</value>
		[DefaultValue(false)]
		[Category("Appearance"), Description("")]
		public bool ElevatedStateRequired
		{
			get => shield;
			set
			{
				if (shield != value)
				{
					parent?.SetButtonElevationRequiredState(ButtonId, value);
					shield = value;
				}
			}
		}

		/// <summary>Gets or sets a value indicating whether this <see cref="TaskDialogButton"/> is enabled.</summary>
		/// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
		[DefaultValue(true)]
		public override bool Enabled
		{
			get => base.Enabled; set
			{
				if (base.Enabled != value)
				{
					parent?.EnableButton(ButtonId, value);
					base.Enabled = value;
				}
			}
		}

		/// <summary>Raises the <see cref="E:Click"/> event.</summary>
		/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
		public void OnClick(CancelEventArgs e)
		{
			e.Cancel = !CloseOnClick;
			Click?.Invoke(this, e);
		}

		/// <summary>Generates a <see cref="Click"/> event for the button.</summary>
		public void PerformClick() => parent?.PerformButtonClick(ButtonId);
	}

	/// <summary>Represents a radio button on a task dialog.</summary>
	/// <seealso cref="Vanara.Windows.Forms.TaskDialog.TaskDialogButtonBase"/>
	public class TaskDialogRadioButton : TaskDialog.TaskDialogButtonBase
	{
		/// <summary>Initializes a new instance of the <see cref="TaskDialogButton"/> class.</summary>
		public TaskDialogRadioButton() : this(null) { }

		/// <summary>Initialize the custom radio button.</summary>
		/// <param name="text">The string that appears on the radio button.</param>
		/// <param name="id">
		/// 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.
		/// </param>
		public TaskDialogRadioButton(string text, int id = -1) : base(text, id)
		{
			if (ButtonText == null) ButtonText = $"RadioButton{ButtonId}";
		}

		/// <summary>Occurs when the radio button is clicked.</summary>
		public event EventHandler Click;

		/// <summary>Gets or sets a value indicating whether this <see cref="TaskDialogRadioButton"/> is enabled.</summary>
		/// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
		[DefaultValue(true)]
		public override bool Enabled
		{
			get => base.Enabled; set
			{
				if (base.Enabled != value)
				{
					parent?.EnableRadioButton(ButtonId, value);
					base.Enabled = value;
				}
			}
		}

		/// <summary>Raises the <see cref="E:Click"/> event.</summary>
		/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
		public void OnClick(EventArgs e) => Click?.Invoke(this, e);

		/// <summary>Generates a <see cref="Click"/> event for the button.</summary>
		public void PerformClick() => parent?.PerformRadioButtonClick(ButtonId);
	}
}