mirror of https://github.com/dahall/Vanara.git
Finished intial work on TaskbarButton form extender that fully enables all interactions with the taskbar button in the UI.
parent
129a8247aa
commit
ae3d4dc52c
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,10 @@
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using Vanara.InteropServices;
|
||||||
using Vanara.PInvoke;
|
using Vanara.PInvoke;
|
||||||
|
using static Vanara.PInvoke.Ole32;
|
||||||
|
using static Vanara.PInvoke.PropSys;
|
||||||
using static Vanara.PInvoke.Shell32;
|
using static Vanara.PInvoke.Shell32;
|
||||||
using static Vanara.PInvoke.User32;
|
using static Vanara.PInvoke.User32;
|
||||||
|
|
||||||
|
@ -45,10 +48,6 @@ namespace Vanara.Windows.Shell
|
||||||
taskbar4 = taskbar2 as ITaskbarList4;
|
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>
|
/// <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.
|
/// 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>
|
/// </summary>
|
||||||
|
@ -467,6 +466,33 @@ namespace Vanara.Windows.Shell
|
||||||
taskbar4?.UnregisterTab(hwnd);
|
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()
|
private static void Validate7OrLater()
|
||||||
{
|
{
|
||||||
if (Environment.OSVersion.Version < Win7Ver)
|
if (Environment.OSVersion.Version < Win7Ver)
|
||||||
|
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,38 +47,19 @@ ChangeFilters, ExecutableType, FolderItemFilter, LibraryFolderFilter, LibraryVie
|
||||||
<Compile Remove="_InProgress_\**\*" />
|
<Compile Remove="_InProgress_\**\*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('net2')) Or $(TargetFramework.StartsWith('net3')) Or $(TargetFramework.StartsWith('net4')) ">
|
<ItemGroup Condition=" $(TargetFramework.StartsWith('net2')) Or $(TargetFramework.StartsWith('net3')) Or $(TargetFramework.StartsWith('net4')) ">
|
||||||
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Design" />
|
<Reference Include="System.Design" />
|
||||||
<Reference Include="System.Drawing" />
|
<Reference Include="System.Drawing" />
|
||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
<Reference Include="System" />
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition=" '$(TargetFramework)' == 'net35' ">
|
||||||
|
<Reference Include="WindowsBase" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('netcore')) ">
|
<ItemGroup Condition=" $(TargetFramework.StartsWith('netcore')) ">
|
||||||
<PackageReference Include="System.IO.FileSystem.AccessControl">
|
<PackageReference Include="System.IO.FileSystem.AccessControl">
|
||||||
<Version>4.6.0</Version>
|
<Version>4.6.0</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</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>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Core\Vanara.Core.csproj" />
|
<ProjectReference Include="..\Core\Vanara.Core.csproj" />
|
||||||
<ProjectReference Include="..\PInvoke\Shared\Vanara.PInvoke.Shared.csproj" />
|
<ProjectReference Include="..\PInvoke\Shared\Vanara.PInvoke.Shared.csproj" />
|
||||||
|
|
Loading…
Reference in New Issue