Finished intial work on TaskbarButton form extender that fully enables all interactions with the taskbar button in the UI.

pull/119/head
dahall 2020-02-26 15:20:47 -07:00
parent 129a8247aa
commit ae3d4dc52c
12 changed files with 1682 additions and 27 deletions

View File

@ -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
{
/// <summary>A generic base to implement <see cref="IExtenderProvider"/> for a single extender type.</summary>
/// <typeparam name="TExtend">The type of the type that can be extended.</typeparam>
public abstract class ExtenderProviderBase<TExtend> : Component, IExtenderProvider, ISupportInitialize where TExtend : Component
{
/// <summary>A dictionary that holds a property bag for each extended type.</summary>
protected readonly Dictionary<TExtend, Dictionary<string, object>> propHash = new Dictionary<TExtend, Dictionary<string, object>>();
/// <summary>Initializes a new instance of the <see cref="ExtenderProviderBase{TExtend}"/> class.</summary>
protected ExtenderProviderBase() { }
/// <summary>Initializes a new instance of the <see cref="ExtenderProviderBase{TExtend}"/> class.</summary>
/// <param name="parent">The parent.</param>
protected ExtenderProviderBase(TExtend parent) => OnAddingExtender(parent);
/// <summary>Initializes a new instance of the <see cref="ExtenderProviderBase{TExtend}"/> class.</summary>
/// <param name="container">The container.</param>
/// <exception cref="ArgumentNullException">container</exception>
protected ExtenderProviderBase(IContainer container) : this()
{
if (container is null)
throw new ArgumentNullException(nameof(container));
container.Add(this);
}
/// <summary>Occurs when a new extender is being added.</summary>
protected event EventHandler<AddExtenderEventArgs> AddingExtender;
/// <summary>Sets the site.</summary>
/// <value>The site.</value>
public override ISite Site
{
set
{
base.Site = value;
var parent = (value?.GetService(typeof(IDesignerHost)) as IDesignerHost)?.RootComponent as TExtend;
if (parent != null)
OnAddingExtender(parent);
}
}
/// <summary>Gets all extended components that have properties assigned.</summary>
/// <value>Returns a <see cref="IEnumerable{TExt}"/> value.</value>
protected IEnumerable<TExtend> ExtendedComponents => propHash.Keys;
/// <summary>Gets the known properties stored against all components.</summary>
/// <value>Returns a <see cref="IEnumerable{T}"/> value.</value>
protected IEnumerable<string> KnownProperties => propHash.Values.SelectMany(d => d.Keys).Distinct();
/// <summary>Signals the object that initialization is starting.</summary>
public virtual void BeginInit() { }
/// <summary>Determines whether this instance can extend the specified extendee.</summary>
/// <param name="extendee">The extendee.</param>
/// <returns><see langword="true"/> if this instance can extend the specified extendee; otherwise, <see langword="false"/>.</returns>
public virtual bool CanExtend(object extendee) => extendee is TExtend;
/// <summary>Signals the object that initialization is complete.</summary>
public virtual void EndInit() { }
/// <summary>Gets the property value.</summary>
/// <typeparam name="T">The type of the property to get.</typeparam>
/// <param name="form">The form.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="propName">Name of the field.</param>
/// <returns></returns>
protected virtual T GetPropertyValue<T>(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;
}
/// <summary>Calls the <see cref="AddingExtender"/> event.</summary>
/// <param name="extender">The extender being added.</param>
protected virtual void OnAddingExtender(TExtend extender)
{
var args = new AddExtenderEventArgs(extender);
AddingExtender?.Invoke(this, args);
propHash[extender] = args.ExtenderProperties;
}
/// <summary>Sets the property value.</summary>
/// <typeparam name="T">The type of the property to set.</typeparam>
/// <param name="form">The form.</param>
/// <param name="value">The value.</param>
/// <param name="propName">Name of the field.</param>
protected virtual bool SetPropertyValue<T>(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;
}
/// <summary>Arguments for the <see cref="AddingExtender"/> event.</summary>
public class AddExtenderEventArgs : EventArgs
{
internal AddExtenderEventArgs(TExtend parent)
{
Extender = parent;
ExtenderProperties = new Dictionary<string, object>();
}
/// <summary>Gets the extender being added.</summary>
/// <value>The extender.</value>
public TExtend Extender { get; }
/// <summary>Gets or sets the property bag to be associated with this extender.</summary>
/// <value>The extender property bag.</value>
public Dictionary<string, object> ExtenderProperties { get; set; }
}
}
}

View File

@ -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<THost> : 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<THost> : HostedMessageWindow<THost> 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;
}
}

View File

@ -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;
}
}
}
}
}

View File

