diff --git a/Windows.Shell/TaskBar/ExtenderProviderBase.cs b/Windows.Shell/TaskBar/ExtenderProviderBase.cs
new file mode 100644
index 00000000..e35bc3a6
--- /dev/null
+++ b/Windows.Shell/TaskBar/ExtenderProviderBase.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.Design;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Vanara.Windows
+{
+ /// A generic base to implement for a single extender type.
+ /// The type of the type that can be extended.
+ public abstract class ExtenderProviderBase : Component, IExtenderProvider, ISupportInitialize where TExtend : Component
+ {
+ /// A dictionary that holds a property bag for each extended type.
+ protected readonly Dictionary> propHash = new Dictionary>();
+
+ /// Initializes a new instance of the class.
+ protected ExtenderProviderBase() { }
+
+ /// Initializes a new instance of the class.
+ /// The parent.
+ protected ExtenderProviderBase(TExtend parent) => OnAddingExtender(parent);
+
+ /// Initializes a new instance of the class.
+ /// The container.
+ /// container
+ protected ExtenderProviderBase(IContainer container) : this()
+ {
+ if (container is null)
+ throw new ArgumentNullException(nameof(container));
+ container.Add(this);
+ }
+
+ /// Occurs when a new extender is being added.
+ protected event EventHandler AddingExtender;
+
+ /// Sets the site.
+ /// The site.
+ public override ISite Site
+ {
+ set
+ {
+ base.Site = value;
+ var parent = (value?.GetService(typeof(IDesignerHost)) as IDesignerHost)?.RootComponent as TExtend;
+ if (parent != null)
+ OnAddingExtender(parent);
+ }
+ }
+
+ /// Gets all extended components that have properties assigned.
+ /// Returns a value.
+ protected IEnumerable ExtendedComponents => propHash.Keys;
+
+ /// Gets the known properties stored against all components.
+ /// Returns a value.
+ protected IEnumerable KnownProperties => propHash.Values.SelectMany(d => d.Keys).Distinct();
+
+ /// Signals the object that initialization is starting.
+ public virtual void BeginInit() { }
+
+ /// Determines whether this instance can extend the specified extendee.
+ /// The extendee.
+ /// if this instance can extend the specified extendee; otherwise, .
+ public virtual bool CanExtend(object extendee) => extendee is TExtend;
+
+ /// Signals the object that initialization is complete.
+ public virtual void EndInit() { }
+
+ /// Gets the property value.
+ /// The type of the property to get.
+ /// The form.
+ /// The default value.
+ /// Name of the field.
+ ///
+ protected virtual T GetPropertyValue(TExtend form, T defaultValue = default, [CallerMemberName] string propName = "")
+ {
+ if (propName.StartsWith("Get"))
+ propName = propName.Remove(0, 3);
+ return propHash.TryGetValue(form, out var props) && props.TryGetValue(propName, out var prop) ? (T)prop : defaultValue;
+ }
+
+ /// Calls the event.
+ /// The extender being added.
+ protected virtual void OnAddingExtender(TExtend extender)
+ {
+ var args = new AddExtenderEventArgs(extender);
+ AddingExtender?.Invoke(this, args);
+ propHash[extender] = args.ExtenderProperties;
+ }
+
+ /// Sets the property value.
+ /// The type of the property to set.
+ /// The form.
+ /// The value.
+ /// Name of the field.
+ protected virtual bool SetPropertyValue(TExtend form, T value, [CallerMemberName] string propName = "")
+ {
+ if (!propHash.ContainsKey(form))
+ OnAddingExtender(form);
+ if (propName.StartsWith("Set"))
+ propName = propName.Remove(0, 3);
+ if (propHash[form].TryGetValue(propName, out var prop) && Equals(prop, value))
+ return false;
+ propHash[form][propName] = value;
+ return true;
+ }
+
+ /// Arguments for the event.
+ public class AddExtenderEventArgs : EventArgs
+ {
+ internal AddExtenderEventArgs(TExtend parent)
+ {
+ Extender = parent;
+ ExtenderProperties = new Dictionary();
+ }
+
+ /// Gets the extender being added.
+ /// The extender.
+ public TExtend Extender { get; }
+
+ /// Gets or sets the property bag to be associated with this extender.
+ /// The extender property bag.
+ public Dictionary ExtenderProperties { get; set; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Windows.Shell/TaskBar/HostedMessageWindow.cs b/Windows.Shell/TaskBar/HostedMessageWindow.cs
new file mode 100644
index 00000000..f4e81787
--- /dev/null
+++ b/Windows.Shell/TaskBar/HostedMessageWindow.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Windows.Forms;
+using Vanara.PInvoke;
+
+namespace Vanara.Windows.Shell
+{
+ internal interface IMessageWindowHost
+ {
+ void WndProc(ref Message msg);
+ }
+
+ [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
+ [SecuritySafeCritical]
+ internal class HostedMessageWindow : NativeWindow where THost : IMessageWindowHost
+ {
+ private readonly THost host;
+ private GCHandle rooting;
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
+ internal HostedMessageWindow(THost host) => this.host = host ?? throw new ArgumentNullException(nameof(host));
+
+ ~HostedMessageWindow()
+ {
+ if (Handle != default && !DoNotClose)
+ User32.PostMessage(Handle, (uint)User32.WindowMessage.WM_CLOSE);
+ }
+
+ protected virtual bool DoNotClose => false;
+
+ public void LockReference(bool locked)
+ {
+ if (locked)
+ {
+ if (!rooting.IsAllocated)
+ rooting = GCHandle.Alloc(host, GCHandleType.Normal);
+ }
+ else
+ {
+ if (rooting.IsAllocated)
+ rooting.Free();
+ }
+ }
+
+ protected override void OnThreadException(Exception e) => Application.OnThreadException(e);
+
+ [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
+ protected override void WndProc(ref Message m)
+ {
+ host.WndProc(ref m);
+ base.WndProc(ref m);
+ }
+ }
+
+ [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
+ internal class WindowMessageHook : HostedMessageWindow where THost : IMessageWindowHost
+ {
+ public WindowMessageHook(Control parent, THost host) : base(host)
+ {
+ if (parent is null) throw new ArgumentNullException(nameof(parent));
+ parent.HandleCreated += (s, e) => AssignHandle(((Control)s).Handle);
+ parent.HandleDestroyed += (s, e) => ReleaseHandle();
+ }
+
+ protected override bool DoNotClose => true;
+ }
+}
\ No newline at end of file
diff --git a/Windows.Shell/TaskBar/ImageIndexer.cs b/Windows.Shell/TaskBar/ImageIndexer.cs
new file mode 100644
index 00000000..1e4ab6dc
--- /dev/null
+++ b/Windows.Shell/TaskBar/ImageIndexer.cs
@@ -0,0 +1,40 @@
+using System.Windows.Forms;
+
+namespace Vanara.Windows.Shell
+{
+ public partial class ThumbnailToolbarButton
+ {
+ internal class ImageIndexer
+ {
+ private int index = -1;
+ private string key = string.Empty;
+ private bool useIntegerIndex = true;
+
+ public virtual int ActualIndex => useIntegerIndex ? Index : (ImageList is null ? -1 : ImageList.Images.IndexOfKey(Key));
+
+ public virtual ImageList ImageList { get; set; }
+
+ public virtual int Index
+ {
+ get => index;
+ set
+ {
+ key = string.Empty;
+ index = value;
+ useIntegerIndex = true;
+ }
+ }
+
+ public virtual string Key
+ {
+ get => key;
+ set
+ {
+ index = -1;
+ key = value ?? string.Empty;
+ useIntegerIndex = false;
+ }
+ }
+ }
+ }
+}
diff --git a/Windows.Shell/TaskBar/JumpList.cs b/Windows.Shell/TaskBar/JumpList.cs
new file mode 100644
index 00000000..bde28400
--- /dev/null
+++ b/Windows.Shell/TaskBar/JumpList.cs
@@ -0,0 +1,538 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.ComponentModel.Design.Serialization;
+using System.Drawing.Design;
+using System.Globalization;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Vanara.InteropServices;
+using Vanara.PInvoke;
+using static Vanara.PInvoke.Ole32;
+using static Vanara.PInvoke.PropSys;
+using static Vanara.PInvoke.Shell32;
+
+namespace Vanara.Windows.Shell
+{
+ /// Represents a Jump List item.
+ ///
+ public interface IJumpListItem : INotifyPropertyChanged
+ {
+ /// Gets the category to which the item belongs.
+ /// The category name.
+ string Category { get; }
+
+ /// Creates a shell object based on this item.
+ /// An instance of either or .
+ object GetShellObject();
+ }
+
+ /// Provides access to the jump list on the application's task bar icon.
+ [TypeConverter(typeof(GenericExpandableObjectConverter))]
+ [Editor(typeof(JumpListItemCollectionEditor), typeof(UITypeEditor))]
+ [Description("Provides access to the jump list on the application's task bar icon.")]
+ public class JumpList : ObservableCollection
+ {
+ /// Initializes a new instance of the class.
+ public JumpList() => CollectionChanged += OnCollectionChanged;
+
+ /// Gets the number of items in the collection.
+ /// The count.
+ [Browsable(false)]
+ public new int Count => base.Count;
+
+ /// Whether to show the special "Frequent" category.
+ ///
+ /// This category is managed by the Shell and keeps track of items that are frequently accessed by this program. Applications can
+ /// request that specific items are included here by calling JumpList.AddToRecentCategory. Because of duplication, applications
+ /// generally should not have both ShowRecentCategory and ShowFrequentCategory set at the same time.
+ ///
+ [Category("Appearance"), DefaultValue(false)]
+ [Description("Gets or sets Whether to show the special \"Frequent\" category.")]
+ public bool ShowFrequentCategory { get; set; }
+
+ /// Whether to show the special "Recent" category.
+ ///
+ /// This category is managed by the Shell and keeps track of items that have been recently accessed by this program. Applications
+ /// can request that specific items are included here by calling JumpList.AddToRecentCategory Because of duplication, applications
+ /// generally should not have both ShowRecentCategory and ShowFrequentCategory set at the same time.
+ ///
+ [Category("Appearance"), DefaultValue(false)]
+ [Description("Gets or sets Whether to show the special \"Recent\" category.")]
+ public bool ShowRecentCategory { get; set; }
+
+ ///
+ /// Notifies the system that an item has been accessed, for the purposes of tracking those items used most recently and most frequently.
+ ///
+ /// The document path to add.
+ public static void AddToRecentDocs(string docPath)
+ {
+ if (docPath is null) throw new ArgumentNullException(nameof(docPath));
+ SHAddToRecentDocs(SHARD.SHARD_PATHW, System.IO.Path.GetFullPath(docPath));
+ }
+
+ ///
+ /// Notifies the system that an item has been accessed, for the purposes of tracking those items used most recently and most frequently.
+ ///
+ /// The to add.
+ public static void AddToRecentDocs(IShellItem iShellItem)
+ {
+ if (iShellItem is null) throw new ArgumentNullException(nameof(iShellItem));
+ SHAddToRecentDocs(SHARD.SHARD_SHELLITEM, iShellItem);
+ }
+
+ ///
+ /// Notifies the system that an item has been accessed, for the purposes of tracking those items used most recently and most frequently.
+ ///
+ /// The to add.
+ /// iShellLink
+ public static void AddToRecentDocs(IShellLinkW iShellLink)
+ {
+ if (iShellLink is null) throw new ArgumentNullException(nameof(iShellLink));
+ SHAddToRecentDocs(SHARD.SHARD_LINK, iShellLink);
+ }
+
+ ///
+ /// Notifies the system that an item has been accessed, for the purposes of tracking those items used most recently and most frequently.
+ ///
+ /// The to add.
+ /// shellItem
+ public static void AddToRecentDocs(ShellItem shellItem)
+ {
+ if (shellItem is null) throw new ArgumentNullException(nameof(shellItem));
+ if (shellItem is ShellLink lnk)
+ AddToRecentDocs(lnk.link);
+ else
+ AddToRecentDocs(shellItem.IShellItem);
+ }
+
+ /// Clears the system usage data for recent documents.
+ public static void ClearRecentDocs() => SHAddToRecentDocs(0, (string)null);
+
+ /// Deletes a custom Jump List for a specified application.
+ /// The AppUserModelID of the process whose taskbar button representation displays the custom Jump List.
+ public static void DeleteList(string appId = null)
+ {
+ using var icdl = ComReleaserFactory.Create(new ICustomDestinationList());
+ icdl.Item.DeleteList(appId);
+ }
+
+ /// Applies the the current settings for the jumplist to the taskbar button.
+ /// The application identifier.
+ public void ApplySettings(string appId = null)
+ {
+ using var icdl = ComReleaserFactory.Create(new ICustomDestinationList());
+ if (!string.IsNullOrEmpty(appId))
+ icdl.Item.SetAppID(appId);
+
+ using var ioaRemoved = ComReleaserFactory.Create(icdl.Item.BeginList(out _));
+ var removedObjs = ioaRemoved.Item.ToArray