Click here to Skip to main content
15,895,812 members
Articles / Desktop Programming / Windows Forms

Clipz - A Friendly Introduction to the Windows 7 Taskbar Features

Rate me:
Please Sign up or sign in to vote.
4.91/5 (57 votes)
17 Dec 2009CPOL9 min read 72.3K   1.6K   122  
An overview of the Windows 7 taskbar features, and how to use then in your own applications.
//Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using MS.WindowsAPICodePack.Internal;

namespace Microsoft.WindowsAPICodePack.Dialogs
{
    /// <summary>
    /// Encapsulates the native logic required to create, 
    /// configure, and show a TaskDialog, 
    /// via the TaskDialogIndirect() Win32 function.
    /// </summary>
    /// <remarks>A new instance of this class should 
    /// be created for each messagebox show, as
    /// the HWNDs for TaskDialogs do not remain constant 
    /// across calls to TaskDialogIndirect.
    /// </remarks>
    internal class NativeTaskDialog : IDisposable
    {
        private TaskDialogNativeMethods.TASKDIALOGCONFIG nativeDialogConfig;
        private NativeTaskDialogSettings settings;
        private IntPtr hWndDialog;
        private TaskDialog outerDialog;

        private IntPtr[] updatedStrings = new IntPtr[Enum.GetNames(typeof(TaskDialogNativeMethods.TASKDIALOG_ELEMENTS)).Length];
        private IntPtr buttonArray, radioButtonArray;

        // Flag tracks whether our first radio 
        // button click event has come through.
        private bool firstRadioButtonClicked = true;

        #region Constructors

        // Configuration is applied at dialog creation time.
        internal NativeTaskDialog( 
            NativeTaskDialogSettings settings,
            TaskDialog outerDialog)
        {
            nativeDialogConfig = settings.NativeConfiguration;
            this.settings = settings;

            // Wireup dialog proc message loop for this instance.
            nativeDialogConfig.pfCallback = 
                new TaskDialogNativeMethods.PFTASKDIALOGCALLBACK(DialogProc);

            // Keep a reference to the outer shell, so we can notify.
            this.outerDialog = outerDialog;
        }

        #endregion

        #region Public Properties

        private DialogShowState showState = 
            DialogShowState.PreShow;

        public DialogShowState ShowState
        {
            get { return showState; }
        }

        private int selectedButtonID;
        internal int SelectedButtonID
        {
            get { return selectedButtonID; }
        }

        private int selectedRadioButtonID;
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        internal int SelectedRadioButtonID
        {
            get { return selectedRadioButtonID; }
        }

        private bool checkBoxChecked;
        internal bool CheckBoxChecked
        {
            get { return checkBoxChecked; }
        }

        #endregion

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object)",
            Justification = "We are not currently handling globalization or localization")]
        internal void NativeShow()
        {
            // Applies config struct and other settings, then
            // calls main Win32 function.
            if (settings == null)
                throw new InvalidOperationException(
                    "An error has occurred in dialog configuration.");

            // Do a last-minute parse of the various dialog control lists,  
            // and only allocate the memory at the last minute.

            MarshalDialogControlStructs();
            // Make the call and show the dialog.
            // NOTE: this call is BLOCKING, though the thread 
            // WILL re-enter via the DialogProc.
            try
            {
                showState = DialogShowState.Showing;

                // Here is the way we use "vanilla" P/Invoke to call 
                // TaskDialogIndirect().  
                HRESULT hresult = TaskDialogNativeMethods.TaskDialogIndirect(
                    nativeDialogConfig,
                    out selectedButtonID,
                    out selectedRadioButtonID,
                    out checkBoxChecked);

                if (CoreErrorHelper.Failed(hresult))
                {
                    string msg;
                    switch (hresult)
                    {
                        case HRESULT.E_INVALIDARG:
                            msg = "Invalid arguments to Win32 call.";
                            break;
                        case HRESULT.E_OUTOFMEMORY:
                            msg = "Dialog contents too complex.";
                            break;
                        default:
                            msg = String.Format(

                                "An unexpected internal error occurred in the Win32 call:{0:x}",
                                hresult);
                            break;
                    }
                    Exception e = Marshal.GetExceptionForHR((int)hresult);
                    throw new Win32Exception(msg, e);
                }
            }
            catch (EntryPointNotFoundException)
            {
                throw new NotSupportedException("TaskDialog feature needs to load version 6 of comctl32.dll but a different version is current loaded in memory.");
            }
            finally
            {
                showState = DialogShowState.Closed;
            }
        }

        // The new task dialog does not support the existing 
        // Win32 functions for closing (e.g. EndDialog()); instead,
        // a "click button" message is sent. In this case, we're 
        // abstracting out to say that the TaskDialog consumer can
        // simply call "Close" and we'll "click" the cancel button. 
        // Note that the cancel button doesn't actually
        // have to exist for this to work.
        internal void NativeClose(TaskDialogResult result)
        {
            showState = DialogShowState.Closing;

            int id = (int)TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDCANCEL;

            if(result == TaskDialogResult.Close)
                id = (int)TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDCLOSE;
            else if(result == TaskDialogResult.CustomButtonClicked)
                id = DialogsDefaults.MinimumDialogControlId; // custom buttons
            else if(result == TaskDialogResult.No)
                id = (int)TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDNO;
            else if(result == TaskDialogResult.Ok)
                id = (int)TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDOK;
            else if(result == TaskDialogResult.Retry)
                id = (int)TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDRETRY;
            else if(result == TaskDialogResult.Yes)
                id = (int)TaskDialogNativeMethods.TASKDIALOG_COMMON_BUTTON_RETURN_ID.IDYES;

            SendMessageHelper(TaskDialogNativeMethods.TASKDIALOG_MESSAGES.TDM_CLICK_BUTTON, id, 0);
        }

        #region Main Dialog Proc

        private int DialogProc(
            IntPtr hwnd,
            uint msg,
            IntPtr wParam,
            IntPtr lParam,
            IntPtr lpRefData)
        {
            // Fetch the HWND - it may be the first time we're getting it.
            hWndDialog = hwnd;

            // Big switch on the various notifications the 
            // dialog proc can get.
            switch ((TaskDialogNativeMethods.TASKDIALOG_NOTIFICATIONS)msg)
            {
                case TaskDialogNativeMethods.TASKDIALOG_NOTIFICATIONS.TDN_CREATED:
                    int result = PerformDialogInitialization();
                    outerDialog.RaiseOpenedEvent();
                    return result;
                case TaskDialogNativeMethods.TASKDIALOG_NOTIFICATIONS.TDN_BUTTON_CLICKED:
                    return HandleButtonClick((int)wParam);
                case TaskDialogNativeMethods.TASKDIALOG_NOTIFICATIONS.TDN_RADIO_BUTTON_CLICKED:
                    return HandleRadioButtonClick((int)wParam);
                case TaskDialogNativeMethods.TASKDIALOG_NOTIFICATIONS.TDN_HYPERLINK_CLICKED:
                    return HandleHyperlinkClick(lParam);
                case TaskDialogNativeMethods.TASKDIALOG_NOTIFICATIONS.TDN_HELP:
                    return HandleHelpInvocation();
                case TaskDialogNativeMethods.TASKDIALOG_NOTIFICATIONS.TDN_TIMER:
                    return HandleTick((int)wParam);
                case TaskDialogNativeMethods.TASKDIALOG_NOTIFICATIONS.TDN_DESTROYED:
                    return PerformDialogCleanup();
                default:
                    break;
            }
            return (int)HRESULT.S_OK;
        }


        // Once the task dialog HWND is open, we need to send 
        // additional messages to configure it.
        private int PerformDialogInitialization()
        {
            // Initialize Progress or Marquee Bar.
            if (IsOptionSet(TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_SHOW_PROGRESS_BAR))
            {
                UpdateProgressBarRange();
                
                // The order of the following is important - 
                // state is more important than value, 
                // and non-normal states turn off the bar value change 
                // animation, which is likely the intended
                // and preferable behavior.
                UpdateProgressBarState(settings.ProgressBarState);
                UpdateProgressBarValue(settings.ProgressBarValue);
                
                // Due to a bug that wasn't fixed in time for RTM of Vista,
                // second SendMessage is required if the state is non-Normal.
                UpdateProgressBarValue(settings.ProgressBarValue);
            }
            else if (IsOptionSet(TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_SHOW_MARQUEE_PROGRESS_BAR))
            {
                // TDM_SET_PROGRESS_BAR_MARQUEE is necessary 
                // to cause the marquee to start animating.
                // Note that this internal task dialog setting is 
                // round-tripped when the marquee is
                // is set to different states, so it never has to 
                // be touched/sent again.
                SendMessageHelper(TaskDialogNativeMethods.TASKDIALOG_MESSAGES.TDM_SET_PROGRESS_BAR_MARQUEE, 1, 0);
                UpdateProgressBarState(settings.ProgressBarState);
            }

            if (settings.ElevatedButtons != null && settings.ElevatedButtons.Count > 0)
            {
                foreach (int id in settings.ElevatedButtons)
                {
                    UpdateElevationIcon(id, true);
                }
            }
            
            return CoreErrorHelper.IGNORED;
        }

        private int HandleButtonClick(int id)
        {
            // First we raise a Click event, if there is a custom button
            // However, we implement Close() by sending a cancel button, so 
            // we don't want to raise a click event in response to that.
            if (showState != DialogShowState.Closing)
                outerDialog.RaiseButtonClickEvent(id);

            // Once that returns, we raise a Closing event for the dialog
            // The Win32 API handles button clicking-and-closing 
            // as an atomic action,
            // but it is more .NET friendly to split them up.
            // Unfortunately, we do NOT have the return values at this stage.
            if(id <= 9)
                return outerDialog.RaiseClosingEvent(id);
            else
                return 1;
        }

        private int HandleRadioButtonClick(int id)
        {
            // When the dialog sets the radio button to default, 
            // it (somewhat confusingly)issues a radio button clicked event
            //  - we mask that out - though ONLY if
            // we do have a default radio button
            if (firstRadioButtonClicked && !IsOptionSet(TaskDialogNativeMethods.TASKDIALOG_FLAGS.TDF_NO_DEFAULT_RADIO_BUTTON))
                firstRadioButtonClicked = false;
            else
            {
                outerDialog.RaiseButtonClickEvent(id);
            }

            // Note: we don't raise Closing, as radio 
            // buttons are non-committing buttons
            return CoreErrorHelper.IGNORED;
        }

        private int HandleHyperlinkClick(IntPtr pszHREF)
        {
            string link = Marshal.PtrToStringUni(pszHREF);
            outerDialog.RaiseHyperlinkClickEvent(link);

            return CoreErrorHelper.IGNORED;
        }


        private int HandleTick(int ticks)
        {
            outerDialog.RaiseTickEvent(ticks);
            return CoreErrorHelper.IGNORED;
        }

        private int HandleHelpInvocation()
        {
            outerDialog.RaiseHelpInvokedEvent();
            return CoreErrorHelper.IGNORED;
        }

        // There should be little we need to do here, 
        // as the use of the NativeTaskDialog is
        // that it is instantiated for a single show, then disposed of.
        private int PerformDialogCleanup()
        {
            firstRadioButtonClicked = true;

            return CoreErrorHelper.IGNORED;
        }

        #endregion

        #region Update members

        internal void UpdateProgressBarValue(int i)
        {
            AssertCurrentlyShowing();
            SendMessageHelper(TaskDialogNativeMethods.TASKDIALOG_MESSAGES.TDM_SET_PROGRESS_BAR_POS, i, 0);
        }

        internal void UpdateProgressBarRange()
        {
            AssertCurrentlyShowing();

            // Build range LPARAM - note it is in REVERSE intuitive order.
            long range = NativeTaskDialog.MakeLongLParam(
                settings.ProgressBarMaximum, 
                settings.ProgressBarMinimum);

            SendMessageHelper(TaskDialogNativeMethods.TASKDIALOG_MESSAGES.TDM_SET_PROGRESS_BAR_RANGE, 0, range);
        }

        internal void UpdateProgressBarState(TaskDialogProgressBarState state)
        {
            AssertCurrentlyShowing();
            SendMessageHelper(TaskDialogNativeMethods.TASKDIALOG_MESSAGES.TDM_SET_PROGRESS_BAR_STATE, (int)state, 0);
        }

        internal void UpdateText(string text)
        {
            UpdateTextCore(text, TaskDialogNativeMethods.TASKDIALOG_ELEMENTS.TDE_CONTENT);
        }

        internal void UpdateInstruction(string instruction)
        {
            UpdateTextCore(instruction, TaskDialogNativeMethods.TASKDIALOG_ELEMENTS.TDE_MAIN_INSTRUCTION);
        }

        internal void UpdateFooterText(string footerText)
        {
            UpdateTextCore(footerText, TaskDialogNativeMethods.TASKDIALOG_ELEMENTS.TDE_FOOTER);
        }

        internal void UpdateExpandedText(string expandedText)
        {
            UpdateTextCore(expandedText, TaskDialogNativeMethods.TASKDIALOG_ELEMENTS.TDE_EXPANDED_INFORMATION);
        }

        private void UpdateTextCore(string s, TaskDialogNativeMethods.TASKDIALOG_ELEMENTS element)
        {
            AssertCurrentlyShowing();

            FreeOldString(element);
            SendMessageHelper(
                TaskDialogNativeMethods.TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT,
                (int)element,
                (long)MakeNewString(s, element));
        }

        internal void UpdateMainIcon(TaskDialogStandardIcon mainIcon)
        {
            UpdateIconCore(mainIcon, TaskDialogNativeMethods.TASKDIALOG_ICON_ELEMENT.TDIE_ICON_MAIN);
        }

        internal void UpdateFooterIcon(TaskDialogStandardIcon footerIcon)
        {
            UpdateIconCore(footerIcon, TaskDialogNativeMethods.TASKDIALOG_ICON_ELEMENT.TDIE_ICON_FOOTER);
        }

        private void UpdateIconCore(TaskDialogStandardIcon icon, TaskDialogNativeMethods.TASKDIALOG_ICON_ELEMENT element)
        {
            AssertCurrentlyShowing();
            SendMessageHelper(
                TaskDialogNativeMethods.TASKDIALOG_MESSAGES.TDM_UPDATE_ICON,
                (int)element,
                (long)icon);
        }

        internal void UpdateCheckBoxChecked(bool cbc)
        {
            AssertCurrentlyShowing();
            SendMessageHelper(
                TaskDialogNativeMethods.TASKDIALOG_MESSAGES.TDM_CLICK_VERIFICATION,
                (cbc ? 1 : 0),
                1);
        }

        internal void UpdateElevationIcon(int buttonId, bool showIcon)
        {
            AssertCurrentlyShowing();
            SendMessageHelper(
                TaskDialogNativeMethods.TASKDIALOG_MESSAGES.TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE,
                buttonId,
                Convert.ToInt32(showIcon));
        }

        internal void UpdateButtonEnabled(int buttonID, bool enabled)
        {
            AssertCurrentlyShowing();
            SendMessageHelper(
                TaskDialogNativeMethods.TASKDIALOG_MESSAGES.TDM_ENABLE_BUTTON, buttonID, enabled == true ? 1 : 0);
        }
        internal void UpdateRadioButtonEnabled(int buttonID, bool enabled)
        {
            AssertCurrentlyShowing();
            SendMessageHelper(
                TaskDialogNativeMethods.TASKDIALOG_MESSAGES.TDM_ENABLE_RADIO_BUTTON, buttonID, enabled == true? 1 : 0);
        }

        internal void AssertCurrentlyShowing()
        {
            Debug.Assert(showState == DialogShowState.Showing, "Update*() methods should only be called while native dialog is showing");
        }

        #endregion

        #region Helpers

        private int SendMessageHelper(TaskDialogNativeMethods.TASKDIALOG_MESSAGES msg, int wParam, long lParam)
        {
            // Be sure to at least assert here - 
            // messages to invalid handles often just disappear silently
            Debug.Assert(hWndDialog != null, 
                "HWND for dialog is null during SendMessage");

            return (int)CoreNativeMethods.SendMessage(
                hWndDialog,
                (uint)msg,
                (IntPtr)wParam,
                new IntPtr(lParam));
        }

        private bool IsOptionSet(TaskDialogNativeMethods.TASKDIALOG_FLAGS flag)
        {
            return ((nativeDialogConfig.dwFlags & flag) == flag);
        }

        // Allocates a new string on the unmanaged heap, 
        // and stores the pointer so we can free it later.

        private IntPtr MakeNewString(string s,
            TaskDialogNativeMethods.TASKDIALOG_ELEMENTS element)
        {
            IntPtr newStringPtr = Marshal.StringToHGlobalUni(s);
            updatedStrings[(int)element] = newStringPtr;
            return newStringPtr;
        }

        // Checks to see if the given element already has an 
        // updated string, and if so, 
        // frees it. This is done in preparation for a call to 
        // MakeNewString(), to prevent
        // leaks from multiple updates calls on the same element 
        // within a single native dialog lifetime.

        private void FreeOldString(TaskDialogNativeMethods.TASKDIALOG_ELEMENTS element)
        {
            int elementIndex = (int)element;
            if (updatedStrings[elementIndex] != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(updatedStrings[elementIndex]);
                updatedStrings[elementIndex] = IntPtr.Zero;
            }
        }

        // Based on the following defines in WinDef.h and WinUser.h:
        // #define MAKELPARAM(l, h) ((LPARAM)(DWORD)MAKELONG(l, h))
        // #define MAKELONG(a, b) ((LONG)(((WORD)(((DWORD_PTR)(a)) & 0xffff)) | ((DWORD)((WORD)(((DWORD_PTR)(b)) & 0xffff))) << 16))
        private static long MakeLongLParam(int a, int b)
        {
            return (a << 16) + b;
        }

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

        private void MarshalDialogControlStructs()
        {
            if (settings.Buttons != null && settings.Buttons.Length > 0)
            {
                buttonArray = AllocateAndMarshalButtons(settings.Buttons);
                settings.NativeConfiguration.pButtons = buttonArray;
                settings.NativeConfiguration.cButtons = (uint)settings.Buttons.Length;
            }
            if (settings.RadioButtons != null && settings.RadioButtons.Length > 0)
            {
                radioButtonArray = AllocateAndMarshalButtons(settings.RadioButtons);
                settings.NativeConfiguration.pRadioButtons = radioButtonArray;
                settings.NativeConfiguration.cRadioButtons = (uint)settings.RadioButtons.Length;
            }
        }

        private static IntPtr AllocateAndMarshalButtons(TaskDialogNativeMethods.TASKDIALOG_BUTTON[] structs)
        {
            IntPtr initialPtr = Marshal.AllocHGlobal(
                Marshal.SizeOf(typeof(TaskDialogNativeMethods.TASKDIALOG_BUTTON)) * structs.Length);

            IntPtr currentPtr = initialPtr;
            foreach (TaskDialogNativeMethods.TASKDIALOG_BUTTON button in structs)
            {
                Marshal.StructureToPtr(button, currentPtr, false);
                currentPtr = (IntPtr)((int)currentPtr + Marshal.SizeOf(button));
            }

            return initialPtr;
        }

        #endregion

        #region IDispose Pattern

        private bool disposed;

        // Finalizer and IDisposable implementation.
        public void Dispose() 
        { 
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        ~NativeTaskDialog() 
        { 
            Dispose(false); 
        }

        // Core disposing logic.
        protected void Dispose(bool disposing)
        {
            if (!disposed)
            {
                disposed = true; 

                // Single biggest resource - make sure the dialog 
                // itself has been instructed to close.

                if (showState == DialogShowState.Showing)
                    NativeClose(TaskDialogResult.Cancel);
                
                // Clean up custom allocated strings that were updated
                // while the dialog was showing. Note that the strings
                // passed in the initial TaskDialogIndirect call will
                // be cleaned up automagically by the default 
                // marshalling logic.

                if (updatedStrings != null)
                {
                    for (int i = 0; i < updatedStrings.Length; i++)
                    {
                        if (updatedStrings[i] != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(updatedStrings[i]);
                            updatedStrings[i] = IntPtr.Zero;
                        }
                    }
                }

                // Clean up the button and radio button arrays, if any.
                if (buttonArray != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(buttonArray);
                    buttonArray = IntPtr.Zero;
                }
                if (radioButtonArray != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(radioButtonArray);
                    radioButtonArray = IntPtr.Zero;
                }

                if (disposing)
                {
                    // Clean up managed resources - currently there are none
                    // that are interesting.
                }
            }
        }

        #endregion
    }
}

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
Web Developer PageLabs
United States United States
I'm the founder of PageLabs, a web-based performance and SEO optimization site.

Give your site a boost in performance, even take a free speed test!

http://www.pagelabs.com

Comments and Discussions