@ -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
{
/// <summary>Represents a Jump List item.</summary>
/// <seealso cref="INotifyPropertyChanged"/>
public interface IJumpListItem : INotifyPropertyChanged
{
/// <summary>Gets the category to which the item belongs.</summary>
/// <value>The category name.</value>
string Category { get; }
/// <summary>Creates a shell object based on this item.</summary>
/// <returns>An instance of either <see cref="IShellItem"/> or <see cref="IShellLinkW"/>.</returns>
object GetShellObject();
}
/// <summary>Provides access to the jump list on the application's task bar icon.</summary>
[TypeConverter(typeof(GenericExpandableObjectConverter<JumpList>))]
[Editor(typeof(JumpListItemCollectionEditor), typeof(UITypeEditor))]
[Description("Provides access to the jump list on the application's task bar icon.")]
public class JumpList : ObservableCollection<IJumpListItem>
{
/// <summary>Initializes a new instance of the <see cref="JumpList"/> class.</summary>
public JumpList() => CollectionChanged += OnCollectionChanged;
/// <summary>Gets the number of items in the collection.</summary>
/// <value>The count.</value>
[Browsable(false)]
public new int Count => base.Count;
/// <summary>Whether to show the special "Frequent" category.</summary>
/// <remarks>
/// 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.
/// </remarks>
[Category("Appearance"), DefaultValue(false)]
[Description("Gets or sets Whether to show the special \"Frequent\" category.")]
public bool ShowFrequentCategory { get; set; }
/// <summary>Whether to show the special "Recent" category.</summary>
/// <remarks>
/// 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.
/// </remarks>
[Category("Appearance"), DefaultValue(false)]
[Description("Gets or sets Whether to show the special \"Recent\" category.")]
public bool ShowRecentCategory { get; set; }
/// <summary>
/// Notifies the system that an item has been accessed, for the purposes of tracking those items used most recently and most frequently.
/// </summary>
/// <param name="docPath">The document path to add.</param>
public static void AddToRecentDocs(string docPath)
{
if (docPath is null) throw new ArgumentNullException(nameof(docPath));
SHAddToRecentDocs(SHARD.SHARD_PATHW, System.IO.Path.GetFullPath(docPath));
}
/// <summary>
/// Notifies the system that an item has been accessed, for the purposes of tracking those items used most recently and most frequently.
/// </summary>
/// <param name="iShellItem">The <see cref="IShellItem"/> to add.</param>
public static void AddToRecentDocs(IShellItem iShellItem)
{
if (iShellItem is null) throw new ArgumentNullException(nameof(iShellItem));
SHAddToRecentDocs(SHARD.SHARD_SHELLITEM, iShellItem);
}
/// <summary>
/// Notifies the system that an item has been accessed, for the purposes of tracking those items used most recently and most frequently.
/// </summary>
/// <param name="iShellLink">The <see cref="IShellLinkW"/> to add.</param>
/// <exception cref="ArgumentNullException">iShellLink</exception>
public static void AddToRecentDocs(IShellLinkW iShellLink)
{
if (iShellLink is null) throw new ArgumentNullException(nameof(iShellLink));
SHAddToRecentDocs(SHARD.SHARD_LINK, iShellLink);
}
/// <summary>
/// Notifies the system that an item has been accessed, for the purposes of tracking those items used most recently and most frequently.
/// </summary>
/// <param name="shellItem">The <see cref="ShellItem"/> to add.</param>
/// <exception cref="ArgumentNullException">shellItem</exception>
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);
}
/// <summary>Clears the system usage data for recent documents.</summary>
public static void ClearRecentDocs() => SHAddToRecentDocs(0, (string)null);
/// <summary>Deletes a custom Jump List for a specified application.</summary>
/// <param name="appId">The AppUserModelID of the process whose taskbar button representation displays the custom Jump List.</param>
public static void DeleteList(string appId = null)
{
using var icdl = ComReleaserFactory.Create(new ICustomDestinationList());
icdl.Item.DeleteList(appId);
}
/// <summary>Applies the the current settings for the jumplist to the taskbar button.</summary>
/// <param name="appId">The application identifier.</param>
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<IObjectArray>(out _));
var removedObjs = ioaRemoved.Item.ToArray<object>();
var exceptions = new System.Collections.Generic.List<Exception>();
foreach (var cat in this.GroupBy(i => i.Category))
{
using var poc = ComReleaserFactory.Create(new IObjectCollection());
foreach (var o in cat)
{
using var psho = ComReleaserFactory.Create(o.GetShellObject());
if (!IsRemoved(psho.Item))
poc.Item.AddObject(psho.Item);
}
if (cat.Key is null)
icdl.Item.AddUserTasks(poc.Item);
else
{
try { icdl.Item.AppendCategory(cat.Key, poc.Item); }
catch (COMException cex) when (cex.ErrorCode == DESTS_E_NO_MATCHING_ASSOC_HANDLER)
{ exceptions.Add(new InvalidOperationException($"At least one of the destinations in the '{cat.Key}' category has an extension that is not registered to this application.")); }
}
}
if (ShowFrequentCategory)
icdl.Item.AppendKnownCategory(KNOWNDESTCATEGORY.KDC_FREQUENT);
if (ShowRecentCategory)
icdl.Item.AppendKnownCategory(KNOWNDESTCATEGORY.KDC_RECENT);
icdl.Item.CommitList();
if (exceptions.Count > 0)
throw new AggregateException(exceptions);
bool IsRemoved(object shellObj)
{
if (shellObj is IShellItem shi)
{
return Array.Exists(removedObjs, o => o is IShellItem oi && ShellItem.Equals(shi, oi));
}
else if (shellObj is IShellLinkW shl)
{
var cstring = ShellLink.GetCompareString(shl);
return Array.Exists(removedObjs, o => o is IShellLinkW l && cstring == ShellLink.GetCompareString(l));
}
return false;
}
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems.OfType<JumpListDestination>().Any(d => string.IsNullOrEmpty(d.Category)))
throw new InvalidOperationException("A JumpListDestination cannot have a null category.");
}
}
/// <summary>A file-based destination for a jumplist with an associated category.</summary>
public class JumpListDestination : JumpListItem, IJumpListItem
{
private string path;
/// <summary>Initializes a new instance of the <see cref="JumpListDestination"/> class.</summary>
public JumpListDestination(string category, string path)
{
Category = category;
Path = path;
}
/// <summary>The shell item to reference or execute.</summary>
[Editor(typeof(System.Windows.Forms.Design.FileNameEditor), typeof(UITypeEditor))]
[DefaultValue(null)]
public string Path
{
get => path;
set
{
if (value is null) throw new ArgumentNullException(nameof(ShellItem));
if (path == value) return;
path = value;
OnPropertyChanged();
}
}
/// <summary>Converts to string.</summary>
/// <returns>A <see cref="string"/> that represents this instance.</returns>
public override string ToString() => $"{Category}:{Path}";
/// <summary>Creates a shell object based on this item.</summary>
/// <returns>An interface.</returns>
object IJumpListItem.GetShellObject() => ShellUtil.GetShellItemForPath(System.IO.Path.GetFullPath(Path));
}
/// <summary>An item in a Jump List.</summary>
[TypeConverter(typeof(GenericExpandableObjectConverter<JumpListItem>))]
public abstract class JumpListItem : INotifyPropertyChanged
{
private string category;
/// <summary>Occurs when a property value changes.</summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Gets or sets the category to which the item belongs.</summary>
/// <value>The category name.</value>
public string Category
{
get => category;
set
{
if (category == value) return;
category = value;
OnPropertyChanged();
}
}
/// <summary>Called when a property has changed.</summary>
/// <param name="propertyName">Name of the property.</param>
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>A separator which can be inserted into a custom list or task list.</summary>
public class JumpListSeparator : JumpListItem, IJumpListItem
{
/// <summary>Initializes a new instance of the <see cref="JumpListSeparator"/> class and optionally assigns it to a category.</summary>
/// <param name="category">The category name. If this value is <see langword="null"/>, this separator will be inserted into the task list.</param>
public JumpListSeparator(string category = null) => Category = category;
/// <summary>Creates a shell object based on this item.</summary>
/// <returns>An instance of either <see cref="IShellItem"/> or <see cref="IShellLinkW"/>.</returns>
object IJumpListItem.GetShellObject()
{
var link = new IShellLinkW();
var props = (IPropertyStore)link;
props?.SetValue(PROPERTYKEY.System.AppUserModel.IsDestListSeparator, true);
return link;
}
}
/// <summary>A task for a jumplist.</summary>
/// <seealso cref="JumpListItem"/>
public class JumpListTask : JumpListItem, IJumpListItem
{
private int iconResIdx;
private string title, description, path, args, dir, iconPath, appUserModelID;
/// <summary>Initializes a new instance of the <see cref="JumpListTask"/> class.</summary>
public JumpListTask(string title, string applicationPath)
{
Title = title;
ApplicationPath = applicationPath;
}
/// <summary>Gets or sets the application path.</summary>
/// <value>The application path.</value>
/// <exception cref="ArgumentNullException">ApplicationPath</exception>
[DefaultValue(null)]
[Editor(typeof(System.Windows.Forms.Design.FileNameEditor), typeof(UITypeEditor))]
public string ApplicationPath
{
get => path;
set
{
if (value is null) throw new ArgumentNullException(nameof(ApplicationPath));
if (path == value) return;
path = value;
OnPropertyChanged();
}
}
/// <summary>
/// Gets or sets an explicit Application User Model ID used to associate processes, files, and windows with a particular application.
/// </summary>
/// <value>The application path.</value>
/// <remarks>
/// An application must provide its AppUserModelID in the following form. It can have no more than 128 characters and cannot contain
/// spaces. Each section should be camel-cased.
/// <para><c>CompanyName.ProductName.SubProduct.VersionInformation</c></para>
/// <para>
/// CompanyName and ProductName should always be used, while the SubProduct and VersionInformation portions are optional and depend
/// on the application's requirements. SubProduct allows a main application that consists of several subapplications to provide a
/// separate taskbar button for each subapplication and its associated windows. VersionInformation allows two versions of an
/// application to coexist while being seen as discrete entities. If an application is not intended to be used in that way, the
/// VersionInformation should be omitted so that an upgraded version can use the same AppUserModelID as the version that it replaced.
/// </para>
/// </remarks>
[DefaultValue(null)]
public string AppUserModelID
{
get => appUserModelID;
set
{
if (appUserModelID == value) return;
if (value != null && value.Length > 128 || value.Contains(" "))
throw new ArgumentException("Invalid format.");
appUserModelID = value;
OnPropertyChanged();
}
}
/// <summary>Gets or sets the arguments.</summary>
/// <value>The arguments.</value>
[DefaultValue(null)]
public string Arguments
{
get => args;
set
{
if (args == value) return;
args = value;
OnPropertyChanged();
}
}
/// <summary>Gets or sets the description.</summary>
/// <value>The description.</value>
[DefaultValue(null)]
public string Description
{
get => description;
set
{
if (description == value) return;
description = value;
OnPropertyChanged();
}
}
/// <summary>Gets or sets the index of the icon resource.</summary>
/// <value>The index of the icon resource.</value>
[DefaultValue(0)]
public int IconResourceIndex
{
get => iconResIdx;
set
{
if (iconResIdx == value) return;
iconResIdx = value;
OnPropertyChanged();
}
}
/// <summary>Gets or sets the icon resource path.</summary>
/// <value>The icon resource path.</value>
/// <exception cref="ArgumentException">Length of path may not exceed 260 characters. - IconResourcePath</exception>
[DefaultValue(null)]
[Editor(typeof(System.Windows.Forms.Design.FileNameEditor), typeof(UITypeEditor))]
public string IconResourcePath
{
get => iconPath;
set
{
if (iconPath == value) return;
if (iconPath != null && iconPath.Length > Kernel32.MAX_PATH)
throw new ArgumentException("Length of path may not exceed 260 characters.", nameof(IconResourcePath));
iconPath = value;
OnPropertyChanged();
}
}
/// <summary>Gets or sets the title.</summary>
/// <value>The title.</value>
/// <exception cref="ArgumentNullException">Title</exception>
[DefaultValue(null)]
public string Title
{
get => title;
set
{
if (value is null) throw new ArgumentNullException(nameof(Title));
if (title == value) return;
title = value;
OnPropertyChanged();
}
}
/// <summary>Gets or sets the working directory.</summary>
/// <value>The working directory.</value>
[DefaultValue(null)]
[Editor(typeof(System.Windows.Forms.Design.FolderNameEditor), typeof(UITypeEditor))]
public string WorkingDirectory
{
get => dir;
set
{
if (dir == value) return;
dir = value;
OnPropertyChanged();
}
}
/// <summary>Converts to string.</summary>
/// <returns>A <see cref="string"/> that represents this instance.</returns>
public override string ToString() => $"{Category}:{ApplicationPath}";
/// <summary>Creates a shell object based on this item.</summary>
/// <returns>An interface.</returns>
object IJumpListItem.GetShellObject()
{
var link = new IShellLinkW();
link.SetPath(ApplicationPath);
if (!string.IsNullOrEmpty(AppUserModelID))
(link as IPropertyStore)?.SetValue(PROPERTYKEY.System.AppUserModel.ID, AppUserModelID);
if (!string.IsNullOrEmpty(WorkingDirectory))
link.SetWorkingDirectory(WorkingDirectory);
if (!string.IsNullOrEmpty(Arguments))
link.SetArguments(Arguments);
// -1 is a sentinel value indicating not to use the icon.
if (IconResourceIndex != -1)
link.SetIconLocation(IconResourcePath ?? ApplicationPath, IconResourceIndex);
if (!string.IsNullOrEmpty(Description))
link.SetDescription(Description);
if (!string.IsNullOrEmpty(Title))
(link as IPropertyStore)?.SetValue(PROPERTYKEY.System.Title, Title);
return link;
}
}
internal class GenericExpandableObjectConverter<T> : ExpandableObjectConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destType)
{
if (destType == typeof(InstanceDescriptor) || destType == typeof(string))
return true;
return base.CanConvertTo(context, destType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo info, object value, Type destType)
{
if (destType == typeof(InstanceDescriptor))
return new InstanceDescriptor(typeof(T).GetConstructor(new Type[0]), null, false);
if (destType == typeof(string))
return "";
return base.ConvertTo(context, info, value, destType);
}
}
internal class JumpListItemCollectionEditor : System.ComponentModel.Design.CollectionEditor
{
/// <summary>Initializes a new instance of the <see cref="JumpListItemCollectionEditor"/> class.</summary>
public JumpListItemCollectionEditor() : base(typeof(JumpList))
{
}
/// <summary>Creates the collection form.</summary>
/// <returns></returns>
protected override CollectionForm CreateCollectionForm()
{
var f = base.CreateCollectionForm();
f.Text = "JumpList Item Collection Editor";
return f;
}
/// <summary>Creates the new item types.</summary>
/// <returns></returns>
protected override Type[] CreateNewItemTypes() => new[] { typeof(JumpListDestination), typeof(JumpListTask), typeof(JumpListSeparator) };
/// <summary>Sets the items.</summary>
/// <param name="editValue">The edit value.</param>
/// <param name="value">The value.</param>
/// <returns></returns>
protected override object SetItems(object editValue, object[] value)
{
if (editValue is JumpList c)
{
c.Clear();
foreach (var i in value.Cast<IJumpListItem>())
c.Add(i);
}
return editValue;
}
protected override object CreateInstance(Type itemType)
{
if (itemType == typeof(JumpListDestination))
return new JumpListDestination("[Category name]", "[File path]");
if (itemType == typeof(JumpListSeparator))
return new JumpListSeparator();
if (itemType == typeof(JumpListTask))
return new JumpListTask("[Title]", "[Application path]");
return base.CreateInstance(itemType);
}
protected override string GetDisplayText(object value) => value is JumpListSeparator ? "-----------" : value.ToString();
/*protected override string HelpTopic => base.HelpTopic;
public override object EditValue(ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
return base.EditValue(context, provider, value);
}
protected override CollectionForm CreateCollectionForm() => new JumpListItemCollectionEditorForm(this);
protected class JumpListItemCollectionEditorForm : CollectionForm
{
public JumpListItemCollectionEditorForm(CollectionEditor editor) : base(editor)
{
}
protected override void OnEditValueChanged();
}*/
}
}

View File

@ -0,0 +1,317 @@
using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Permissions;
using System.Windows.Forms;
using Vanara.PInvoke;
namespace Vanara.Windows.Shell
{
/// <summary>Provides access to the functionality of the taskbar button.</summary>
/// <seealso cref="System.ComponentModel.Component"/>
[ProvideProperty("AppID", typeof(Form))]
[ProvideProperty("TaskbarButtonTooltip", typeof(Form))]
[ProvideProperty("TaskbarButtonOverlay", typeof(Form))]
[ProvideProperty("TaskbarButtonOverlayTooltip", typeof(Form))]
[ProvideProperty("TaskbarButtonProgressState", typeof(Form))]
[ProvideProperty("TaskbarButtonProgressValue", typeof(Form))]
[ProvideProperty("JumpList", typeof(Form))]
[ProvideProperty("TaskbarButtonThumbnails", typeof(Form))]
public class TaskbarButton : ExtenderProviderBase<Form>, INotifyPropertyChanged
{
private const string category = "Taskbar Button";
static TaskbarButton() => Application.AddMessageFilter(new ThumbButtonMessageFilter());
/// <summary>Initializes a new instance of the <see cref="TaskbarButton"/> class.</summary>
public TaskbarButton()
{
TaskbarButtonCreated += OnTaskbarButtonCreated;
ThumbnailButtonClick += OnThumbnailButtonClick;
}
/// <summary>Initializes a new instance of the <see cref="TaskbarButton"/> class.</summary>
/// <param name="container">The container of this component.</param>
/// <exception cref="System.ArgumentNullException">container</exception>
public TaskbarButton(IContainer container) : this()
{
if (container is null)
throw new ArgumentNullException(nameof(container));
container.Add(this);
}
/// <summary>
/// Occurs when the system reports a taskbar button has been created. The first parameter will contain the HWND of the window for
/// which the button was created.
/// </summary>
public static event Action<HWND> TaskbarButtonCreated;
/// <summary>
/// Occurs when the system reports a thumbnail button has been clicked. The first parameter contains the HWND of the window shown by
/// the thumbnail and the second contains the Command ID of the button that was clicked.
/// </summary>
public static event Action<HWND, int> ThumbnailButtonClick;
/// <summary>Occurs when a property has changed.</summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Gets a value indicating whether the taskbar button has been created.</summary>
/// <value><see langword="true"/> if the taskbar button was created; otherwise, <see langword="false"/>.</value>
[Browsable(false)]
public bool IsTaskbarButtonCreated { get; internal set; } = false;
/// <summary>Signals the object that initialization is starting.</summary>
public override void BeginInit()
{
base.BeginInit();
if (Container is Form f && f.ShowInTaskbar)
TaskbarList.ActivateTaskbarItem(f);
}
/// <summary>Gets the application identifier.</summary>
/// <param name="form">The form.</param>
/// <returns>The application identifier.</returns>
[Category(category), DisplayName("AppID"), DefaultValue(null)]
[Description("Gets or sets the application identifier.")]
public string GetAppID(Form form) => GetPropertyValue<string>(form);
/// <summary>Gets the jumplist to display on this taskbar button.</summary>
/// <param name="form">The form.</param>
/// <returns>The jumplist.</returns>
[Category(category), DisplayName("JumpList"), Localizable(true)]
[Description("Gets the jumplist to display with the taskbar button.")]
public JumpList GetJumpList(Form form)
{
var ret = GetPropertyValue<JumpList>(form);
if (ret is null)
{
ret = new JumpList();
SetPropertyValue(form, ret, "JumpList");
}
return ret;
}
/// <summary>Gets the overlay icon to dispaly on a taskbar button to indicate application status or a notification to the user.</summary>
/// <param name="form">The form.</param>
/// <returns>The overlay icon.</returns>
[Category(category), DisplayName("TaskbarButtonOverlay"), DefaultValue(null), Localizable(true)]
[Description("Gets or sets the overlay icon to dispaly on a taskbar button.")]
public Icon GetTaskbarButtonOverlay(Form form) => GetPropertyValue<Icon>(form);
/// <summary>
/// Gets the overlay tooltip to dispaly on a taskbar button to indicate application status or a notification to the user.
/// </summary>
/// <param name="form">The form.</param>
/// <returns>The overlay tooltip.</returns>
[Category(category), DisplayName("TaskbarButtonOverlayTooltip"), DefaultValue(null), Localizable(true)]
[Description("Gets or sets the overlay tooltip to dispaly on a taskbar button.")]
public string GetTaskbarButtonOverlayTooltip(Form form) => GetPropertyValue<string>(form);
/// <summary>Gets the state of the progress indicator displayed on a taskbar button.</summary>
/// <param name="form">
/// The window in which the progress of an operation is being shown. This window's associated taskbar button will display the
/// progress bar.
/// </param>
/// <returns>The current state of the progress button.</returns>
[Category(category), DisplayName("TaskbarButtonProgressState"), DefaultValue(TaskbarButtonProgressState.None)]
[Description("Gets or sets the state of the progress indicator displayed on a taskbar button.")]
public TaskbarButtonProgressState GetTaskbarButtonProgressState(Form form) => GetPropertyValue(form, TaskbarButtonProgressState.None);
/// <summary>
/// Displays or updates a progress bar hosted in a taskbar button to show the specific percentage completed of the full operation.
/// </summary>
/// <param name="form">The window whose associated taskbar button is being used as a progress indicator.</param>
/// <returns>The proportion of the operation that has been completed at the time the method is called.</returns>
[Category(category), DisplayName("TaskbarButtonProgressValue"), DefaultValue(0.0f)]
[Description("Gets or sets the percentage completion for the progress bar hosted in a taskbar button.")]
public float GetTaskbarButtonProgressValue(Form form) => GetPropertyValue(form, 0.0f);
/// <summary>Gets the taskbar button thumbnails.</summary>
/// <param name="form">The window owning the taskbar button thumbnails.</param>
/// <returns>A collection of thumbnails.</returns>
[Category(category), DisplayName("TaskbarButtonThumbnails")]
[Description("Gets the list of thumbnails associated with the taskbar button.")]
public TaskbarButtonThumbnails GetTaskbarButtonThumbnails(Form form)
{
var ret = GetPropertyValue<TaskbarButtonThumbnails>(form);
if (ret is null)
{
ret = new TaskbarButtonThumbnails(form);
SetPropertyValue(form, ret, "TaskbarButtonThumbnails");
}
return ret;
}
/// <summary>Gets the description displayed on the tooltip of the taskbar button.</summary>
/// <param name="form">The form.</param>
/// <returns>The description</returns>
[Category(category), DisplayName("TaskbarButtonTooltip"), DefaultValue(null), Localizable(true)]
[Description("Gets or sets the description displayed on the tooltip of the taskbar button.")]
public string GetTaskbarButtonTooltip(Form form) => GetPropertyValue<string>(form);
/// <summary>Sets the application identifier.</summary>
/// <param name="form">The form.</param>
/// <param name="value">The value.</param>
public void SetAppID(Form form, string value)
{
if (SetPropertyValue(form, value) && IsTaskbarButtonCreated)
ApplySetting(form, value);
}
/// <summary>Sets the overlay icon to dispaly on a taskbar button to indicate application status or a notification to the user.</summary>
/// <param name="form">The form.</param>
/// <param name="value">The overlay icon to apply.</param>
public void SetTaskbarButtonOverlay(Form form, Icon value)
{
if (SetPropertyValue(form, value) && IsTaskbarButtonCreated)
ApplySetting(form, value);
}
/// <summary>
/// Gets the overlay tooltip to dispaly on a taskbar button to indicate application status or a notification to the user.
/// </summary>
/// <param name="form">The form.</param>
/// <param name="value">The overlay tooltip.</param>
public void SetTaskbarButtonOverlayTooltip(Form form, string value)
{
if (SetPropertyValue(form, value) && IsTaskbarButtonCreated)
ApplySetting(form, value);
}
/// <summary>Sets the type and state of the progress indicator displayed on a taskbar button.</summary>
/// <param name="form">
/// The window in which the progress of an operation is being shown. This window's associated taskbar button will display the
/// progress bar.
/// </param>
/// <param name="value">The current state of the progress button. Specify only one of the enum values.</param>
public void SetTaskbarButtonProgressState(Form form, TaskbarButtonProgressState value)
{
if (SetPropertyValue(form, value) && IsTaskbarButtonCreated)
ApplySetting(form, value);
}
/// <summary>
/// Displays or updates a progress bar hosted in a taskbar button to show the specific percentage completed of the full operation.
/// </summary>
/// <param name="form">The window whose associated taskbar button is being used as a progress indicator.</param>
/// <param name="value">
/// The proportion of the operation that has been completed at the time the method is called. This value must be between 0.0f and
/// </param>
public void SetTaskbarButtonProgressValue(Form form, float value)
{
if (value < 0f || value > 1.0f)
throw new ArgumentOutOfRangeException(nameof(value), "Progress value must be a number between 0 and 1, inclusive.");
if (SetPropertyValue(form, value) && IsTaskbarButtonCreated)
ApplySetting(form, value);
}
/// <summary>Sets the description displayed on the tooltip of the taskbar button.</summary>
/// <param name="form">The form.</param>
/// <param name="value">The description.</param>
public void SetTaskbarButtonTooltip(Form form, string value)
{
if (SetPropertyValue(form, value) && IsTaskbarButtonCreated)
ApplySetting(form, value);
}
/// <summary>Calls the <see cref="PropertyChanged"/> event.</summary>
/// <param name="form">The form.</param>
/// <param name="propName">Name of the changed property.</param>
protected virtual void OnProperyChanged(Form form, string propName) => PropertyChanged?.Invoke(form, new PropertyChangedEventArgs(propName));
private static string GetUniqueProcessID() => System.Reflection.Assembly.GetEntryAssembly()?.GetName().Name ?? null;
private void ApplySetting(Form form, object value, [CallerMemberName] string propName = "")
{
if (propName.StartsWith("Set"))
propName = propName.Remove(0, 3);
switch (propName)
{
case "AppID":
TaskbarList.SetWindowAppId(form.Handle, (string)value);
break;
case "TaskbarButtonTooltip":
TaskbarList.SetThumbnailTooltip(form, (string)value);
break;
case "TaskbarButtonOverlay":
TaskbarList.SetOverlayIcon(form, (Icon)value, GetTaskbarButtonOverlayTooltip(form));
break;
case "TaskbarButtonOverlayTooltip":
TaskbarList.SetOverlayIcon(form, GetTaskbarButtonOverlay(form), (string)value);
break;
case "TaskbarButtonProgressState":
TaskbarList.SetProgressState(form, (TaskbarButtonProgressState)value);
break;
case "TaskbarButtonProgressValue":
TaskbarList.SetProgressValue(form, (ulong)(100000 * (float)value), 100000);
break;
case "TaskbarButtonThumbnails":
GetTaskbarButtonThumbnails(form).ResetToolbar();
break;
case "JumpList":
GetJumpList(form).ApplySettings(GetAppID(form));
break;
default:
throw new InvalidOperationException("Unrecognized setting name.");
}
OnProperyChanged(form, propName);
}
private void OnTaskbarButtonCreated(HWND hWnd)
{
IsTaskbarButtonCreated = true;
// Apply any settings for this window
var form = ExtendedComponents.FirstOrDefault(f => f.Handle == hWnd);
if (form is null) return;
foreach (var kv in propHash[form])
ApplySetting(form, kv.Value, kv.Key);
}
private void OnThumbnailButtonClick(HWND hWnd, int buttonId)
{
var form = ExtendedComponents.FirstOrDefault(f => f.Handle == hWnd);
if (form is null) return;
var th = GetTaskbarButtonThumbnails(form);
if (th is null) return;
if (buttonId >= 0 && buttonId < th.Toolbar.Buttons.Count)
th.Toolbar.Buttons[buttonId].InvokeClick();
}
private void ResetJumpList(Form form) => propHash[form].Remove("JumpList");
private void ResetTaskbarButtonThumbnails(Form form) => propHash[form].Remove("TaskbarButtonThumbnails");
private bool ShouldSerializeJumpList(Form form) => GetJumpList(form).Count > 0;
private bool ShouldSerializeTaskbarButtonThumbnails(Form form) => GetTaskbarButtonThumbnails(form).Count > 0;
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
private class ThumbButtonMessageFilter : IMessageFilter
{
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == Shell32.WM_TASKBARBUTTONCREATED)
TaskbarButtonCreated(m.HWnd);
else if (m.Msg == (int)User32.WindowMessage.WM_COMMAND && Macros.HIWORD(m.WParam) == Shell32.THBN_CLICKED)
{
ThumbnailButtonClick(m.HWnd, Macros.LOWORD(m.WParam));
return true;
}
return false;
}
}
// TODO: ??: SetActiveAlt, MarkFullscreenWindow
}
}

View File

@ -0,0 +1,127 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
using static Vanara.PInvoke.Shell32;
namespace Vanara.Windows.Shell
{
/// <summary>Specifies taskbar button thumbnail tab properties.</summary>
public enum TaskbarItemTabThumbnailOption
{
/// <summary>The tab window provides a thumbnail and peek image, either live or static as appropriate.</summary>
TabWindow = 0,
/// <summary>
/// Always use the thumbnail or peek image provided by the main application frame window rather than a thumbnail or peek image
/// provided by the individual tab window.
/// </summary>
MainWindow = 1,
/// <summary>
/// When the application tab is active and a live representation of its window is available, use the main application's frame window
/// as the thumbnail or peek feature. At other times, use the tab window thumbnail or peek feature.
/// </summary>
MainWindowWhenActive = 2,
}
//[TypeConverter(typeof(TaskbarItemTabConverter))]
//[Serializable]
/// <summary></summary>
/// <seealso cref="System.ComponentModel.INotifyPropertyChanged"/>
public class TaskbarButtonThumbnail : INotifyPropertyChanged //, ISerializable
{
internal STPFLAG flag = 0;
private Control tabWin;
/// <summary>Initializes a new instance of the <see cref="TaskbarButtonThumbnail"/> class.</summary>
public TaskbarButtonThumbnail()
{
}
/// <summary>Initializes a new instance of the <see cref="TaskbarButtonThumbnail"/> class.</summary>
/// <param name="tabWindow">The tab window.</param>
public TaskbarButtonThumbnail(Control tabWindow) => ChildWindow = tabWindow;
/*private TaskbarItemTab(SerializationInfo info, StreamingContext context)
{
flag = (STPFLAG)info.GetValue(nameof(flag), flag.GetType());
TabWindow = (Control)info.GetValue(nameof(ChildWindow), typeof(Control));
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(ChildWindow), tabWin, typeof(Control));
info.AddValue(nameof(flag), flag, flag.GetType());
}*/
/// <summary>Occurs when a property has changed.</summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Gets or sets the child window whose image will be displayed in this thumbnail.</summary>
/// <value>The child window.</value>
[DefaultValue(null), Category("Appearance")]
public Control ChildWindow
{
get => tabWin;
set
{
if (ReferenceEquals(tabWin, value ?? throw new ArgumentNullException(nameof(ChildWindow), "Child window must be specified for tab.")))
return;
tabWin = value;
//if (Parent != null)
//{
// var idx = Parent.Thumbnails.IndexOf(this);
// var nextTab = (idx < (Parent.Thumbnails.Count - 1)) ? Parent.Thumbnails[idx + 1] : null;
// Register(nextTab);
//}
OnPropertyChanged();
}
}
/// <summary>Gets or sets the peek image provider.</summary>
/// <value>The peek image provider.</value>
[DefaultValue(typeof(TaskbarItemTabThumbnailOption), "TabWindow"), Category("Appearance")]
public TaskbarItemTabThumbnailOption PeekImageProvider
{
get => (TaskbarItemTabThumbnailOption)(((int)flag & 0xC) >> 2);
set { flag = (STPFLAG)(((int)flag & 0x3) | ((int)value << 2)); OnPropertyChanged(); }
}
/// <summary>Gets or sets the thumbnail provider.</summary>
/// <value>The thumbnail provider.</value>
[DefaultValue(typeof(TaskbarItemTabThumbnailOption), "TabWindow"), Category("Appearance")]
public TaskbarItemTabThumbnailOption ThumbnailProvider
{
get => (TaskbarItemTabThumbnailOption)((int)flag & 0x3);
set { flag = (STPFLAG)(((int)flag & 0xC) | (int)value); OnPropertyChanged(); }
}
/// <summary>Called when [property changed].</summary>
/// <param name="propertyName">Name of the property.</param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/*internal class TaskbarItemTabConverter : TypeConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(InstanceDescriptor) || base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == null)
throw new ArgumentNullException(nameof(destinationType));
if (destinationType == typeof(InstanceDescriptor))
{
TaskbarItemTab tab = (TaskbarItemTab)value;
var ci = typeof(TaskbarItemTab).GetConstructor((tab.ChildWindow == null) ? Type.EmptyTypes : new Type[] { typeof(Control) });
if (ci != null)
return new InstanceDescriptor(ci, (tab.ChildWindow == null) ? null : new object[] { tab.ChildWindow }, false);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}*/
}

View File

@ -0,0 +1,109 @@
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
namespace Vanara.Windows.Shell
{
/// <summary>The list of thumbnails to be displayed on the taskbar button.</summary>
/// <seealso cref="System.Collections.ObjectModel.ObservableCollection{T}"/>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class TaskbarButtonThumbnails : ObservableCollection<TaskbarButtonThumbnail>
{
internal IWin32Window parent;
private bool hasAddedButtons = false;
internal TaskbarButtonThumbnails(IWin32Window parent)
{
this.parent = parent;
Toolbar = new ThumbnailToolbar();
Toolbar.PropertyChanged += (s, e) => ResetToolbar();
base.CollectionChanged += LocalCollectionChanged;
}
/// <summary>Gets or sets the toolbar associated with the taskbar button.</summary>
/// <value>The toolbar.</value>
[Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ThumbnailToolbar Toolbar { get; }
/// <summary>Gets the count.</summary>
/// <value>The count.</value>
[Browsable(false)]
public new int Count => base.Count;
internal void ResetToolbar()
{
if (Toolbar?.ImageList != null)
TaskbarList.ThumbBarSetImageList(parent, Toolbar.ImageList);
if (Toolbar?.Buttons?.Count > 0)
{
if (!hasAddedButtons)
{
TaskbarList.ThumbBarAddButtons(parent, Toolbar.Buttons.ToArray());
hasAddedButtons = true;
}
else
TaskbarList.ThumbBarUpdateButtons(parent, Toolbar.Buttons.ToArray());
}
}
private void ActivateThumbnail(TaskbarButtonThumbnail thumbnail)
{
if (parent != null)
TaskbarList.SetTabActive(parent, thumbnail?.ChildWindow ?? throw new ArgumentNullException(nameof(thumbnail), "The TaskbarItemTab.ChildWindow property must be set in order to be activated."));
}
private void LocalCollectionChanged(object _, NotifyCollectionChangedEventArgs e)
{
// If new thumbnails are added, they have to be registered
if (e.NewItems != null)
{
foreach (var item in e.NewItems.Cast<TaskbarButtonThumbnail>())
{
item.PropertyChanged += ThmbChanged;
RegisterThumbnail(item);
}
}
// If new thumbnails are removed, they have to be unregistered
if (e.OldItems != null)
{
foreach (var item in e.OldItems.Cast<TaskbarButtonThumbnail>())
{
item.PropertyChanged -= ThmbChanged;
UnregisterThumbnail(item);
}
}
void ThmbChanged(object s, PropertyChangedEventArgs e) => RefreshThumbnail(s as TaskbarButtonThumbnail);
}
private void RefreshThumbnail(TaskbarButtonThumbnail thumbnail)
{
if (parent != null && thumbnail.ChildWindow != null)
TaskbarList.SetTabProperties(thumbnail.ChildWindow, thumbnail.flag);
}
private void RegisterThumbnail(TaskbarButtonThumbnail thumbnail)
{
var idx = IndexOf(thumbnail);
var nxt = idx < Count - 1 ? this[idx + 1] : null;
if (parent != null && thumbnail.ChildWindow != null)
{
TaskbarList.RegisterTab(parent, thumbnail.ChildWindow);
TaskbarList.SetTabOrder(thumbnail.ChildWindow, nxt?.ChildWindow);
TaskbarList.SetTabProperties(thumbnail.ChildWindow, thumbnail.flag);
}
}
private void UnregisterThumbnail(TaskbarButtonThumbnail thumbnail)
{
if (thumbnail.ChildWindow != null)
TaskbarList.UnregisterTab(thumbnail.ChildWindow);
}
}
}

View File

@ -2,7 +2,10 @@
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Vanara.InteropServices;
using Vanara.PInvoke;
using static Vanara.PInvoke.Ole32;
using static Vanara.PInvoke.PropSys;
using static Vanara.PInvoke.Shell32;
using static Vanara.PInvoke.User32;
@ -45,10 +48,6 @@ namespace Vanara.Windows.Shell
taskbar4 = taskbar2 as ITaskbarList4;
}
/// <summary>Gets the taskbar button created MSG identifier.</summary>
/// <value>The taskbar button created MSG identifier.</value>
public static uint TaskbarButtonCreatedWinMsgId => RegisterWindowMessage("TaskbarButtonCreated");
/// <summary>
/// Activates an item on the taskbar. The window is not actually activated; the window's item on the taskbar is merely displayed as active.
/// </summary>
@ -467,6 +466,33 @@ namespace Vanara.Windows.Shell
taskbar4?.UnregisterTab(hwnd);
}
internal static IPropertyStore GetWindowPropertyStore(HWND hwnd) => SHGetPropertyStoreForWindow<IPropertyStore>(hwnd);
internal static void SetWindowAppId(HWND hwnd, string appId) => SetWindowProperty(hwnd, PROPERTYKEY.System.AppUserModel.ID, appId);
internal static string GetWindowAppId(HWND hwnd) => GetWindowProperty(hwnd, PROPERTYKEY.System.AppUserModel.ID);
internal static void SetWindowProperty(HWND hwnd, PROPERTYKEY propkey, string value)
{
// Get the IPropertyStore for the given window handle
using var pPropStore = ComReleaserFactory.Create(GetWindowPropertyStore(hwnd));
// Set the value
using var pv = new PROPVARIANT(value);
pPropStore.Item.SetValue(propkey, pv);
}
internal static string GetWindowProperty(HWND hwnd, PROPERTYKEY propkey)
{
// Get the IPropertyStore for the given window handle
using var pPropStore = ComReleaserFactory.Create(GetWindowPropertyStore(hwnd));
// Get the value
using var pv = new PROPVARIANT();
pPropStore.Item.GetValue(propkey, pv);
return pv.Value.ToString();
}
private static void Validate7OrLater()
{
if (Environment.OSVersion.Version < Win7Ver)

View File

@ -0,0 +1,41 @@
using System.ComponentModel;
using System.Windows.Forms;
namespace Vanara.Windows.Shell
{
/// <summary>The toolbar associated with thumbnails shown when hovering over an application's taskbar button.</summary>
/// <seealso cref="System.ComponentModel.INotifyPropertyChanged"/>
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ThumbnailToolbar : INotifyPropertyChanged
{
private ImageList _imageList;
/// <summary>Initializes a new instance of the <see cref="ThumbnailToolbar"/> class.</summary>
public ThumbnailToolbar()
{
Buttons = new ThumbnailToolbarButtonCollection();
Buttons.CollectionChanged += (s, e) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Buttons)));
}
/// <summary>Occurs when a property has changed.</summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Gets the buttons.</summary>
/// <value>The buttons.</value>
[Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ThumbnailToolbarButtonCollection Buttons { get; }
/// <summary>Gets or sets the image list for use by the toolbar buttons.</summary>
/// <value>The image list.</value>
public ImageList ImageList
{
get => _imageList;
set
{
if (ReferenceEquals(_imageList, value)) return;
_imageList = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImageList)));
}
}
}
}

View File

@ -0,0 +1,218 @@
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
using Vanara.Extensions;
using static Vanara.PInvoke.Shell32;
namespace Vanara.Windows.Shell
{
/// <summary></summary>
public enum Visibility : byte
{
/// <summary>The collapsed</summary>
Collapsed = 2,
/// <summary>The hidden</summary>
Hidden = 1,
/// <summary>The visible</summary>
Visible = 0
}
/// <summary>A button in the toolbar associated with thumbnails displayed on a taskbar button.</summary>
/// <seealso cref="System.ComponentModel.INotifyPropertyChanged"/>
[DefaultProperty("Description"), DefaultEvent("Click")]
public partial class ThumbnailToolbarButton : INotifyPropertyChanged
{
internal THUMBBUTTON btn;
internal ImageIndexer indexer = new ImageIndexer();
private Icon icon;
private ThumbnailToolbar parent;
private Visibility visibility;
/// <summary>Initializes a new instance of the <see cref="ThumbnailToolbarButton"/> class.</summary>
public ThumbnailToolbarButton()
{
}
/// <summary>Occurs when the button is clicked.</summary>
[Category("Behavior")]
public event EventHandler Click;
/// <summary>Occurs when a property has changed.</summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Gets or sets the description displayed as a tooltip for the button.</summary>
/// <value>The description text.</value>
[DefaultValue(null), Category("Appearance")]
public string Description
{
get => btn.szTip;
set { btn.szTip = value; btn.dwMask |= THUMBBUTTONMASK.THB_TOOLTIP; OnPropertyChanged(); }
}
/// <summary>Gets or sets a value indicating whether to dismiss the thumbnail when this button is clicked.</summary>
/// <value><see langword="true"/> to dismiss when clicked; otherwise, <see langword="false"/>.</value>
[DefaultValue(false), Category("Behavior")]
public bool DismissWhenClicked
{
get => GetFlagValue(THUMBBUTTONFLAGS.THBF_DISMISSONCLICK);
set => SetFlagValue(THUMBBUTTONFLAGS.THBF_DISMISSONCLICK, value);
}
/// <summary>Gets or sets a value indicating whether to draw the button's border.</summary>
/// <value><see langword="true"/> if button border is drawn; otherwise, <see langword="false"/>.</value>
[DefaultValue(true), Category("Appearance")]
public bool DrawButtonBorder
{
get => !GetFlagValue(THUMBBUTTONFLAGS.THBF_NOBACKGROUND);
set => SetFlagValue(THUMBBUTTONFLAGS.THBF_NOBACKGROUND, !value);
}
/// <summary>Gets or sets the icon shown on the toolbar button.</summary>
/// <value>The button icon.</value>
[Description("ButtonImageDescr"), Localizable(true), Category("Appearance")]
[DefaultValue(null)]
public Icon Icon
{
get => icon;
set
{
if (icon != value)
{
icon = value;
if (icon != null)
ImageIndex = -1;
UpdateImageInfo();
OnPropertyChanged();
}
}
}
/// <summary>Gets or sets the index of the image from <see cref="ThumbnailToolbar.ImageList"/>.</summary>
/// <value>The index of the image.</value>
/// <exception cref="ArgumentOutOfRangeException">ImageIndex</exception>
[TypeConverter(typeof(ImageIndexConverter)), Editor("System.Windows.Forms.Design.ImageIndexEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor)), Localizable(true), DefaultValue(-1), RefreshProperties(RefreshProperties.Repaint)]
[Description("ButtonImageIndexDescr"), Category("Appearance")]
public int ImageIndex
{
get
{
if (indexer.Index != -1 && Parent?.ImageList != null && indexer.Index >= Parent.ImageList.Images.Count)
return Parent.ImageList.Images.Count - 1;
return indexer.Index;
}
set
{
if (value < -1)
throw new ArgumentOutOfRangeException(nameof(ImageIndex));
if (indexer.Index != value)
{
if (value != -1)
icon = null;
indexer.Index = value;
UpdateImageInfo();
OnPropertyChanged();
}
}
}
/// <summary>Gets or sets the image key of the image from <see cref="ThumbnailToolbar.ImageList"/>.</summary>
/// <value>The image key value.</value>
[TypeConverter(typeof(ImageKeyConverter)), Editor("System.Windows.Forms.Design.ImageIndexEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor)), Localizable(true), DefaultValue(""), RefreshProperties(RefreshProperties.Repaint)]
[Description("ButtonImageIndexDescr"), Category("Appearance")]
public string ImageKey
{
get => indexer.Key;
set
{
if (indexer.Key != value)
{
if (value != null)
icon = null;
indexer.Key = value;
UpdateImageInfo();
OnPropertyChanged();
}
}
}
/// <summary>Gets or sets a value indicating whether this button is enabled.</summary>
/// <value><see langword="true"/> if this button is enabled; otherwise, <see langword="false"/>.</value>
[DefaultValue(true), Category("Behavior")]
public bool IsEnabled
{
get => !GetFlagValue(THUMBBUTTONFLAGS.THBF_DISABLED);
set => SetFlagValue(THUMBBUTTONFLAGS.THBF_DISABLED, !value);
}
/// <summary>
/// Gets or sets a value indicating whether this button is interactive. If <see langword="false"/>, no pressed button state is
/// drawn. This is intended for instances where the button is used in a notification.
/// </summary>
/// <value><see langword="true"/> if this button is interactive; otherwise, <see langword="false"/>.</value>
[DefaultValue(true), Category("Behavior")]
public bool IsInteractive
{
get => !GetFlagValue(THUMBBUTTONFLAGS.THBF_NONINTERACTIVE);
set => SetFlagValue(THUMBBUTTONFLAGS.THBF_NONINTERACTIVE, !value);
}
/// <summary>Gets or sets the visibility of the button.</summary>
/// <value>The button visibility.</value>
[DefaultValue(typeof(Visibility), "Visible"), Category("Appearance")]
public Visibility Visibility
{
get => visibility;
set { if (visibility != value) { visibility = value; OnPropertyChanged(); } }
}
internal ThumbnailToolbar Parent
{
get => parent;
set
{
parent = value;
indexer.ImageList = value.ImageList;
}
}
internal void InvokeClick() => Click?.Invoke(this, EventArgs.Empty);
/// <summary>Called when a property has changed.</summary>
/// <param name="propName">Name of the property.</param>
protected virtual void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
private bool GetFlagValue(THUMBBUTTONFLAGS f) => btn.dwFlags.IsFlagSet(f);
private void SetFlagValue(THUMBBUTTONFLAGS f, bool value, [CallerMemberName] string memberName = "")
{
btn.dwFlags = btn.dwFlags.SetFlags(f, value);
btn.dwMask |= THUMBBUTTONMASK.THB_FLAGS;
OnPropertyChanged(memberName);
}
private void UpdateImageInfo()
{
if (icon == null && indexer.ActualIndex >= 0)
{
btn.dwMask |= THUMBBUTTONMASK.THB_ICON | THUMBBUTTONMASK.THB_BITMAP;
btn.hIcon = IntPtr.Zero;
btn.iBitmap = (uint)indexer.ActualIndex;
}
else
{
btn.dwMask |= THUMBBUTTONMASK.THB_ICON | THUMBBUTTONMASK.THB_BITMAP;
if (icon == null)
btn.hIcon = IntPtr.Zero;
else
btn.hIcon = icon.Handle;
btn.iBitmap = 0;
}
}
}
}

View File

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using static Vanara.PInvoke.Shell32;
namespace Vanara.Windows.Shell
{
/// <summary>A collection of thumbnail toolbar buttons.</summary>
public class ThumbnailToolbarButtonCollection : ObservableCollection<ThumbnailToolbarButton>
{
private const int maxBtns = 7;
private readonly ThumbnailToolbar Parent;
internal ThumbnailToolbarButtonCollection()
{
CollectionChanged += OnCollectionChanged;
}
/// <summary>Adds a sequence of <see cref="ThumbnailToolbarButton"/> instances to the collection.</summary>
/// <param name="items">The items to add.</param>
public void AddRange(IEnumerable<ThumbnailToolbarButton> items)
{
foreach (var item in items)
Add(item);
}
internal THUMBBUTTON[] ToArray()
{
var ret = new THUMBBUTTON[maxBtns];
for (var i = 0; i < maxBtns; i++)
{
if (i < Count)
ret[i] = this[i].btn;
else
ret[i] = THUMBBUTTON.Default;
ret[i].iId = (uint)i;
}
return ret;
}
/// <summary>Inserts the item into the collection.</summary>
/// <param name="index">The index at which to insert.</param>
/// <param name="item">The item to insert.</param>
protected override void InsertItem(int index, ThumbnailToolbarButton item)
{
if (Count >= maxBtns)
throw new InvalidOperationException($"A maximum of {maxBtns} buttons may be added to a {nameof(ThumbnailToolbarButtonCollection)}.");
item.indexer.ImageList = Parent.ImageList;
base.InsertItem(index, item);
}
/// <summary>Called when the collection has changed.</summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
protected virtual void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e?.NewItems != null)
foreach (ThumbnailToolbarButton tbi in e.NewItems)
tbi.Parent = Parent;
}
}
}

View File

@ -47,38 +47,19 @@ ChangeFilters, ExecutableType, FolderItemFilter, LibraryFolderFilter, LibraryVie
<Compile Remove="_InProgress_\**\*" />
</ItemGroup>
<ItemGroup Condition=" $(TargetFramework.StartsWith('net2')) Or $(TargetFramework.StartsWith('net3')) Or $(TargetFramework.StartsWith('net4')) ">
<Reference Include="System" />
<Reference Include="System.Design" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net35' ">
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup Condition=" $(TargetFramework.StartsWith('netcore')) ">
<PackageReference Include="System.IO.FileSystem.AccessControl">
<Version>4.6.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Compile Remove="TaskBar\~AssocUtil.cs" />
<Compile Remove="TaskBar\~ImageIndexer.cs" />
<Compile Remove="TaskBar\~JumpList.cs" />
<Compile Remove="TaskBar\~TaskbarButton.cs" />
<Compile Remove="TaskBar\~TaskbarButtonThumbnail.cs" />
<Compile Remove="TaskBar\~TaskbarButtonThumbnails.cs" />
<Compile Remove="TaskBar\~ThumbnailToolbar.cs" />
<Compile Remove="TaskBar\~ThumbnailToolbarButton.cs" />
<Compile Remove="TaskBar\~ThumbnailToolbarButtonCollection.cs" />
</ItemGroup>
<ItemGroup>
<None Include="TaskBar\~AssocUtil.cs" />
<None Include="TaskBar\~ImageIndexer.cs" />
<None Include="TaskBar\~JumpList.cs" />
<None Include="TaskBar\~TaskbarButton.cs" />
<None Include="TaskBar\~TaskbarButtonThumbnail.cs" />
<None Include="TaskBar\~TaskbarButtonThumbnails.cs" />
<None Include="TaskBar\~ThumbnailToolbar.cs" />
<None Include="TaskBar\~ThumbnailToolbarButton.cs" />
<None Include="TaskBar\~ThumbnailToolbarButtonCollection.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Vanara.Core.csproj" />
<ProjectReference Include="..\PInvoke\Shared\Vanara.PInvoke.Shared.csproj" />