I found a beautiful article (Link) that provides a simple solution to the taskbar progress bar problem. In summary, it instructs you to download the Windows API pack from the MSDN website, adding a reference to the Microsoft.WindowsAPICodePack.Shell.dll that it contains, and finally add three lines of code to your application:
I just wanted to add some taskbar progress animation to my WinForms application,
without having to download code packs or switch to WPF to use TaskbarItemInfo.
The solution was a class that uses the ITaskbarList3 interface:
Easily add taskbar progress to the taskbar icon of an existing form.
No NuGet package, no complex coding... just copy/paste.
The only code you need to add to your form, are calls to the new form properties which update the icon. (Why junk up your form with API calls, procs, and an enum... when it can be avoided?)
Put the following code into a new class module in your project.
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
namespace System.Windows.Forms
{
[ToolboxBitmap(typeof(Form))]
public class ProgressForm : Form
{
private ThumbnailProgressState m_State = ThumbnailProgressState.NoProgress;
private int m_Maximum = 100;
private int m_Value = 0;
//
// Summary:
// Gets or sets the state in which progress should be indicated on the task
// bar.
//
// Returns:
// One of the System.Windows.Forms.ThumbnailProgressState values. The default is System.Windows.Forms.ThumbnailProgressState.NoProgress
//
// Exceptions:
// T:System.ComponentModel.InvalidEnumArgumentException:
// The value is not a member of the System.Windows.Forms.ThumbnailProgressState enumeration.
//
// History:
// An old Microsoft article
// July 2022 - Verified with Win10, reworded/reformatted ThumbnailProgressState.
// Argument exception of property Value is more granular.
// Windows7orGreater - no longer relevant, but unchanged in case there is a need to distinguish versions beyond Windows 10
//
// Usage:
// Add class with this code to your project
// Add the inheritance "ProgressForm" to the form that will show progress.
// Example: public partial class Form1 : ProgressForm
//
[Browsable(true)]
[DefaultValue(ThumbnailProgressState.NoProgress)]
[EditorBrowsable(EditorBrowsableState.Always)]
public ThumbnailProgressState State
{
get { return m_State; }
set
{
switch (value)
{
case ThumbnailProgressState.NoProgress:
case ThumbnailProgressState.Indeterminate:
case ThumbnailProgressState.Normal:
case ThumbnailProgressState.Error:
case ThumbnailProgressState.Paused:
m_State = value;
OnStateChanged(new EventArgs());
break;
default:
throw new InvalidEnumArgumentException("The value is not a member of the System.Windows.Forms.ThumbnailProgressState enumeration.");
}
}
}
//
// Summary:
// Gets or sets the current position of the progress bar.
//
// Returns:
// The position within the range of the progress bar. The default is 0.
//
// Exceptions:
// T:System.ArgumentException:
// • The value specified is greater than the value of the System.Windows.Forms.ProgressForm.Maximum property.
// • The value specified is less than 0.
[Browsable(true)]
[DefaultValue(0)]
[EditorBrowsable(EditorBrowsableState.Always)]
public int Value
{
get { return m_Value; }
set
{
if (value > m_Maximum)
{
throw new ArgumentException("The value specified is greater than the value of the System.Windows.Forms.ProgressForm.Maximum property.");
}
else if(value < 0)
{
throw new ArgumentException("The value specified is less than 0.");
}
else
{
m_Value = value;
OnValueChanged(new EventArgs());
}
}
}
//
// Summary:
// Gets or sets the maximum value of the range of the control.
//
// Returns:
// The maximum value of the range. The default is 100.
//
// Exceptions:
// T:System.ArgumentException:
// The value specified is less than 0.
[Browsable(true)]
[DefaultValue(100)]
[EditorBrowsable(EditorBrowsableState.Always)]
public int Maximum
{
get { return m_Maximum; }
set
{
if (value < 0)
{
throw new ArgumentException("The value specified is less than 0.");
}
else
{
m_Maximum = value;
if (value < m_Value) m_Value = value;
OnMaximumChanged(new EventArgs());
}
}
}
protected virtual void OnStateChanged(EventArgs e)
{
if (Windows7orGreater) SetProgressState();
}
protected virtual void OnValueChanged(EventArgs e)
{
if (Windows7orGreater) SetProgressValue();
}
protected virtual void OnMaximumChanged(EventArgs e)
{
if (Windows7orGreater) SetProgressValue();
}
protected override void WndProc(ref Message m)
{
if (Windows7orGreater)
{
// if taskbar button created or recreated, update progress status
if (m.Msg == WM_TaskbarButtonCreated) SetProgressState();
}
base.WndProc(ref m);
}
private void SetProgressState()
{
// must be Windows7orGreater
TaskbarList.SetProgressState(Handle, m_State);
SetProgressValue();
}
private void SetProgressValue()
{
switch (m_State)
{
case ThumbnailProgressState.Normal:
case ThumbnailProgressState.Error:
case ThumbnailProgressState.Paused:
TaskbarList.SetProgressValue(Handle, (ulong)m_Value, (ulong)m_Maximum);
break;
}
}
private static int WM_TaskbarButtonCreated = -1;
private static int _winVersion = -1;
internal static bool Windows7orGreater
{
get
{
if (_winVersion < 0)
{
Version osVersion = Environment.OSVersion.Version;
if ((osVersion.Major == 6 && osVersion.Minor > 0) || (osVersion.Major > 6))
{
// Taskbar progress indicator requires Windows 7 Or Greater
_winVersion = 1;
// register taskbar creation window message
WM_TaskbarButtonCreated = RegisterWindowMessage(@"TaskbarButtonCreated");
}
else
{
_winVersion = 0;
}
}
return (_winVersion > 0);
}
}
private static ITaskbarList3 _taskbarList = null;
internal static ITaskbarList3 TaskbarList
{
get
{
if (_taskbarList == null)
{
lock (typeof(ProgressForm))
{
if (_taskbarList == null)
{
_taskbarList = (ITaskbarList3)new CTaskbarList();
_taskbarList.HrInit();
}
}
}
return _taskbarList;
}
}
[DllImport("user32.dll")]
internal static extern int RegisterWindowMessage(string message);
[ComImportAttribute()]
[GuidAttribute("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
internal interface ITaskbarList3
{
// 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
void SetProgressValue(IntPtr hwnd, UInt64 ullCompleted, UInt64 ullTotal);
void SetProgressState(IntPtr hwnd, ThumbnailProgressState tbpFlags);
}
[GuidAttribute("56FDF344-FD6D-11d0-958A-006097C9A090")]
[ClassInterfaceAttribute(ClassInterfaceType.None)]
[ComImportAttribute()]
internal class CTaskbarList { }
}
public enum ThumbnailProgressState
{
/// <summary>
/// No progress is displayed.<br>
/// yourFormName.Value is ignored.</br> </summary>
NoProgress = 0,
/// <summary>
/// Normal progress is displayed.<br>
/// The bar is GREEN.</br> </summary>
Normal = 0x2,
/// <summary>
/// The operation is paused.<br>
/// The bar is YELLOW.</br></summary>
Paused = 0x8,
/// <summary>
/// An error occurred.<br>
/// The bar is RED.</br> </summary>
Error = 0x4,
/// <summary>
/// The progress is indeterminate.<br>
/// Marquee style bar (constant scroll).</br> </summary>
Indeterminate = 0x1
}
}
Add ProgressForm to the form which needs the ability to show progress:
public partial class Form1 : ProgressForm
Add this sample code to your form and step through it to watch it work:
if (this.State == ThumbnailProgressState.NoProgress)
{
// Show the progress with GREEN
this.State = ThumbnailProgressState.Normal;
}
this.Maximum = 512; // set to any integer above zero (defaults to 100)
this.Value = 0; // Visually equivalent to this.State = ThumbnailProgressState.NoProgress;
this.Value = 256; // 50% solid GREEN overlay
this.State = ThumbnailProgressState.Error; // Still 50% but now solid RED overlay
this.State = ThumbnailProgressState.Paused; // Still 50% but now solid YELLOW overlay
this.Value = 384; // 75% YELLOW overlay (384 / 512 == 75%)
this.Value = this.Maximum; // 100% YELLOW overlay (512 of 512)
this.State = ThumbnailProgressState.Indeterminate; // Marquee. (Ignores Value and constantly scrolls with a fade)
this.State = ThumbnailProgressState.NoProgress; // Ignores Value, there is no overlay