Click here to Skip to main content
15,892,746 members
Articles / Programming Languages / C#

Vista Task Dialog and Emulating It Redux

Rate me:
Please Sign up or sign in to vote.
4.57/5 (15 votes)
5 Oct 2008BSD3 min read 48.5K   897   36  
Revisiting Vista Task Dialog and emulating it.
/*
 * Copyright © 2007 KevinGre
 * 
 * Design Notes:-
 * --------------
 * References:
 * - http://www.codeproject.com/KB/vista/TaskDialogWinForms.aspx
 * 
 * Revision Control:-
 * ------------------
 * Created On: 2007 January 02
 * 
 * $Revision: 363 $
 * $LastChangedDate: 2008-10-20 16:11:22 +0800 (Mon, 20 Oct 2008) $
 */
using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace CustomControls.TaskDialog
{
    /// <summary>
    /// The signature of the callback that recieves notificaitons from the Task Dialog.
    /// </summary>
    /// <param name="taskDialog">The active task dialog which has methods that can be performed on an active Task Dialog.</param>
    /// <param name="args">The notification arguments including the type of notification and information for the notification.</param>
    /// <param name="callbackData">The value set on TaskDialog.CallbackData</param>
    /// <returns>Return value meaning varies depending on the Notification member of args.</returns>
    public delegate bool TaskDialogCallback ( ActiveTaskDialog taskDialog, TaskDialogNotificationArgs args, object callbackData );

    /// <summary>
    /// The TaskDialog common button flags used to specify the builtin bottons 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 = 0x0001,

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

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

        /// <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 = 0x0008,

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

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

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

        /// <summary>
        /// System warning icon.
        /// </summary>
        Warning = 0xFFFF, // MAKEINTRESOURCEW(-1)

        /// <summary>
        /// System Error icon.
        /// </summary>
        Error = 0xFFFE, // MAKEINTRESOURCEW(-2)

        /// <summary>
        /// System Information icon.
        /// </summary>
        Information = 0xFFFD, // MAKEINTRESOURCEW(-3)

        /// <summary>
        /// Shield icon.
        /// </summary>
        Shield = 0xFFFC, // MAKEINTRESOURCEW(-4)
    }

    /// <summary>
    /// Task Dialog callback notifications. 
    /// </summary>
    public enum TaskDialogNotification
    {
        /// <summary>
        /// Sent by the Task Dialog once the dialog has been created and before it is displayed.
        /// The value returned by the callback is ignored.
        /// </summary>
        Created = 0,

        //// Spec is not clear what this is so not supporting it.
        ///// <summary>
        ///// Sent by the Task Dialog when a navigation has occurred.
        ///// The value returned by the callback is ignored.
        ///// </summary>   
        // Navigated = 1,

        /// <summary>
        /// Sent by the Task Dialog when the user selects a button or command link in the task dialog.
        /// The button ID corresponding to the button selected will be available in the
        /// TaskDialogNotificationArgs. To prevent the Task Dialog from closing, the application must
        /// return true, otherwise the Task Dialog will be closed and the button ID returned to via
        /// the original application call.
        /// </summary>
        ButtonClicked = 2,            // wParam = Button ID

        /// <summary>
        /// Sent by the Task Dialog when the user clicks on a hyperlink in the Task Dialog’s content.
        /// The string containing the HREF of the hyperlink will be available in the
        /// TaskDialogNotificationArgs. To prevent the TaskDialog from shell executing the hyperlink,
        /// the application must return TRUE, otherwise ShellExecute will be called.
        /// </summary>
        HyperlinkClicked = 3,            // lParam = (LPCWSTR)pszHREF

        /// <summary>
        /// Sent by the Task Dialog approximately every 200 milliseconds when TaskDialog.CallbackTimer
        /// has been set to true. The number of milliseconds since the dialog was created or the
        /// notification returned true is available on the TaskDialogNotificationArgs. To reset
        /// the tickcount, the application must return true, otherwise the tickcount will continue to
        /// increment.
        /// </summary>
        Timer = 4,            // wParam = Milliseconds since dialog created or timer reset

        /// <summary>
        /// Sent by the Task Dialog when it is destroyed and its window handle no longer valid.
        /// The value returned by the callback is ignored.
        /// </summary>
        Destroyed = 5,

        /// <summary>
        /// Sent by the Task Dialog when the user selects a radio button in the task dialog.
        /// The button ID corresponding to the button selected will be available in the
        /// TaskDialogNotificationArgs.
        /// The value returned by the callback is ignored.
        /// </summary>
        RadioButtonClicked = 6,            // wParam = Radio Button ID

        /// <summary>
        /// Sent by the Task Dialog once the dialog has been constructed and before it is displayed.
        /// The value returned by the callback is ignored.
        /// </summary>
        DialogConstructed = 7,

        /// <summary>
        /// Sent by the Task Dialog when the user checks or unchecks the verification checkbox.
        /// The verificationFlagChecked value is available on the TaskDialogNotificationArgs.
        /// The value returned by the callback is ignored.
        /// </summary>
        VerificationClicked = 8,             // wParam = 1 if checkbox checked, 0 if not, lParam is unused and always 0

        /// <summary>
        /// Sent by the Task Dialog when the user presses F1 on the keyboard while the dialog has focus.
        /// The value returned by the callback is ignored.
        /// </summary>
        Help = 9,

        /// <summary>
        /// Sent by the task dialog when the user clicks on the dialog's expando button.
        /// The expanded value is available on the TaskDialogNotificationArgs.
        /// The value returned by the callback is ignored.
        /// </summary>
        ExpandoButtonClicked = 10            // wParam = 0 (dialog is now collapsed), wParam != 0 (dialog is now expanded)
    }

    /// <summary>
    /// Progress bar state.
    /// </summary>
    [SuppressMessage ( "Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue" )] // Comes from CommCtrl.h PBST_* values which don't have a zero.
    public enum ProgressBarState
    {
        /// <summary>
        /// Normal.
        /// </summary>
        Normal = 1,

        /// <summary>
        /// Error state.
        /// </summary>
        Error = 2,

        /// <summary>
        /// Paused state.
        /// </summary>
        Paused = 3
    }

    /// <summary>
    /// A custom button for the TaskDialog.
    /// </summary>
    [SuppressMessage ( "Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes" )] // Would be unused code as not required for usage.
    [StructLayout ( LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1 )]
    public struct TaskDialogButton
    {
        /// <summary>
        /// The ID of the button. This value is returned by TaskDialog.Show when the button is clicked.
        /// </summary>
        private int buttonId;

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

        /// <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>
        public TaskDialogButton ( int id, string text )
        {
            this.buttonId = id;
            this.buttonText = text;
        }

        /// <summary>
        /// The ID of the button. This value is returned by TaskDialog.Show when the button is clicked.
        /// </summary>
        public int ButtonId
        {
            get { return this.buttonId; }
            set { this.buttonId = value; }
        }

        /// <summary>
        /// The string that appears on the button.
        /// </summary>
        public string ButtonText
        {
            get { return this.buttonText; }
            set { this.buttonText = value; }
        }
    }

    /// <summary>
    /// A Task Dialog. This is like a MessageBox but with many more features. TaskDialog requires Windows Longhorn or later.
    /// </summary>
    public class TaskDialog
    {
        /// <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>
        private string windowTitle;

        /// <summary>
        /// The string to be used for the main instruction.
        /// </summary>
        private string mainInstruction;

        /// <summary>
        /// The string to be used for the dialog’s primary content. If the EnableHyperlinks member is true,
        /// then this string may contain hyperlinks in the form: <A HREF="executablestring">Hyperlink Text</A>. 
        /// WARNING: Enabling hyperlinks when using content from an unsafe source may cause security vulnerabilities.
        /// </summary>
        private string content;

        /// <summary>
        /// Specifies the push buttons displayed in the dialog box.  This parameter may be a combination of flags.
        /// 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>
        private TaskDialogCommonButtons commonButtons;

        /// <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>
        private TaskDialogIcon mainIcon;

        /// <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>
        private Icon customMainIcon;

        /// <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>
        private TaskDialogIcon footerIcon;

        /// <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>
        private Icon customFooterIcon;

        /// <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>
        private TaskDialogButton[] buttons;

        /// <summary>
        /// Specifies the radio buttons to display in the dialog.
        /// </summary>
        private TaskDialogButton[] radioButtons;

        /// <summary>
        /// The flags passed to TaskDialogIndirect.
        /// </summary>
        private UnsafeNativeMethods.TASKDIALOG_FLAGS flags;

        /// <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>
        private int defaultButton;

        /// <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>
        private int defaultRadioButton;

        /// <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>
        private string verificationText;

        /// <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 EnableHyperlinks member is true, then this string
        /// may contain hyperlinks in the form: <A HREF="executablestring">Hyperlink Text</A>.
        /// WARNING: Enabling hyperlinks when using content from an unsafe source may cause security vulnerabilities.
        /// </summary>
        private string expandedInformation;

        /// <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>
        private string expandedControlText;

        /// <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>
        private string collapsedControlText;

        /// <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 hyperlinks in the form: <A HREF="executablestring">
        /// Hyperlink Text</A>.
        /// WARNING: Enabling hyperlinks when using content from an unsafe source may cause security vulnerabilities.
        /// </summary>
        private string footer;

        /// <summary>
        /// The callback that receives messages from the Task Dialog when various events occur.
        /// </summary>
        private TaskDialogCallback callback;

        /// <summary>
        /// Reference that is passed to the callback.
        /// </summary>
        private object callbackData;

        /// <summary>
        /// Specifies the width of the Task Dialog’s client area in DLU’s. If 0, Task Dialog will calculate the ideal width.
        /// </summary>
        private uint width;

        /// <summary>
        /// Creates a default Task Dialog.
        /// </summary>
        public TaskDialog ( )
        {
            this.Reset ( );
        }

        /// <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>
        public static bool IsAvailable
        {
            get
            {
                OperatingSystem os = Environment.OSVersion;
                if ( os.Platform != PlatformID.Win32NT )
                {
                    return false;
                }

                return ( os.Version.CompareTo ( TaskDialog.RequiredOSVersion ) >= 0 );
            }
        }

        /// <summary>
        /// The minimum Windows version needed to support TaskDialog.
        /// </summary>
        public static Version RequiredOSVersion
        {
            get { return new Version ( 6, 0, 5243 ); }
        }

        /// <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>
        public string WindowTitle
        {
            get { return this.windowTitle; }
            set { this.windowTitle = value; }
        }

        /// <summary>
        /// The string to be used for the main instruction.
        /// </summary>
        public string MainInstruction
        {
            get { return this.mainInstruction; }
            set { this.mainInstruction = value; }
        }

        /// <summary>
        /// The string to be used for the dialog’s primary content. If the EnableHyperlinks member is true,
        /// then this string may contain hyperlinks in the form: <A HREF="executablestring">Hyperlink Text</A>. 
        /// WARNING: Enabling hyperlinks when using content from an unsafe source may cause security vulnerabilities.
        /// </summary>
        public string Content
        {
            get { return this.content; }
            set { this.content = value; }
        }

        /// <summary>
        /// Specifies the push buttons displayed in the dialog box. This parameter may be a combination of flags.
        /// 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>
        public TaskDialogCommonButtons CommonButtons
        {
            get { return this.commonButtons; }
            set { this.commonButtons = value; }
        }

        /// <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>
        public TaskDialogIcon MainIcon
        {
            get { return this.mainIcon; }
            set { this.mainIcon = value; }
        }

        /// <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>
        public Icon CustomMainIcon
        {
            get { return this.customMainIcon; }
            set { this.customMainIcon = value; }
        }

        /// <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>
        public TaskDialogIcon FooterIcon
        {
            get { return this.footerIcon; }
            set { this.footerIcon = value; }
        }

        /// <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>
        public Icon CustomFooterIcon
        {
            get { return this.customFooterIcon; }
            set { this.customFooterIcon = value; }
        }

        /// <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>
        [SuppressMessage ( "Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly" )] // Style of use is like single value. Array is of value types.
        [SuppressMessage ( "Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays" )] // Returns a reference, not a copy.
        public TaskDialogButton[] Buttons
        {
            get
            {
                return this.buttons;
            }

            set
            {
                if ( value == null )
                {
                    throw new ArgumentNullException ( "value" );
                }

                this.buttons = value;
            }
        }

        /// <summary>
        /// Specifies the radio buttons to display in the dialog.
        /// </summary>
        [SuppressMessage ( "Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly" )] // Style of use is like single value. Array is of value types.
        [SuppressMessage ( "Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays" )] // Returns a reference, not a copy.
        public TaskDialogButton[] RadioButtons
        {
            get
            {
                return this.radioButtons;
            }

            set
            {
                if ( value == null )
                {
                    throw new ArgumentNullException ( "value" );
                }

                this.radioButtons = value;
            }
        }

        /// <summary>
        /// Enables hyperlink processing for the strings specified in the Content, ExpandedInformation
        /// and FooterText members. When enabled, these members may be strings that contain hyperlinks
        /// in the form: <A HREF="executablestring">Hyperlink Text</A>. 
        /// WARNING: Enabling hyperlinks when using content from an unsafe source may cause security vulnerabilities.
        /// Note: Task Dialog will not actually execute any hyperlinks. Hyperlink execution must be handled
        /// in the callback function specified by Callback member.
        /// </summary>
        public bool EnableHyperlinks
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_ENABLE_HYPERLINKS ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_ENABLE_HYPERLINKS, value ); }
        }

        /// <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>
        public bool AllowDialogCancellation
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_ALLOW_DIALOG_CANCELLATION ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_ALLOW_DIALOG_CANCELLATION, 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>
        public bool UseCommandLinks
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_USE_COMMAND_LINKS ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.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>
        public bool UseCommandLinksNoIcon
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_USE_COMMAND_LINKS_NO_ICON ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_USE_COMMAND_LINKS_NO_ICON, value ); }
        }

        /// <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>
        public bool ExpandFooterArea
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_EXPAND_FOOTER_AREA ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_EXPAND_FOOTER_AREA, 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>
        public bool ExpandedByDefault
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_EXPANDED_BY_DEFAULT ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_EXPANDED_BY_DEFAULT, 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>
        public bool VerificationFlagChecked
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_VERIFICATION_FLAG_CHECKED ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_VERIFICATION_FLAG_CHECKED, value ); }
        }

        /// <summary>
        /// Indicates that a Progress Bar should be displayed.
        /// </summary>
        public bool ShowProgressBar
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_SHOW_PROGRESS_BAR ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_SHOW_PROGRESS_BAR, value ); }
        }

        /// <summary>
        /// Indicates that an Marquee Progress Bar should be displayed.
        /// </summary>
        public bool ShowMarqueeProgressBar
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_SHOW_MARQUEE_PROGRESS_BAR ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_SHOW_MARQUEE_PROGRESS_BAR, value ); }
        }

        /// <summary>
        /// Indicates that the TaskDialog’s callback should be called approximately every 200 milliseconds.
        /// </summary>
        public bool CallbackTimer
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_CALLBACK_TIMER ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_CALLBACK_TIMER, 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>
        public bool PositionRelativeToWindow
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_POSITION_RELATIVE_TO_WINDOW ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_POSITION_RELATIVE_TO_WINDOW, value ); }
        }

        /// <summary>
        /// Indicates that the TaskDialog should have right to left layout.
        /// </summary>
        public bool RightToLeftLayout
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_RTL_LAYOUT ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_RTL_LAYOUT, value ); }
        }

        /// <summary>
        /// Indicates that the TaskDialog should have no default radio button.
        /// </summary>
        public bool NoDefaultRadioButton
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_NO_DEFAULT_RADIO_BUTTON ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_NO_DEFAULT_RADIO_BUTTON, value ); }
        }

        /// <summary>
        /// Indicates that the TaskDialog can be minimised. Works only if there if parent window is null. Will enable cancellation also.
        /// </summary>
        public bool CanBeMinimized
        {
            get { return ( this.flags & UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_CAN_BE_MINIMIZED ) != 0; }
            set { this.SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_CAN_BE_MINIMIZED, value ); }
        }

        /// <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>
        public int DefaultButton
        {
            get { return this.defaultButton; }
            set { this.defaultButton = value; }
        }

        /// <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>
        public int DefaultRadioButton
        {
            get { return this.defaultRadioButton; }
            set { this.defaultRadioButton = 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>
        public string VerificationText
        {
            get { return this.verificationText; }
            set { this.verificationText = value; }
        }

        /// <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 hyperlinks in the form: <A HREF="executablestring">Hyperlink Text</A>.
        /// WARNING: Enabling hyperlinks when using content from an unsafe source may cause security vulnerabilities.
        /// </summary>
        public string ExpandedInformation
        {
            get { return this.expandedInformation; }
            set { this.expandedInformation = 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>
        public string ExpandedControlText
        {
            get { return this.expandedControlText; }
            set { this.expandedControlText = 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>
        public string CollapsedControlText
        {
            get { return this.collapsedControlText; }
            set { this.collapsedControlText = 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 hyperlinks in the form: <A HREF="executablestring">
        /// Hyperlink Text</A>.
        /// WARNING: Enabling hyperlinks when using content from an unsafe source may cause security vulnerabilities.
        /// </summary>
        public string Footer
        {
            get { return this.footer; }
            set { this.footer = value; }
        }

        /// <summary>
        /// width of the Task Dialog's client area in DLU's. If 0, Task Dialog will calculate the ideal width.
        /// </summary>
        public uint Width
        {
            get { return this.width; }
            set { this.width = value; }
        }

        /// <summary>
        /// The callback that receives messages from the Task Dialog when various events occur.
        /// </summary>
        public TaskDialogCallback Callback
        {
            get { return this.callback; }
            set { this.callback = value; }
        }

        /// <summary>
        /// Reference that is passed to the callback.
        /// </summary>
        public object CallbackData
        {
            get { return this.callbackData; }
            set { this.callbackData = value; }
        }

        /// <summary>
        /// Resets the Task Dialog to the state when first constructed, all properties set to their default value.
        /// </summary>
        public void Reset ( )
        {
            this.windowTitle = null;
            this.mainInstruction = null;
            this.content = null;
            this.commonButtons = 0;
            this.mainIcon = TaskDialogIcon.None;
            this.customMainIcon = null;
            this.footerIcon = TaskDialogIcon.None;
            this.customFooterIcon = null;
            this.buttons = new TaskDialogButton[0];
            this.radioButtons = new TaskDialogButton[0];
            this.flags = 0;
            this.defaultButton = 0;
            this.defaultRadioButton = 0;
            this.verificationText = null;
            this.expandedInformation = null;
            this.expandedControlText = null;
            this.collapsedControlText = null;
            this.footer = null;
            this.callback = null;
            this.callbackData = null;
            this.width = 0;
        }

        /// <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>
        /// <returns>The result of the dialog, either a DialogResult value for common push buttons set in the CommonButtons
        /// member or the ButtonID from a TaskDialogButton structure set on the Buttons member.</returns>
        public int Show ( )
        {
            bool verificationFlagChecked;
            int radioButtonResult;
            return this.Show ( IntPtr.Zero, out verificationFlagChecked, out radioButtonResult );
        }

        /// <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="owner">Owner window the task Dialog will modal to.</param>
        /// <returns>The result of the dialog, either a DialogResult value for common push buttons set in the CommonButtons
        /// member or the ButtonID from a TaskDialogButton structure set on the Buttons member.</returns>
        public int Show ( IWin32Window owner )
        {
            bool verificationFlagChecked;
            int radioButtonResult;
            return this.Show ( ( owner == null ? IntPtr.Zero : owner.Handle ), out verificationFlagChecked, out radioButtonResult );
        }

        /// <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 result of the dialog, either a DialogResult value for common push buttons set in the CommonButtons
        /// member or the ButtonID from a TaskDialogButton structure set on the Buttons member.</returns>
        public int Show ( IntPtr hwndOwner )
        {
            bool verificationFlagChecked;
            int radioButtonResult;
            return this.Show ( hwndOwner, out verificationFlagChecked, out radioButtonResult );
        }

        /// <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="owner">Owner window the task Dialog will modal to.</param>
        /// <param name="verificationFlagChecked">Returns true if the verification checkbox was checked when the dialog
        /// was dismissed.</param>
        /// <returns>The result of the dialog, either a DialogResult value for common push buttons set in the CommonButtons
        /// member or the ButtonID from a TaskDialogButton structure set on the Buttons member.</returns>
        public int Show ( IWin32Window owner, out bool verificationFlagChecked )
        {
            int radioButtonResult;
            return this.Show ( ( owner == null ? IntPtr.Zero : owner.Handle ), out verificationFlagChecked, out radioButtonResult );
        }

        /// <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>
        /// <param name="verificationFlagChecked">Returns true if the verification checkbox was checked when the dialog
        /// was dismissed.</param>
        /// <returns>The result of the dialog, either a DialogResult value for common push buttons set in the CommonButtons
        /// member or the ButtonID from a TaskDialogButton structure set on the Buttons member.</returns>
        public int Show ( IntPtr hwndOwner, out bool verificationFlagChecked )
        {
            // We have to call a private version or PreSharp gets upset about a unsafe
            // block in a public method. (PreSharp error 56505)
            int radioButtonResult;
            return this.PrivateShow ( hwndOwner, out verificationFlagChecked, out radioButtonResult );
        }

        /// <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="owner">Owner window the task Dialog will modal to.</param>
        /// <param name="verificationFlagChecked">Returns true if the verification checkbox was checked when the dialog
        /// was dismissed.</param>
        /// <param name="radioButtonResult">The radio botton selected by the user.</param>
        /// <returns>The result of the dialog, either a DialogResult value for common push buttons set in the CommonButtons
        /// member or the ButtonID from a TaskDialogButton structure set on the Buttons member.</returns>
        public int Show ( IWin32Window owner, out bool verificationFlagChecked, out int radioButtonResult )
        {
            return this.Show ( ( owner == null ? IntPtr.Zero : owner.Handle ), out verificationFlagChecked, out radioButtonResult );
        }

        /// <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>
        /// <param name="verificationFlagChecked">Returns true if the verification checkbox was checked when the dialog
        /// was dismissed.</param>
        /// <param name="radioButtonResult">The radio botton selected by the user.</param>
        /// <returns>The result of the dialog, either a DialogResult value for common push buttons set in the CommonButtons
        /// member or the ButtonID from a TaskDialogButton structure set on the Buttons member.</returns>
        public int Show ( IntPtr hwndOwner, out bool verificationFlagChecked, out int radioButtonResult )
        {
            // We have to call a private version or PreSharp gets upset about a unsafe
            // block in a public method. (PreSharp error 56505)
            return this.PrivateShow ( hwndOwner, out verificationFlagChecked, out radioButtonResult );
        }

        /// <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>
        /// <param name="verificationFlagChecked">Returns true if the verification checkbox was checked when the dialog
        /// was dismissed.</param>
        /// <param name="radioButtonResult">The radio botton selected by the user.</param>
        /// <returns>The result of the dialog, either a DialogResult value for common push buttons set in the CommonButtons
        /// member or the ButtonID from a TaskDialogButton structure set on the Buttons member.</returns>
        private int PrivateShow ( IntPtr hwndOwner, out bool verificationFlagChecked, out int radioButtonResult )
        {
            verificationFlagChecked = false;
            radioButtonResult = 0;
            int result = 0;

            if ( !TaskDialog.IsAvailable )
            {
                // Hand it off to emulator.
                EmulateTaskDialog taskDialogEmulate = new EmulateTaskDialog ( this );
                taskDialogEmulate.ShowDialog ( );
                verificationFlagChecked = taskDialogEmulate.TaskDialogVerificationFlagChecked;
                radioButtonResult = taskDialogEmulate.TaskDialogRadioButtonResult;
                result = taskDialogEmulate.TaskDialogResult;

                return result;
            }
            
            UnsafeNativeMethods.TASKDIALOGCONFIG config = new UnsafeNativeMethods.TASKDIALOGCONFIG ( );

            try
            {
                config.cbSize = (uint)Marshal.SizeOf ( typeof ( UnsafeNativeMethods.TASKDIALOGCONFIG ) );
                config.hwndParent = hwndOwner;
                config.dwFlags = this.flags;
                config.dwCommonButtons = this.commonButtons;

                if ( !string.IsNullOrEmpty ( this.windowTitle ) )
                {
                    config.pszWindowTitle = this.windowTitle;
                }

                config.MainIcon = (IntPtr)this.mainIcon;
                if ( this.customMainIcon != null )
                {
                    config.dwFlags |= UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_USE_HICON_MAIN;
                    config.MainIcon = this.customMainIcon.Handle;
                }

                if ( !string.IsNullOrEmpty ( this.mainInstruction ) )
                {
                    config.pszMainInstruction = this.mainInstruction;
                }

                if ( !string.IsNullOrEmpty ( this.content ) )
                {
                    config.pszContent = this.content;
                }

                TaskDialogButton[] customButtons = this.buttons;
                if ( customButtons.Length > 0 )
                {
                    // Hand marshal the buttons array.
                    int elementSize = Marshal.SizeOf ( typeof ( TaskDialogButton ) );
                    config.pButtons = Marshal.AllocHGlobal ( elementSize * (int)customButtons.Length );
                    for ( int i = 0; i < customButtons.Length; i++ )
                    {
                        unsafe // Unsafe because of pointer arithmatic.
                        {
                            byte* p = (byte*)config.pButtons;
                            Marshal.StructureToPtr ( customButtons[i], (IntPtr)( p + ( elementSize * i ) ), false );
                        }

                        config.cButtons++;
                    }
                }

                TaskDialogButton[] customRadioButtons = this.radioButtons;
                if ( customRadioButtons.Length > 0 )
                {
                    // Hand marshal the buttons array.
                    int elementSize = Marshal.SizeOf ( typeof ( TaskDialogButton ) );
                    config.pRadioButtons = Marshal.AllocHGlobal ( elementSize * (int)customRadioButtons.Length );
                    for ( int i = 0; i < customRadioButtons.Length; i++ )
                    {
                        unsafe // Unsafe because of pointer arithmatic.
                        {
                            byte* p = (byte*)config.pRadioButtons;
                            Marshal.StructureToPtr ( customRadioButtons[i], (IntPtr)( p + ( elementSize * i ) ), false );
                        }

                        config.cRadioButtons++;
                    }
                }

                config.nDefaultButton = this.defaultButton;
                config.nDefaultRadioButton = this.defaultRadioButton;

                if ( !string.IsNullOrEmpty ( this.verificationText ) )
                {
                    config.pszVerificationText = this.verificationText;
                }

                if ( !string.IsNullOrEmpty ( this.expandedInformation ) )
                {
                    config.pszExpandedInformation = this.expandedInformation;
                }

                if ( !string.IsNullOrEmpty ( this.expandedControlText ) )
                {
                    config.pszExpandedControlText = this.expandedControlText;
                }

                if ( !string.IsNullOrEmpty ( this.collapsedControlText ) )
                {
                    config.pszCollapsedControlText = this.CollapsedControlText;
                }

                config.FooterIcon = (IntPtr)this.footerIcon;
                if ( this.customFooterIcon != null )
                {
                    config.dwFlags |= UnsafeNativeMethods.TASKDIALOG_FLAGS.TDF_USE_HICON_FOOTER;
                    config.FooterIcon = this.customFooterIcon.Handle;
                }

                if ( !string.IsNullOrEmpty ( this.footer ) )
                {
                    config.pszFooter = this.footer;
                }

                // If our user has asked for a callback then we need to ask for one to
                // translate to the friendly version.
                if ( this.callback != null )
                {
                    config.pfCallback = new UnsafeNativeMethods.TaskDialogCallback ( this.PrivateCallback );
                }

                ////config.lpCallbackData = this.callbackData; // How do you do this? Need to pin the ref?
                config.cxWidth = this.width;

                // The call all this mucking about is here for.
                UnsafeNativeMethods.TaskDialogIndirect ( ref config, out result, out radioButtonResult, out verificationFlagChecked );
            }
            finally
            {
                // Free the unmanged memory needed for the button arrays.
                // There is the possiblity of leaking memory if the app-domain is destroyed in a non clean way
                // and the hosting OS process is kept alive but fixing this would require using hardening techniques
                // that are not required for the users of this class.
                if ( config.pButtons != IntPtr.Zero )
                {
                    int elementSize = Marshal.SizeOf ( typeof ( TaskDialogButton ) );
                    for ( int i = 0; i < config.cButtons; i++ )
                    {
                        unsafe
                        {
                            byte* p = (byte*)config.pButtons;
                            Marshal.DestroyStructure ( (IntPtr)( p + ( elementSize * i ) ), typeof ( TaskDialogButton ) );
                        }
                    }

                    Marshal.FreeHGlobal ( config.pButtons );
                }

                if ( config.pRadioButtons != IntPtr.Zero )
                {
                    int elementSize = Marshal.SizeOf ( typeof ( TaskDialogButton ) );
                    for ( int i = 0; i < config.cRadioButtons; i++ )
                    {
                        unsafe
                        {
                            byte* p = (byte*)config.pRadioButtons;
                            Marshal.DestroyStructure ( (IntPtr)( p + ( elementSize * i ) ), typeof ( TaskDialogButton ) );
                        }
                    }

                    Marshal.FreeHGlobal ( config.pRadioButtons );
                }
            }

            return result;
        }

        /// <summary>
        /// The callback from the native Task Dialog. This prepares the friendlier arguments and calls the simplier 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 noitification information.  The contents of this parameter depends on the value of the msg parameter.</param>
        /// <param name="lparam">Specifies additional noitification 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 int PrivateCallback ( [In] IntPtr hwnd, [In] uint msg, [In] UIntPtr wparam, [In] IntPtr lparam, [In] IntPtr refData )
        {
            TaskDialogCallback callback = this.callback;
            if ( callback != null )
            {
                // Prepare arguments for the callback to the user we are insulating from Interop casting sillyness.

                // Future: Consider reusing a single ActiveTaskDialog object and mark it as destroyed on the destry notification.
                ActiveTaskDialog activeDialog = new ActiveTaskDialog ( hwnd );
                TaskDialogNotificationArgs args = new TaskDialogNotificationArgs ( );
                args.Notification = (TaskDialogNotification)msg;
                switch ( args.Notification )
                {
                    case TaskDialogNotification.ButtonClicked:
                    case TaskDialogNotification.RadioButtonClicked:
                        args.ButtonId = (int)wparam;
                        break;
                    case TaskDialogNotification.HyperlinkClicked:
                        args.Hyperlink = Marshal.PtrToStringUni ( lparam );
                        break;
                    case TaskDialogNotification.Timer:
                        args.TimerTickCount = (uint)wparam;
                        break;
                    case TaskDialogNotification.VerificationClicked:
                        args.VerificationFlagChecked = ( wparam != UIntPtr.Zero );
                        break;
                    case TaskDialogNotification.ExpandoButtonClicked:
                        args.Expanded = ( wparam != UIntPtr.Zero );
                        break;
                }

                return ( callback ( activeDialog, args, this.callbackData ) ? 1 : 0 );
            }

            return 0; // false;
        }

        /// <summary>
        /// Helper function to set or clear a bit in the flags field.
        /// </summary>
        /// <param name="flag">The Flag bit to set or clear.</param>
        /// <param name="value">True to set, false to clear the bit in the flags field.</param>
        private void SetFlag ( UnsafeNativeMethods.TASKDIALOG_FLAGS flag, bool value )
        {
            if ( value )
            {
                this.flags |= flag;
            }
            else
            {
                this.flags &= ~flag;
            }
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions