Click here to Skip to main content
15,886,689 members
Articles / Programming Languages / C#

Creating a Timer Using the Amazing New Windows 7 Features

Rate me:
Please Sign up or sign in to vote.
4.89/5 (99 votes)
4 Jan 2010CPOL7 min read 153.1K   6K   166  
This article shows how to create a simple egg timer that uses features new to Windows 7.
//Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Permissions;
using MS.WindowsAPICodePack.Internal;

namespace Microsoft.WindowsAPICodePack.Dialogs
{
    /// <summary>
    /// Encapsulates a new-to-Vista Win32 TaskDialog window 
    /// - a powerful successor to the MessageBox available
    /// in previous versions of Windows.
    /// </summary>
    [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public class TaskDialog : IDialogControlHost, IDisposable
    {
        // Global instance of TaskDialog, to be used by static Show() method.
        // As most parameters of a dialog created via static Show() will have
        // identical parameters, we'll create one TaskDialog and treat it
        // as a NativeTaskDialog generator for all static Show() calls.
        private static TaskDialog staticDialog;

        // Main current native dialog.
        private NativeTaskDialog nativeDialog;

        private List<TaskDialogButtonBase> buttons;
        private List<TaskDialogButtonBase> radioButtons;
        private List<TaskDialogButtonBase> commandLinks;
        private IntPtr ownerWindow;

        #region Public Properties
        /// <summary>
        /// Occurs when a progress bar changes.
        /// </summary>
        public event EventHandler<TaskDialogTickEventArgs> Tick;

        /// <summary>
        /// Occurs when a user clicks a hyperlink.
        /// </summary>
        public event EventHandler<TaskDialogHyperlinkClickedEventArgs> HyperlinkClick;

        /// <summary>
        /// Occurs when the TaskDialog is closing.
        /// </summary>
        public event EventHandler<TaskDialogClosingEventArgs> Closing;

        /// <summary>
        /// Occurs when a user clicks on Help.
        /// </summary>
        public event EventHandler HelpInvoked;

        /// <summary>
        /// Occurs when the TaskDialog is opened.
        /// </summary>
        public event EventHandler Opened;

        /// <summary>
        /// Gets or sets a value that contains the owner window's handle.
        /// </summary>
        public IntPtr OwnerWindowHandle
        {
            get { return ownerWindow; }
            set
            {
                ThrowIfDialogShowing("Dialog owner cannot be modified while dialog is showing.");
                ownerWindow = value;
            }
        }

        // Main content (maps to MessageBox's "message"). 
        private string text;
        /// <summary>
        /// Gets or sets a value that contains the message text.
        /// </summary>
        public string Text
        {
            get { return text; }
            set
            {
                // Set local value, then update native dialog if showing.
                text = value;
                if (NativeDialogShowing)
                    nativeDialog.UpdateText(text);
            }
        }

        private string instructionText;
        /// <summary>
        /// Gets or sets a value that contains the instruction text.
        /// </summary>
        public string InstructionText
        {
            get { return instructionText; }
            set
            {
                // Set local value, then update native dialog if showing.
                instructionText = value;
                if (NativeDialogShowing)
                    nativeDialog.UpdateInstruction(instructionText);
            }
        }

        private string caption;
        /// <summary>
        /// Gets or sets a value that contains the caption text.
        /// </summary>
        public string Caption
        {
            get { return caption; }
            set
            {
                ThrowIfDialogShowing("Dialog caption can't be set while dialog is showing.");
                caption = value;
            }
        }

        private string footerText;
        /// <summary>
        /// Gets or sets a value that contains the footer text.
        /// </summary>
        public string FooterText
        {
            get { return footerText; }
            set
            {
                // Set local value, then update native dialog if showing.
                footerText = value;
                if (NativeDialogShowing)
                    nativeDialog.UpdateFooterText(footerText);
            }
        }

        private string checkBoxText;
        /// <summary>
        /// Gets or sets a value that contains the footer check box text.
        /// </summary>
        public string FooterCheckBoxText
        {
            get { return checkBoxText; }
            set
            {
                ThrowIfDialogShowing("Checkbox text can't be set while dialog is showing.");
                checkBoxText = value;
            }
        }

        private string detailsExpandedText;
        /// <summary>
        /// Gets or sets a value that contains the expanded text in the details section.
        /// </summary>
        public string DetailsExpandedText
        {
            get { return detailsExpandedText; }
            set
            {
                // Set local value, then update native dialog if showing.
                detailsExpandedText = value;
                if (NativeDialogShowing)
                    nativeDialog.UpdateExpandedText(detailsExpandedText);
            }
        }

        private bool detailsExpanded;
        /// <summary>
        /// Gets or sets a value that determines if the details section is expanded.
        /// </summary>
        public bool DetailsExpanded
        {
            get { return detailsExpanded; }
            set
            {
                ThrowIfDialogShowing("Expanded state of the dialog can't be modified while dialog is showing.");
                detailsExpanded = value;
            }
        }

        private string detailsExpandedLabel;
        /// <summary>
        /// Gets or sets a value that contains the expanded control text.
        /// </summary>
        public string DetailsExpandedLabel
        {
            get { return detailsExpandedLabel; }
            set
            {
                ThrowIfDialogShowing("Expanded control label can't be set while dialog is showing.");
                detailsExpandedLabel = value;
            }
        }

        private string detailsCollapsedLabel;
        /// <summary>
        /// Gets or sets a value that contains the collapsed control text.
        /// </summary>
        public string DetailsCollapsedLabel
        {
            get { return detailsCollapsedLabel; }
            set
            {
                ThrowIfDialogShowing("Collapsed control text can't be set while dialog is showing.");
                detailsCollapsedLabel = value;
            }
        }

        private bool cancelable;
        /// <summary>
        /// Gets or sets a value that determines if Cancelable is set.
        /// </summary>
        public bool Cancelable
        {
            get { return cancelable; }
            set
            {
                ThrowIfDialogShowing("Cancelable can't be set while dialog is showing.");
                cancelable = value;
            }
        }

        private TaskDialogStandardIcon icon;
        /// <summary>
        /// Gets or sets a value that contains the TaskDialog main icon.
        /// </summary>
        public TaskDialogStandardIcon Icon
        {
            get { return icon; }
            set
            {
                // Set local value, then update native dialog if showing.
                icon = value;
                if (NativeDialogShowing)
                    nativeDialog.UpdateMainIcon(icon);
            }
        }

        private TaskDialogStandardIcon footerIcon;
        /// <summary>
        /// Gets or sets a value that contains the footer icon.
        /// </summary>
        public TaskDialogStandardIcon FooterIcon
        {
            get { return footerIcon; }
            set
            {
                // Set local value, then update native dialog if showing.
                footerIcon = value;
                if (NativeDialogShowing)
                    nativeDialog.UpdateFooterIcon(footerIcon);
            }
        }

        private TaskDialogStandardButtons standardButtons = TaskDialogStandardButtons.None;
        /// <summary>
        /// Gets or sets a value that contains the standard buttons.
        /// </summary>
        public TaskDialogStandardButtons StandardButtons
        {
            get { return standardButtons; }
            set
            {
                ThrowIfDialogShowing("Standard buttons can't be set while dialog is showing.");
                standardButtons = value;
            }
        }

        private DialogControlCollection<TaskDialogControl> controls;
        /// <summary>
        /// Gets a value that contains the TaskDialog controls.
        /// </summary>
        public DialogControlCollection<TaskDialogControl> Controls
        {
            // "Show protection" provided by collection itself, 
            // as well as individual controls.
            get { return controls; }
        }

        private bool hyperlinksEnabled;
        /// <summary>
        /// Gets or sets a value that determines if hyperlinks are enabled.
        /// </summary>
        public bool HyperlinksEnabled
        {
            get { return hyperlinksEnabled; }
            set
            {
                ThrowIfDialogShowing("Hyperlinks can't be enabled/disabled while dialog is showing.");
                hyperlinksEnabled = value;
            }
        }

        private bool? footerCheckBoxChecked = null;
        /// <summary>
        /// Gets or sets a value that indicates if the footer checkbox is checked.
        /// </summary>
        public bool? FooterCheckBoxChecked
        {
            get
            {
                if (!footerCheckBoxChecked.HasValue)
                    return false;
                else
                    return footerCheckBoxChecked;
            }
            set
            {
                // Set local value, then update native dialog if showing.
                footerCheckBoxChecked = value;
                if (NativeDialogShowing)
                    nativeDialog.UpdateCheckBoxChecked(footerCheckBoxChecked.Value);
            }
        }

        private TaskDialogExpandedDetailsLocation expansionMode;
        /// <summary>
        /// Gets or sets a value that contains the expansion mode for this dialog.
        /// </summary>
        public TaskDialogExpandedDetailsLocation ExpansionMode
        {
            get { return expansionMode; }
            set
            {
                ThrowIfDialogShowing("Expanded information mode can't be set while dialog is showing.");
                expansionMode = value;
            }
        }

        private TaskDialogStartupLocation startupLocation;

        /// <summary>
        /// Gets or sets a value that contains the startup location.
        /// </summary>
        public TaskDialogStartupLocation StartupLocation
        {
            get { return startupLocation; }
            set
            {
                ThrowIfDialogShowing("Startup location can't be changed while dialog is showing.");
                startupLocation = value;
            }
        }

        private TaskDialogProgressBar progressBar;
        /// <summary>
        /// Gets or sets the progress bar on the taskdialog. ProgressBar a visual representation 
        /// of the progress of a long running operation.
        /// </summary>
        public TaskDialogProgressBar ProgressBar
        {
            get { return progressBar; }
            set
            {
                ThrowIfDialogShowing("Progress bar can't be changed while dialog is showing");
                if (value != null)
                {
                    if (value.HostingDialog != null)
                        throw new InvalidOperationException("Progress bar cannot be hosted in multiple dialogs.");

                    value.HostingDialog = this;
                }
                progressBar = value;
            }
        }

        #endregion

        #region Constructors

        // Constructors.


        /// <summary>
        /// Creates a basic TaskDialog window 
        /// </summary>
        public TaskDialog()
        {
            // Throw PlatformNotSupportedException if the user is not running Vista or beyond
            CoreHelpers.ThrowIfNotVista();

            // Initialize various data structs.
            controls = new DialogControlCollection<TaskDialogControl>(this);
            buttons = new List<TaskDialogButtonBase>();
            radioButtons = new List<TaskDialogButtonBase>();
            commandLinks = new List<TaskDialogButtonBase>();
        }



        #endregion

        #region Static Show Methods

        /// <summary>
        /// Creates and shows a task dialog with the specified message text.
        /// </summary>
        /// <param name="text">The text to display.</param>
        /// <returns>The dialog result.</returns>
        public static TaskDialogResult Show(string text)
        {
            return ShowCoreStatic(
                text,
                TaskDialogDefaults.MainInstruction,
                TaskDialogDefaults.Caption);
        }

        /// <summary>
        /// Creates and shows a task dialog with the specified supporting text and main instruction.
        /// </summary>
        /// <param name="text">The supporting text to display.</param>
        /// <param name="instructionText">The main instruction text to display.</param>
        /// <returns>The dialog result.</returns>
        public static TaskDialogResult Show(string text, string instructionText)
        {
            return ShowCoreStatic(
                text, instructionText,
                TaskDialogDefaults.Caption);
        }

        /// <summary>
        /// Creates and shows a task dialog with the specified supporting text, main instruction, and dialog caption.
        /// </summary>
        /// <param name="text">The supporting text to display.</param>
        /// <param name="instructionText">The main instruction text to display.</param>
        /// <param name="caption">The caption for the dialog.</param>
        /// <returns>The dialog result.</returns>
        public static TaskDialogResult Show(string text, string instructionText, string caption)
        {
            return ShowCoreStatic(text, instructionText, caption);
        }
        #endregion

        #region Instance Show Methods

        /// <summary>
        /// Creates and shows a task dialog.
        /// </summary>
        /// <returns>The dialog result.</returns>
        public TaskDialogResult Show()
        {
            return ShowCore();
        }
        #endregion

        #region Core Show Logic

        // CORE SHOW METHODS:
        // All static Show() calls forward here - 
        // it is responsible for retrieving
        // or creating our cached TaskDialog instance, getting it configured,
        // and in turn calling the appropriate instance Show.

        private static TaskDialogResult ShowCoreStatic(
            string text,
            string instructionText,
            string caption)
        {
            // Throw PlatformNotSupportedException if the user is not running Vista or beyond
            CoreHelpers.ThrowIfNotVista();

            // If no instance cached yet, create it.
            if (staticDialog == null)
            {
                // New TaskDialog will automatically pick up defaults when 
                // a new config structure is created as part of ShowCore().
                staticDialog = new TaskDialog();
            }

            // Set the few relevant properties, 
            // and go with the defaults for the others.
            staticDialog.text = text;
            staticDialog.instructionText = instructionText;
            staticDialog.caption = caption;

            return staticDialog.Show();
        }

        private TaskDialogResult ShowCore()
        {
            TaskDialogResult result;

            try
            {
                // Populate control lists, based on current 
                // contents - note we are somewhat late-bound 
                // on our control lists, to support XAML scenarios.
                SortDialogControls();

                // First, let's make sure it even makes 
                // sense to try a show.
                ValidateCurrentDialogSettings();

                // Create settings object for new dialog, 
                // based on current state.
                NativeTaskDialogSettings settings =
                    new NativeTaskDialogSettings();
                ApplyCoreSettings(settings);
                ApplySupplementalSettings(settings);

                // Show the dialog.
                // NOTE: this is a BLOCKING call; the dialog proc callbacks
                // will be executed by the same thread as the 
                // Show() call before the thread of execution 
                // contines to the end of this method.
                nativeDialog = new NativeTaskDialog(settings, this);
                nativeDialog.NativeShow();

                // Build and return dialog result to public API - leaving it
                // null after an exception is thrown is fine in this case
                result = ConstructDialogResult(nativeDialog);
                footerCheckBoxChecked = nativeDialog.CheckBoxChecked;
            }
            finally
            {
                CleanUp();
                nativeDialog = null;
            }

            return result;
        }

        // Helper that looks at the current state of the TaskDialog and verifies
        // that there aren't any abberant combinations of properties.
        // NOTE that this method is designed to throw 
        // rather than return a bool.
        private void ValidateCurrentDialogSettings()
        {
            if (footerCheckBoxChecked.HasValue &&
                footerCheckBoxChecked.Value == true &&
                String.IsNullOrEmpty(checkBoxText))
                throw new InvalidOperationException(
                    "Checkbox text must be provided to enable the dialog checkbox.");

            // Progress bar validation.
            // Make sure the progress bar values are valid.
            // the Win32 API will valiantly try to rationalize 
            // bizarre min/max/value combinations, but we'll save
            // it the trouble by validating.
            if (progressBar != null)
                if (!progressBar.HasValidValues)
                    throw new ArgumentException(
                        "Progress bar must have a value between the minimum and maxium values.");

            // Validate Buttons collection.
            // Make sure we don't have buttons AND 
            // command-links - the Win32 API treats them as different
            // flavors of a single button struct.
            if (buttons.Count > 0 && commandLinks.Count > 0)
                throw new NotSupportedException(
                    "Dialog cannot display both non-standard buttons and command links.");
            if (buttons.Count > 0 && standardButtons != TaskDialogStandardButtons.None)
                throw new NotSupportedException(
                    "Dialog cannot display both non-standard buttons and standard buttons.");
        }

        // Analyzes the final state of the NativeTaskDialog instance and creates the 
        // final TaskDialogResult that will be returned from the public API
        private TaskDialogResult ConstructDialogResult(NativeTaskDialog native)
        {
            Debug.Assert(native.ShowState == DialogShowState.Closed, "dialog result being constructed for unshown dialog.");

            TaskDialogResult result = TaskDialogResult.Cancel;

            TaskDialogStandardButtons standardButton = MapButtonIdToStandardButton(native.SelectedButtonID);

            // If returned ID isn't a standard button, let's fetch 
            if (standardButton == TaskDialogStandardButtons.None)
                result = TaskDialogResult.CustomButtonClicked;
            else
                result = (TaskDialogResult)standardButton;

            return result;
        }

        /// <summary>
        /// Close TaskDialog
        /// </summary>
        /// <exception cref="InvalidOperationException">if TaskDialog is not showing.</exception>
        public void Close()
        {
            if (!NativeDialogShowing)
                throw new InvalidOperationException(
                    "Attempting to close a non-showing dialog.");

            nativeDialog.NativeClose(TaskDialogResult.Cancel);
            // TaskDialog's own cleanup code - 
            // which runs post show - will handle disposal of native dialog.
        }

        /// <summary>
        /// Close TaskDialog with a given TaskDialogResult
        /// </summary>
        /// <param name="closingResult">TaskDialogResult to return from the TaskDialog.Show() method</param>
        /// <exception cref="InvalidOperationException">if TaskDialog is not showing.</exception>
        public void Close(TaskDialogResult closingResult)
        {
            if (!NativeDialogShowing)
                throw new InvalidOperationException(
                    "Attempting to close a non-showing dialog.");

            nativeDialog.NativeClose(closingResult);
            // TaskDialog's own cleanup code - 
            // which runs post show - will handle disposal of native dialog.
        }

        #endregion

        #region Configuration Construction

        private void ApplyCoreSettings(NativeTaskDialogSettings settings)
        {
            ApplyGeneralNativeConfiguration(settings.NativeConfiguration);
            ApplyTextConfiguration(settings.NativeConfiguration);
            ApplyOptionConfiguration(settings.NativeConfiguration);
            ApplyControlConfiguration(settings);
        }

        private void ApplyGeneralNativeConfiguration(TaskDialogNativeMethods.TASKDIALOGCONFIG dialogConfig)
        {
            // If an owner wasn't specifically specified, 
            // we'll use the app's main window.
            if (ownerWindow != IntPtr.Zero)
                dialogConfig.hwndParent = ownerWindow;

            // Other miscellaneous sets.
            dialogConfig.MainIcon =
                new TaskDialogNativeMethods.TASKDIALOGCONFIG_ICON_UNION((int)icon);
            dialogConfig.FooterIcon =
                new TaskDialogNativeMethods.TASKDIALOGCONFIG_ICON_UNION((int)footerIcon);
            dialogConfig.dwCommonButtons =
                (TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_FLAGS)standardButtons;
        }

        /// <summary>
        /// Sets important text properties.
        /// </summary>
        /// <param name="dialogConfig">An instance of a <see cref="TaskDialogNativeMethods.TASKDIALOGCONFIG"/> object.</param>
        private void ApplyTextConfiguration(TaskDialogNativeMethods.TASKDIALOGCONFIG dialogConfig)
        {
            // note that nulls or empty strings are fine here.
            dialogConfig.pszContent = text;
            dialogConfig.pszWindowTitle = caption;
            dialogConfig.pszMainInstruction = instructionText;
            dialogConfig.pszExpandedInformation = detailsExpandedText;
            dialogConfig.pszExpandedControlText = detailsExpandedLabel;
            dialogConfig.pszCollapsedControlText = detailsCollapsedLabel;
            dialogConfig.pszFooter = footerText;
            dialogConfig.pszVerificationText = checkBoxText;
        }

        private void ApplyOptionConfiguration(TaskDialogNativeMethods.TASKDIALOGCONFIG dialogConfig)
        {
            // Handle options - start with no options set.
            TaskDialogNativeMethods.TASKDIALOG_FLAGS options = TaskDialogNativeMethods.TASKDIALOG_FLAGS.NONE;
            if (cancelable)
                options |= TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_ALLOW_DIALOG_CANCELLATION;
            if (footerCheckBoxChecked.HasValue && footerCheckBoxChecked.Value)
                options |= TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_VERIFICATION_FLAG_CHECKED;
            if (hyperlinksEnabled)
                options |= TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_ENABLE_HYPERLINKS;
            if (detailsExpanded)
                options |= TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_EXPANDED_BY_DEFAULT;
            if (Tick != null)
                options |= TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_CALLBACK_TIMER;
            if (startupLocation == TaskDialogStartupLocation.CenterOwner)
                options |= TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_POSITION_RELATIVE_TO_WINDOW;

            // Note: no validation required, as we allow this to 
            // be set even if there is no expanded information 
            // text because that could be added later.
            // Default for Win32 API is to expand into (and after) 
            // the content area.
            if (expansionMode == TaskDialogExpandedDetailsLocation.ExpandFooter)
                options |= TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_EXPAND_FOOTER_AREA;

            // Finally, apply options to config.
            dialogConfig.dwFlags = options;
        }

        // Builds the actual configuration 
        // that the NativeTaskDialog (and underlying Win32 API)
        // expects, by parsing the various control 
        // lists, marshalling to the unmanaged heap, etc.

        private void ApplyControlConfiguration(NativeTaskDialogSettings settings)
        {
            // Deal with progress bars/marquees.
            if (progressBar != null)
            {
                if (progressBar.State == TaskDialogProgressBarState.Marquee)
                    settings.NativeConfiguration.dwFlags |= TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_SHOW_MARQUEE_PROGRESS_BAR;
                else
                    settings.NativeConfiguration.dwFlags |= TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_SHOW_PROGRESS_BAR;
            }

            // Build the native struct arrays that NativeTaskDialog 
            // needs - though NTD will handle
            // the heavy lifting marshalling to make sure 
            // all the cleanup is centralized there.
            if (buttons.Count > 0 || commandLinks.Count > 0)
            {
                // These are the actual arrays/lists of 
                // the structs that we'll copy to the 
                // unmanaged heap.
                List<TaskDialogButtonBase> sourceList = (
                    buttons.Count > 0 ? buttons : commandLinks);
                settings.Buttons = BuildButtonStructArray(sourceList);

                // Apply option flag that forces all 
                // custom buttons to render as command links.
                if (commandLinks.Count > 0)
                    settings.NativeConfiguration.dwFlags |=
                      TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_USE_COMMAND_LINKS;

                // Set default button and add elevation icons 
                // to appropriate buttons.
                settings.NativeConfiguration.nDefaultButton =
                    FindDefaultButtonId(sourceList);

                ApplyElevatedIcons(settings, sourceList);
            }
            if (radioButtons.Count > 0)
            {
                settings.RadioButtons = BuildButtonStructArray(radioButtons);

                // Set default radio button - radio buttons don't support.
                int defaultRadioButton = FindDefaultButtonId(radioButtons);
                settings.NativeConfiguration.nDefaultRadioButton =
                    defaultRadioButton;

                if (defaultRadioButton ==
                    TaskDialogNativeMethods.NO_DEFAULT_BUTTON_SPECIFIED)
                    settings.NativeConfiguration.dwFlags |= TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_NO_DEFAULT_RADIO_BUTTON;
            }
        }

        private static TaskDialogNativeMethods.TASKDIALOG_BUTTON[] BuildButtonStructArray(List<TaskDialogButtonBase> controls)
        {
            TaskDialogNativeMethods.TASKDIALOG_BUTTON[] buttonStructs;
            TaskDialogButtonBase button;

            int totalButtons = controls.Count;
            buttonStructs = new TaskDialogNativeMethods.TASKDIALOG_BUTTON[totalButtons];
            for (int i = 0; i < totalButtons; i++)
            {
                button = controls[i];
                buttonStructs[i] = new TaskDialogNativeMethods.TASKDIALOG_BUTTON(button.Id, button.ToString());
            }
            return buttonStructs;
        }

        // Searches list of controls and returns the ID of 
        // the default control, or null if no default was specified.
        private static int FindDefaultButtonId(List<TaskDialogButtonBase> controls)
        {
            int found = TaskDialogNativeMethods.NO_DEFAULT_BUTTON_SPECIFIED;
            foreach (TaskDialogButtonBase control in controls)
            {
                if (control.Default)
                {
                    // Check if we've found a default in this list already.
                    if (found != TaskDialogNativeMethods.NO_DEFAULT_BUTTON_SPECIFIED)
                        throw new InvalidOperationException("Can't have more than one default button of a given type.");
                    return control.Id;
                }
            }
            return found;
        }

        private static void ApplyElevatedIcons(NativeTaskDialogSettings settings, List<TaskDialogButtonBase> controls)
        {
            foreach (TaskDialogButton control in controls)
            {
                if (control.ShowElevationIcon)
                {
                    if (settings.ElevatedButtons == null)
                        settings.ElevatedButtons = new List<int>();
                    settings.ElevatedButtons.Add(control.Id);
                }
            }
        }

        private void ApplySupplementalSettings(NativeTaskDialogSettings settings)
        {
            if (progressBar != null)
            {
                if (progressBar.State != TaskDialogProgressBarState.Marquee)
                {
                    settings.ProgressBarMinimum = progressBar.Minimum;
                    settings.ProgressBarMaximum = progressBar.Maximum;
                    settings.ProgressBarValue = progressBar.Value;
                    settings.ProgressBarState = progressBar.State;
                }
            }

            if (HelpInvoked != null)
                settings.InvokeHelp = true;
        }

        // Here we walk our controls collection and 
        // sort the various controls by type. 
        private void SortDialogControls()
        {
            foreach (TaskDialogControl control in controls)
            {
                if (control is TaskDialogButtonBase && String.IsNullOrEmpty(((TaskDialogButtonBase)control).Text))
                {
                    if (control is TaskDialogCommandLink && String.IsNullOrEmpty(((TaskDialogCommandLink)control).Instruction))
                        throw new InvalidOperationException(
                            "Button text must be non-empty");
                }

                // Loop through child controls 
                // and sort the controls based on type.
                if (control is TaskDialogCommandLink)
                {
                    commandLinks.Add((TaskDialogCommandLink)control);
                }
                else if (control is TaskDialogRadioButton)
                {
                    if (radioButtons == null)
                        radioButtons = new List<TaskDialogButtonBase>();
                    radioButtons.Add((TaskDialogRadioButton)control);
                }
                else if (control is TaskDialogButtonBase)
                {
                    if (buttons == null)
                        buttons = new List<TaskDialogButtonBase>();
                    buttons.Add((TaskDialogButtonBase)control);
                }
                else if (control is TaskDialogProgressBar)
                {
                    progressBar = (TaskDialogProgressBar)control;
                }
                else
                {
                    throw new ArgumentException("Unknown dialog control type.");
                }
            }
        }

        #endregion

        #region Helpers

        // Helper to map the standard button IDs returned by 
        // TaskDialogIndirect to the standard button ID enum - 
        // note that we can't just cast, as the Win32
        // typedefs differ incoming and outgoing.

        private static TaskDialogStandardButtons MapButtonIdToStandardButton(int id)
        {
            switch ((TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID)id)
            {
                case TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDOK:
                    return TaskDialogStandardButtons.Ok;
                case TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDCANCEL:
                    return TaskDialogStandardButtons.Cancel;
                case TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDABORT:
                    // Included for completeness in API - 
                    // we can't pass in an Abort standard button.
                    return TaskDialogStandardButtons.None;
                case TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDRETRY:
                    return TaskDialogStandardButtons.Retry;
                case TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDIGNORE:
                    // Included for completeness in API - 
                    // we can't pass in an Ignore standard button.
                    return TaskDialogStandardButtons.None;
                case TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDYES:
                    return TaskDialogStandardButtons.Yes;
                case TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDNO:
                    return TaskDialogStandardButtons.No;
                case TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDCLOSE:
                    return TaskDialogStandardButtons.Close;
                default:
                    return TaskDialogStandardButtons.None;
            }
        }

        private void ThrowIfDialogShowing(string message)
        {
            if (NativeDialogShowing)
                throw new NotSupportedException(message);
        }

        private bool NativeDialogShowing
        {
            get
            {
                return (nativeDialog != null)
                    && (nativeDialog.ShowState == DialogShowState.Showing ||
                    nativeDialog.ShowState == DialogShowState.Closing);
            }
        }

        // NOTE: we are going to require names be unique 
        // across both buttons and radio buttons,
        // even though the Win32 API allows them to be separate.
        private TaskDialogButtonBase GetButtonForId(int id)
        {
            return (TaskDialogButtonBase)controls.GetControlbyId(id);
        }

        #endregion

        #region IDialogControlHost Members

        // We're explicitly implementing this interface 
        // as the user will never need to know about it
        // or use it directly - it is only for the internal 
        // implementation of "pseudo controls" within 
        // the dialogs.

        // Called whenever controls are being added 
        // to or removed from the dialog control collection.
        bool IDialogControlHost.IsCollectionChangeAllowed()
        {
            // Only allow additions to collection if dialog is NOT showing.
            return !NativeDialogShowing;
        }

        // Called whenever controls have been added or removed.
        void IDialogControlHost.ApplyCollectionChanged()
        {
            // If we're showing, we should never get here - 
            // the changing notification would have thrown and the 
            // property would not have been changed.
            Debug.Assert(!NativeDialogShowing,
                "Collection changed notification received despite show state of dialog");
        }

        // Called when a control currently in the collection 
        // has a property changing - this is 
        // basically to screen out property changes that 
        // cannot occur while the dialog is showing
        // because the Win32 API has no way for us to 
        // propagate the changes until we re-invoke the Win32 call.
        bool IDialogControlHost.IsControlPropertyChangeAllowed(string propertyName, DialogControl control)
        {
            Debug.Assert(control is TaskDialogControl,
                "Property changing for a control that is not a TaskDialogControl-derived type");
            Debug.Assert(propertyName != "Name",
                "Name changes at any time are not supported - public API should have blocked this");

            bool canChange = false;

            if (!NativeDialogShowing)
            {
                // Certain properties can't be changed if the dialog is not showing
                // we need a handle created before we can set these...
                switch(propertyName)
                {
                    case "Enabled":
                        canChange = false;
                        break;
                    default:
                        canChange = true;
                        break;
                }
            }
            else
            {
                // If the dialog is showing, we can only 
                // allow some properties to change.
                switch (propertyName)
                {
                    // Properties that CAN'T be changed while dialog is showing.
                    case "Text":
                    case "Default":
                        canChange = false;
                        break;

                    // Properties that CAN be changed while dialog is showing.
                    case "ShowElevationIcon":
                    case "Enabled":
                        canChange = true;
                        break;
                    default:
                        Debug.Assert(true, "Unknown property name coming through property changing handler");
                        break;
                }
            }
            return canChange;
        }

        // Called when a control currently in the collection 
        // has a property changed - this handles propagating
        // the new property values to the Win32 API. 
        // If there isn't a way to change the Win32 value, then we
        // should have already screened out the property set 
        // in NotifyControlPropertyChanging.
        void IDialogControlHost.ApplyControlPropertyChange(string propertyName, DialogControl control)
        {
            // We only need to apply changes to the 
            // native dialog when it actually exists.
            if (NativeDialogShowing)
            {
                if (control is TaskDialogProgressBar)
                {
                    if (!progressBar.HasValidValues)
                        throw new ArgumentException(
                            "Progress bar must have a value between Minimum and Maximum.");
                    switch (propertyName)
                    {
                        case "State":
                            nativeDialog.UpdateProgressBarState(progressBar.State);
                            break;
                        case "Value":
                            nativeDialog.UpdateProgressBarValue(progressBar.Value);
                            break;
                        case "Minimum":
                        case "Maximum":
                            nativeDialog.UpdateProgressBarRange();
                            break;
                        default:
                            Debug.Assert(true, "Unknown property being set");
                            break;
                    }
                }
                else if (control is TaskDialogButton)
                {
                    TaskDialogButton button = (TaskDialogButton)control;
                    switch (propertyName)
                    {
                        case "ShowElevationIcon":
                            nativeDialog.UpdateElevationIcon(button.Id, button.ShowElevationIcon);
                            break;
                        case "Enabled":
                            nativeDialog.UpdateButtonEnabled(button.Id, button.Enabled);
                            break;
                        default:
                            Debug.Assert(true, "Unknown property being set");
                            break;
                    }
                }
                else if (control is TaskDialogRadioButton)
                {
                    TaskDialogRadioButton button = (TaskDialogRadioButton)control;
                    switch (propertyName)
                    {
                        case "Enabled":
                            nativeDialog.UpdateRadioButtonEnabled(button.Id, button.Enabled);
                            break;
                        default:
                            Debug.Assert(true, "Unknown property being set");
                            break;
                    }
                }
                else
                {
                    // Do nothing with property change - 
                    // note that this shouldn't ever happen, we should have
                    // either thrown on the changing event, or we handle above.
                    Debug.Assert(true, "Control property changed notification not handled properly - being ignored");
                }
            }
            return;
        }

        #endregion

        #region Event Percolation Methods

        // All Raise*() methods are called by the 
        // NativeTaskDialog when various pseudo-controls
        // are triggered.
        internal void RaiseButtonClickEvent(int id)
        {
            // First check to see if the ID matches a custom button.
            TaskDialogButtonBase button = GetButtonForId(id);

            // If a custom button was found, 
            // raise the event - if not, it's a standard button, and
            // we don't support custom event handling for the standard buttons
            if (button != null)
                button.RaiseClickEvent();
        }

        internal void RaiseHyperlinkClickEvent(string link)
        {
            EventHandler<TaskDialogHyperlinkClickedEventArgs> handler = HyperlinkClick;
            if (handler != null)
            {
                handler(this, new TaskDialogHyperlinkClickedEventArgs(link));
            }
        }

        // Gives event subscriber a chance to prevent 
        // the dialog from closing, based on 
        // the current state of the app and the button 
        // used to commit. Note that we don't 
        // have full access at this stage to 
        // the full dialog state.
        internal int RaiseClosingEvent(int id)
        {
            EventHandler<TaskDialogClosingEventArgs> handler = Closing;
            if (handler != null)
            {

                TaskDialogButtonBase customButton = null;
                TaskDialogClosingEventArgs e = new TaskDialogClosingEventArgs();

                // Try to identify the button - is it a standard one?
                TaskDialogStandardButtons buttonClicked = MapButtonIdToStandardButton(id);

                // If not, it had better be a custom button...
                if (buttonClicked == TaskDialogStandardButtons.None)
                {
                    customButton = GetButtonForId(id);

                    // ... or we have a problem.
                    if (customButton == null)
                        throw new InvalidOperationException("Bad button ID in closing event.");

                    e.CustomButton = customButton.Name;

                    e.TaskDialogResult = TaskDialogResult.CustomButtonClicked;
                }
                else
                    e.TaskDialogResult = (TaskDialogResult)buttonClicked;

                // Raise the event and determine how to proceed.
                handler(this, e);
                if (e.Cancel)
                    return (int)HRESULT.S_FALSE;
            }

            // It's okay to let the dialog close.
            return (int)HRESULT.S_OK;
        }

        internal void RaiseHelpInvokedEvent()
        {
            EventHandler handler = HelpInvoked;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }

        internal void RaiseOpenedEvent()
        {
            EventHandler handler = Opened;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }

        internal void RaiseTickEvent(int ticks)
        {
            EventHandler<TaskDialogTickEventArgs> handler = Tick;
            if (handler != null)
                handler(this, new TaskDialogTickEventArgs(ticks));
        }

        #endregion

        #region Cleanup Code

        // Cleans up data and structs from a single 
        // native dialog Show() invocation.
        private void CleanUp()
        {
            // Reset values that would be considered 
            // 'volatile' in a given instance.
            if (progressBar != null)
            {
                progressBar.Reset();
            }

            // Clean out sorted control lists - 
            // though we don't of course clear the main controls collection,
            // so the controls are still around; we'll 
            // resort on next show, since the collection may have changed.
            if (buttons != null)
                buttons.Clear();
            if (commandLinks != null)
                commandLinks.Clear();
            if (radioButtons != null)
                radioButtons.Clear();
            progressBar = null;

            // Have the native dialog clean up the rest.
            if (nativeDialog != null)
                nativeDialog.Dispose();
        }


        // Dispose pattern - cleans up data and structs for 
        // a) any native dialog currently showing, and
        // b) anything else that the outer TaskDialog has.
        private bool disposed;

        /// <summary>
        /// Dispose TaskDialog Resources
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// TaskDialog Finalizer
        /// </summary>
        ~TaskDialog()
        {
            Dispose(false);
        }

        /// <summary>
        /// Dispose TaskDialog Resources
        /// </summary>
        /// <param name="disposing">If true, indicates that this is being called via Dispose rather than via the finalizer.</param>
        public virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                disposed = true;

                if (disposing)
                {
                    // Clean up managed resources.
                    if (nativeDialog != null && nativeDialog.ShowState == DialogShowState.Showing)
                    {
                        nativeDialog.NativeClose(TaskDialogResult.Cancel);
                    }
                    
                    buttons = null;
                    radioButtons = null;
                    commandLinks = null;
                }

                // Clean up unmanaged resources SECOND, NTD counts on 
                // being closed before being disposed.
                if (nativeDialog != null)
                {
                    nativeDialog.Dispose();
                    nativeDialog = null;
                }

                if (staticDialog != null)
                {
                    staticDialog.Dispose();
                    staticDialog = null;
                }


            }
        }

        #endregion

        /// <summary>
        /// Indicates whether this feature is supported on the current platform.
        /// </summary>
        public static bool IsPlatformSupported
        {
            get 
            { 
                // We need Windows Vista onwards ...
                return CoreHelpers.RunningOnVista;
            }
        }
    }
}

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 Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions