#1,032 – Show Progress on Windows Taskbar Icon

When your application is doing some work in the background, it’s common to show progress in a ProgressBar within your application.

1032-001

In Windows 7 and Windows 8, you can also make use of the underlying Windows API to show an integrated progress bar on the application’s taskbar icon.  The progress is rendered using a green bar that moves across the width of the icon.  This is helpful for showing progress while your application is minimized.

1032-002

Below is a complete example of how you can update the taskbar icon to show progress.  Some of the low-level API code was taken from the Windows API Code Pack on MSDN, which contains examples of many more API functions that you can call.  You can use the Windows API Code Pack as an external library, or you can call the low-level Windows API yourself.  The code below does the latter and is the minimum code required to show progress on the taskbar.

To start with, we define the following code in a TaskbarAPI.cs file.  This provides a managed wrapper around the underlying Windows API calls.  The main thing is the ITaskbarList4 interface, in its entirety.  We then define some extra stuff for the types that appear in the interface.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace WpfApplication1
{
    internal enum HResult
    {
        Ok = 0x0000

        // Add more constants here, if necessary
    }

    public enum TaskbarProgressBarStatus
    {
        NoProgress = 0,
        Indeterminate = 0x1,
        Normal = 0x2,
        Error = 0x4,
        Paused = 0x8
    }

    internal enum ThumbButtonMask
    {
        Bitmap = 0x1,
        Icon = 0x2,
        Tooltip = 0x4,
        THB_FLAGS = 0x8
    }

    [Flags]
    internal enum ThumbButtonOptions
    {
        Enabled = 0x00000000,
        Disabled = 0x00000001,
        DismissOnClick = 0x00000002,
        NoBackground = 0x00000004,
        Hidden = 0x00000008,
        NonInteractive = 0x00000010
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    internal struct ThumbButton
    {
        ///
/// WPARAM value for a THUMBBUTTON being clicked.
        ///
        internal const int Clicked = 0x1800;

        [MarshalAs(UnmanagedType.U4)]
        internal ThumbButtonMask Mask;
        internal uint Id;
        internal uint Bitmap;
        internal IntPtr Icon;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        internal string Tip;
        [MarshalAs(UnmanagedType.U4)]
        internal ThumbButtonOptions Flags;
    }

    internal enum SetTabPropertiesOption
    {
        None = 0x0,
        UseAppThumbnailAlways = 0x1,
        UseAppThumbnailWhenActive = 0x2,
        UseAppPeekAlways = 0x4,
        UseAppPeekWhenActive = 0x8
    }

    // using System.Runtime.InteropServices
    [ComImportAttribute()]
    [GuidAttribute("c43dc798-95d1-4bea-9030-bb99e2983a1a")]
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface ITaskbarList4
    {
        // ITaskbarList
        [PreserveSig]
        void HrInit();
        [PreserveSig]
        void AddTab(IntPtr hwnd);
        [PreserveSig]
        void DeleteTab(IntPtr hwnd);
        [PreserveSig]
        void ActivateTab(IntPtr hwnd);
        [PreserveSig]
        void SetActiveAlt(IntPtr hwnd);

        // ITaskbarList2
        [PreserveSig]
        void MarkFullscreenWindow(
            IntPtr hwnd,
            [MarshalAs(UnmanagedType.Bool)] bool fFullscreen);

        // ITaskbarList3
        [PreserveSig]
        void SetProgressValue(IntPtr hwnd, UInt64 ullCompleted, UInt64 ullTotal);
        [PreserveSig]
        void SetProgressState(IntPtr hwnd, TaskbarProgressBarStatus tbpFlags);
        [PreserveSig]
        void RegisterTab(IntPtr hwndTab, IntPtr hwndMDI);
        [PreserveSig]
        void UnregisterTab(IntPtr hwndTab);
        [PreserveSig]
        void SetTabOrder(IntPtr hwndTab, IntPtr hwndInsertBefore);
        [PreserveSig]
        void SetTabActive(IntPtr hwndTab, IntPtr hwndInsertBefore, uint dwReserved);
        [PreserveSig]
        HResult ThumbBarAddButtons(
            IntPtr hwnd,
            uint cButtons,
            [MarshalAs(UnmanagedType.LPArray)] ThumbButton[] pButtons);
        [PreserveSig]
        HResult ThumbBarUpdateButtons(
            IntPtr hwnd,
            uint cButtons,
            [MarshalAs(UnmanagedType.LPArray)] ThumbButton[] pButtons);
        [PreserveSig]
        void ThumbBarSetImageList(IntPtr hwnd, IntPtr himl);
        [PreserveSig]
        void SetOverlayIcon(
          IntPtr hwnd,
          IntPtr hIcon,
          [MarshalAs(UnmanagedType.LPWStr)] string pszDescription);
        [PreserveSig]
        void SetThumbnailTooltip(
            IntPtr hwnd,
            [MarshalAs(UnmanagedType.LPWStr)] string pszTip);
        [PreserveSig]
        void SetThumbnailClip(
            IntPtr hwnd,
            IntPtr prcClip);

        // ITaskbarList4
        void SetTabProperties(IntPtr hwndTab, SetTabPropertiesOption stpFlags);
    }

    [GuidAttribute("56FDF344-FD6D-11d0-958A-006097C9A090")]
    [ClassInterfaceAttribute(ClassInterfaceType.None)]
    [ComImportAttribute()]
    internal class CTaskbarList { }
}

Next, we define a little helper class that calls the API on our behalf.  We use a Dispatcher so that we can get access to the application’s main window, doing this on the UI thread.

using System;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;

namespace WpfApplication1
{
    public static class TaskbarUtility
    {
        private static ITaskbarList4 _taskbarList;

        static TaskbarUtility()
        {
            if (!IsSupported())
                throw new Exception("Taskbar functions not available");

            _taskbarList = (ITaskbarList4)new CTaskbarList();
            _taskbarList.HrInit();
        }

        private static bool IsSupported()
        {
            return Environment.OSVersion.Platform == PlatformID.Win32NT &&
                Environment.OSVersion.Version.CompareTo(new Version(6, 1)) >= 0;
        }

        public static void SetProgressState(TaskbarProgressBarStatus state)
        {
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
            {
                _taskbarList.SetProgressState((new WindowInteropHelper(Application.Current.MainWindow)).Handle, state);
            }));
        }

        public static void SetProgressValue(int currentValue, int maximumValue)
        {
            // using System.Windows.Interop
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
            {
                _taskbarList.SetProgressValue(
                    (new WindowInteropHelper(Application.Current.MainWindow)).Handle,
                    Convert.ToUInt64(currentValue),
                    Convert.ToUInt64(maximumValue));
            }));
        }
    }
}

The XAML for the example just contains a ProgressBar and a Button to start the background operation.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="ProgressBar"
        Width="280" Height="140">

    <StackPanel>
        <ProgressBar Value="{Binding TheProgress}"
                     Height="15" Margin="15,15,15,0"/>
        <Label Content="Doing some work..."
               Margin="10,0"/>
        <Button Margin="15" Padding="15,3" HorizontalAlignment="Center"
            Content="Start" Click="Button_Click"/>
    </StackPanel>
</Window>

Our code-behind is also fairly simple.  When the user clicks the Button, we start an operation on the background thread and periodically update both the ProgressBar control and the progress bar built into the taskbar icon.

using System.ComponentModel;
using System.Threading;
using System.Windows;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private int theProgress = 0;
        public int TheProgress
        {
            get { return theProgress; }
            protected set
            {
                theProgress = value;
                RaisePropertyChanged("TheProgress");
            }
        }

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            new Thread(() =>
            {
                TaskbarUtility.SetProgressState(TaskbarProgressBarStatus.Normal);

                for (int i = 1; i <= 100; i++)
                {
                    TheProgress = i;
                    TaskbarUtility.SetProgressValue(i, 100);
                    Thread.Sleep(50);
                }

                TheProgress = 0;
                TaskbarUtility.SetProgressState(TaskbarProgressBarStatus.NoProgress);
            }).Start();
        }

        // INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        private void RaisePropertyChanged(string propName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
}