mirror of https://github.com/dahall/Vanara.git
873 lines
38 KiB
C#
873 lines
38 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.InteropServices.ComTypes;
|
|
using System.Text;
|
|
using Vanara.Extensions;
|
|
using Vanara.InteropServices;
|
|
using Vanara.PInvoke;
|
|
using static Vanara.PInvoke.Gdi32;
|
|
using static Vanara.PInvoke.Kernel32;
|
|
using static Vanara.PInvoke.Ole32;
|
|
using static Vanara.PInvoke.PropSys;
|
|
using static Vanara.PInvoke.Shell32;
|
|
|
|
namespace Vanara.Windows.Shell
|
|
{
|
|
/// <summary>Attributes that can be retrieved on an item (file or folder) or set of items using <see cref="ShellItem.Attributes"/>.</summary>
|
|
[Flags]
|
|
public enum ShellItemAttribute : uint
|
|
{
|
|
/// <summary>The specified items can be copied.</summary>
|
|
CanCopy = 0x00000001,
|
|
|
|
/// <summary>The specified items can be moved.</summary>
|
|
CanMove = 0x00000002,
|
|
|
|
/// <summary>
|
|
/// Shortcuts can be created for the specified items. If a namespace extension returns this attribute, a Create Shortcut entry with a
|
|
/// default handler is added to the shortcut menu that is displayed during drag-and-drop operations. The extension can also implement
|
|
/// its own handler for the link verb in place of the default. If the extension does so, it is responsible for creating the shortcut.
|
|
/// A Create Shortcut item is also added to the Windows Explorer File menu and to normal shortcut menus. If the item is selected,
|
|
/// your application's IContextMenu::InvokeCommand method is invoked with the lpVerb member of the CMINVOKECOMMANDINFO structure set
|
|
/// to link. Your application is responsible for creating the link.
|
|
/// </summary>
|
|
CanLink = 0x00000004,
|
|
|
|
/// <summary>
|
|
/// The specified items can be bound to an IStorage object throughIShellFolder::BindToObject. For more information about namespace
|
|
/// manipulation capabilities, see IStorage.
|
|
/// </summary>
|
|
Storage = 0x00000008,
|
|
|
|
/// <summary>
|
|
/// The specified items can be renamed. Note that this value is essentially a suggestion; not all namespace clients allow items to be
|
|
/// renamed. However, those that do must have this attribute set.
|
|
/// </summary>
|
|
CanRename = 0x00000010,
|
|
|
|
/// <summary>The specified items can be deleted.</summary>
|
|
CanDelete = 0x00000020,
|
|
|
|
/// <summary>The specified items have property sheets.</summary>
|
|
HasPropSheet = 0x00000040,
|
|
|
|
/// <summary>The specified items are drop targets.</summary>
|
|
DropTarget = 0x00000100,
|
|
|
|
/// <summary>
|
|
/// This flag is a mask for the capability attributes: CANCOPY, CANMOVE, CANLINK, CANRENAME, CANDELETE, HASPROPSHEET, and DROPTARGET.
|
|
/// Callers normally do not use this value.
|
|
/// </summary>
|
|
CapabilityMask = 0x00000177,
|
|
|
|
/// <summary>Windows 7 and later. The specified items are system items.</summary>
|
|
System = 0x00001000,
|
|
|
|
/// <summary>The specified items are encrypted and might require special presentation.</summary>
|
|
Encrypted = 0x00002000,
|
|
|
|
/// <summary>
|
|
/// Accessing the item (through IStream or other storage interfaces) is expected to be a slow operation. Applications should avoid
|
|
/// accessing items flagged with ISSLOW. Note: Opening a stream for an item is generally a slow operation at all times. ISSLOW
|
|
/// indicates that it is expected to be especially slow, for example in the case of slow network connections or offline
|
|
/// (FILE_ATTRIBUTE_OFFLINE) files. However, querying ISSLOW is itself a slow operation. Applications should query ISSLOW only on a
|
|
/// background thread. An alternate method, such as retrieving the PKEY_FileAttributes property and testing for
|
|
/// FILE_ATTRIBUTE_OFFLINE, could be used in place of a method call that involves ISSLOW.
|
|
/// </summary>
|
|
IsSlow = 0x00004000,
|
|
|
|
/// <summary>The specified items are shown as dimmed and unavailable to the user.</summary>
|
|
Ghosted = 0x00008000,
|
|
|
|
/// <summary>The specified items are shortcuts.</summary>
|
|
Link = 0x00010000,
|
|
|
|
/// <summary>The specified objects are shared.</summary>
|
|
Share = 0x00020000,
|
|
|
|
/// <summary>
|
|
/// The specified items are read-only. In the case of folders, this means that new items cannot be created in those folders. This
|
|
/// should not be confused with the behavior specified by the FILE_ATTRIBUTE_READONLY flag retrieved by IColumnProvider::GetItemData
|
|
/// in a SHCOLUMNDATAstructure. FILE_ATTRIBUTE_READONLY has no meaning for Win32 file system folders.
|
|
/// </summary>
|
|
ReadOnly = 0x00040000,
|
|
|
|
/// <summary>
|
|
/// The item is hidden and should not be displayed unless the Show hidden files and folders option is enabled in Folder Settings.
|
|
/// </summary>
|
|
Hidden = 0x00080000,
|
|
|
|
/// <summary>Do not use.</summary>
|
|
DisplayAttrMask = 0x000FC000,
|
|
|
|
/// <summary>
|
|
/// The items are non-enumerated items and should be hidden. They are not returned through an enumerator such as that created by
|
|
/// theIShellFolder::EnumObjects method.
|
|
/// </summary>
|
|
NonEnumerated = 0x00100000,
|
|
|
|
/// <summary>The items contain new content, as defined by the particular application.</summary>
|
|
NewContent = 0x00200000,
|
|
|
|
/// <summary>Not supported.</summary>
|
|
CanMoniker = 0x00400000,
|
|
|
|
/// <summary>Not supported.</summary>
|
|
HasStorage = 0x00400000,
|
|
|
|
/// <summary>
|
|
/// Indicates that the item has a stream associated with it. That stream can be accessed through a call to IShellFolder::BindToObject
|
|
/// orIShellItem::BindToHandler with IID_IStream in the riid parameter.
|
|
/// </summary>
|
|
Stream = 0x00400000,
|
|
|
|
/// <summary>Children of this item are accessible through IStream or IStorage. Those children are flagged with STORAGE or STREAM.</summary>
|
|
StorageAncestor = 0x00800000,
|
|
|
|
/// <summary>
|
|
/// When specified as input, VALIDATE instructs the folder to validate that the items contained in a folder or Shell item array
|
|
/// exist. If one or more of those items do not exist, IShellFolder::GetAttributesOf and IShellItemArray::GetAttributes return a
|
|
/// failure code. This flag is never returned as an [out] value. When used with the file system folder, VALIDATE instructs the folder
|
|
/// to discard cached properties retrieved by clients of IShellFolder2::GetDetailsEx that might have accumulated for the specified items.
|
|
/// </summary>
|
|
Validate = 0x01000000,
|
|
|
|
/// <summary>The specified items are on removable media or are themselves removable devices.</summary>
|
|
Removable = 0x02000000,
|
|
|
|
/// <summary>The specified items are compressed.</summary>
|
|
Compressed = 0x04000000,
|
|
|
|
/// <summary>The specified items can be hosted inside a web browser or Windows Explorer frame.</summary>
|
|
Browsable = 0x08000000,
|
|
|
|
/// <summary>
|
|
/// The specified folders are either file system folders or contain at least one descendant (child, grandchild, or later) that is a
|
|
/// file system (FILESYSTEM) folder.
|
|
/// </summary>
|
|
FileSysAncestor = 0x10000000,
|
|
|
|
/// <summary>
|
|
/// The specified items are folders. Some items can be flagged with both STREAM and FOLDER, such as a compressed file with a .zip
|
|
/// file name extension. Some applications might include this flag when testing for items that are both files and containers.
|
|
/// </summary>
|
|
Folder = 0x20000000,
|
|
|
|
/// <summary>
|
|
/// The specified folders or files are part of the file system (that is, they are files, directories, or root directories). The
|
|
/// parsed names of the items can be assumed to be valid Win32 file system paths. These paths can be either UNC or drive-letter based.
|
|
/// </summary>
|
|
FileSystem = 0x40000000,
|
|
|
|
/// <summary>
|
|
/// This flag is a mask for the storage capability attributes: STORAGE, LINK, READONLY, STREAM, STORAGEANCESTOR, FILESYSANCESTOR,
|
|
/// FOLDER, and FILESYSTEM. Callers normally do not use this value.
|
|
/// </summary>
|
|
StorageCapMask = 0x70C50008,
|
|
|
|
/// <summary>
|
|
/// The specified folders have subfolders. The HASSUBFOLDER attribute is only advisory and might be returned by Shell folder
|
|
/// implementations even if they do not contain subfolders. Note, however, that the converse—failing to return
|
|
/// HASSUBFOLDER—definitively states that the folder objects do not have subfolders. Returning HASSUBFOLDER is recommended whenever a
|
|
/// significant amount of time is required to determine whether any subfolders exist. For example, the Shell always returns
|
|
/// HASSUBFOLDER when a folder is located on a network drive.
|
|
/// </summary>
|
|
HasSubfolder = 0x80000000,
|
|
|
|
/// <summary>This flag is a mask for content attributes, at present only HASSUBFOLDER. Callers normally do not use this value.</summary>
|
|
ContentsMask = 0x80000000,
|
|
|
|
/// <summary>
|
|
/// Mask used by the PKEY_SFGAOFlags property to determine attributes that are considered to cause slow calculations or lack context:
|
|
/// ISSLOW, READONLY, HASSUBFOLDER, and VALIDATE. Callers normally do not use this value.
|
|
/// </summary>
|
|
PKEYMask = 0x81044000
|
|
}
|
|
|
|
/// <summary>Used to determine how to compare two Shell items. ShellItem.Compare uses this enumerated type.</summary>
|
|
[Flags]
|
|
public enum ShellItemComparison : uint
|
|
{
|
|
/// <summary>Exact comparison of two instances of a Shell item.</summary>
|
|
AllFields = 0x80000000,
|
|
|
|
/// <summary>Indicates that the comparison is based on a canonical name.</summary>
|
|
Canonical = 0x10000000,
|
|
|
|
/// <summary>Indicates that the comparison is based on the display in a folder view.</summary>
|
|
Display = 0,
|
|
|
|
/// <summary>Windows 7 and later. If the Shell items are not the same, test the file system paths.</summary>
|
|
SecondaryFileSystemPath = 0x20000000
|
|
}
|
|
|
|
/// <summary>Requests the form of an item's display name to retrieve through <see cref="ShellItem.GetDisplayName(ShellItemDisplayString)"/>.</summary>
|
|
public enum ShellItemDisplayString : uint
|
|
{
|
|
/// <summary>Returns the display name relative to the parent folder. In UI this name is generally ideal for display to the user.</summary>
|
|
NormalDisplay = 0x00000000,
|
|
|
|
/// <summary>Returns the parsing name relative to the parent folder. This name is not suitable for use in UI.</summary>
|
|
ParentRelativeParsing = 0x80018001,
|
|
|
|
/// <summary>Returns the parsing name relative to the desktop. This name is not suitable for use in UI.</summary>
|
|
DesktopAbsoluteParsing = 0x80028000,
|
|
|
|
/// <summary>Returns the editing name relative to the parent folder. In UI this name is suitable for display to the user.</summary>
|
|
ParentRelativeEditing = 0x80031001,
|
|
|
|
/// <summary>Returns the editing name relative to the desktop. In UI this name is suitable for display to the user.</summary>
|
|
DesktopAbsoluteEditing = 0x8004c000,
|
|
|
|
/// <summary>
|
|
/// Returns the item's file system path, if it has one. Only items that report SFGAO_FILESYSTEM have a file system path. When an item
|
|
/// does not have a file system path, a call to IShellItem::GetDisplayName on that item will fail. In UI this name is suitable for
|
|
/// display to the user in some cases, but note that it might not be specified for all items.
|
|
/// </summary>
|
|
FileSysPath = 0x80058000,
|
|
|
|
/// <summary>
|
|
/// Returns the item's URL, if it has one. Some items do not have a URL, and in those cases a call to IShellItem::GetDisplayName will
|
|
/// fail. This name is suitable for display to the user in some cases, but note that it might not be specified for all items.
|
|
/// </summary>
|
|
Url = 0x80068000,
|
|
|
|
/// <summary>
|
|
/// Returns the path relative to the parent folder in a friendly format as displayed in an address bar. This name is suitable for
|
|
/// display to the user.
|
|
/// </summary>
|
|
ParentRelativeForAddressBar = 0x8007c001,
|
|
|
|
/// <summary>Returns the path relative to the parent folder.</summary>
|
|
ParentRelative = 0x80080001,
|
|
|
|
/// <summary>Introduced in Windows 8.</summary>
|
|
ParentRelativeForUI = 0x80094001
|
|
}
|
|
|
|
/// <summary>Options for retrieving images from a <see cref="ShellItem"/>.</summary>
|
|
[Flags]
|
|
public enum ShellItemGetImageOptions
|
|
{
|
|
/// <summary>Shrink the bitmap as necessary to fit, preserving its aspect ratio.</summary>
|
|
ResizeToFit = 0x00000000,
|
|
|
|
/// <summary>
|
|
/// Passed by callers if they want to stretch the returned image themselves. For example, if the caller passes an icon size of 80x80,
|
|
/// a 96x96 thumbnail could be returned. This action can be used as a performance optimization if the caller expects that they will
|
|
/// need to stretch the image.
|
|
/// </summary>
|
|
BiggerSizeOk = 0x00000001,
|
|
|
|
/// <summary>
|
|
/// Return the item only if it is already in memory. Do not access the disk even if the item is cached. Note that this only returns
|
|
/// an already-cached icon and can fall back to a per-class icon if an item has a per-instance icon that has not been cached.
|
|
/// Retrieving a thumbnail, even if it is cached, always requires the disk to be accessed, so GetImage should not be called from the
|
|
/// UI thread without passing MemoryOnly.
|
|
/// </summary>
|
|
MemoryOnly = 0x00000002,
|
|
|
|
/// <summary>Return only the icon, never the thumbnail.</summary>
|
|
IconOnly = 0x00000004,
|
|
|
|
/// <summary>
|
|
/// Return only the thumbnail, never the icon. Note that not all items have thumbnails, so ThumbnailOnly will cause the method to
|
|
/// fail in these cases.
|
|
/// </summary>
|
|
ThumbnailOnly = 0x00000008,
|
|
|
|
/// <summary>
|
|
/// Allows access to the disk, but only to retrieve a cached item. This returns a cached thumbnail if it is available. If no cached
|
|
/// thumbnail is available, it returns a cached per-instance icon but does not extract a thumbnail or icon.
|
|
/// </summary>
|
|
InCacheOnly = 0x00000010,
|
|
|
|
/// <summary>Introduced in Windows 8. If necessary, crop the bitmap to a square.</summary>
|
|
CropToSquare = 0x00000020,
|
|
|
|
/// <summary>Introduced in Windows 8. Stretch and crop the bitmap to a 0.7 aspect ratio.</summary>
|
|
WideThumbnails = 0x00000040,
|
|
|
|
/// <summary>
|
|
/// Introduced in Windows 8. If returning an icon, paint a background using the associated app's registered background color.
|
|
/// </summary>
|
|
IconBackground = 0x00000080,
|
|
|
|
/// <summary>Introduced in Windows 8. If necessary, stretch the bitmap so that the height and width fit the given size.</summary>
|
|
ScaleUp = 0x00000100,
|
|
}
|
|
|
|
/// <summary>Flags that direct the handling of the item from which you're retrieving the info tip text.</summary>
|
|
[Flags]
|
|
public enum ShellItemToolTipOptions
|
|
{
|
|
/// <summary>No special handling.</summary>
|
|
Default = 0x00000000,
|
|
|
|
/// <summary>Provide the name of the item in ppwszTip rather than the info tip text.</summary>
|
|
Name = 0x00000001,
|
|
|
|
/// <summary>If the item is a shortcut, retrieve the info tip text of the shortcut rather than its target.</summary>
|
|
LinkNotTarget = 0x00000002,
|
|
|
|
/// <summary>If the item is a shortcut, retrieve the info tip text of the shortcut's target.</summary>
|
|
LinkTarget = 0x00000004,
|
|
|
|
/// <summary>Search the entire namespace for the information. This value can result in a delayed response time.</summary>
|
|
AllowDelay = 0x00000008,
|
|
|
|
/// <summary><c>Windows Vista and later.</c> Put the info tip on a single line.</summary>
|
|
SingleLine = 0x00000010,
|
|
}
|
|
|
|
// TODO: object GetPropertyStoreForKeys(IntPtr rgKeys, uint cKeys, GPS flags, in Guid riid);
|
|
// TODO: object GetPropertyStoreWithCreateObject(GPS flags, object punkCreateObject, in Guid riid);
|
|
/// <summary>Encapsulates an item in the Windows Shell.</summary>
|
|
/// <seealso cref="System.IComparable{ShellItem}"/>
|
|
/// <seealso cref="System.IDisposable"/>
|
|
/// <seealso cref="System.IEquatable{IShellItem}"/>
|
|
/// <seealso cref="System.IEquatable{ShellItem}"/>
|
|
public class ShellItem : IComparable<ShellItem>, IDisposable, IEquatable<IShellItem>, IEquatable<ShellItem>, INotifyPropertyChanged
|
|
{
|
|
internal static readonly bool IsMin7 = Environment.OSVersion.Version >= new Version(6, 1);
|
|
internal static readonly bool IsMinVista = Environment.OSVersion.Version.Major >= 6;
|
|
internal static IBindCtx iBindCtx;
|
|
internal IShellItem iShellItem;
|
|
internal IShellItem2 iShellItem2;
|
|
private static Dictionary<Type, BHID> bhidLookup;
|
|
private PropertyDescriptionList propDescList;
|
|
private ShellItemPropertyStore props;
|
|
private IQueryInfo qi;
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="ShellItem"/> class.</summary>
|
|
/// <param name="path">The file system path of the item.</param>
|
|
public ShellItem(string path) => Init(ShellUtil.GetShellItemForPath(path));
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="ShellItem"/> class.</summary>
|
|
/// <param name="idList">The ID list.</param>
|
|
public ShellItem(PIDL idList)
|
|
{
|
|
if (idList == null || idList.IsInvalid) throw new ArgumentNullException(nameof(idList));
|
|
if (IsMinVista)
|
|
{
|
|
SHCreateItemFromIDList(idList, typeof(IShellItem).GUID, out var obj).ThrowIfFailed();
|
|
Init((IShellItem)obj);
|
|
}
|
|
else
|
|
{
|
|
Init(new ShellItemImpl(idList, false));
|
|
}
|
|
}
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="ShellItem"/> class.</summary>
|
|
/// <param name="si">An existing IShellItem instance.</param>
|
|
protected ShellItem(IShellItem si) => Init(si);
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="ShellItem"/> class.</summary>
|
|
/// <param name="knownFolder">A known folder reference.</param>
|
|
protected ShellItem(KNOWNFOLDERID knownFolder)
|
|
{
|
|
if (IsMin7)
|
|
{
|
|
SHGetKnownFolderItem(knownFolder.Guid(), KNOWN_FOLDER_FLAG.KF_FLAG_DEFAULT, HTOKEN.NULL, typeof(IShellItem).GUID, out var ppv).ThrowIfFailed();
|
|
Init((IShellItem)ppv);
|
|
}
|
|
else
|
|
{
|
|
var csidl = knownFolder.SpecialFolder();
|
|
if (csidl == null) throw new ArgumentOutOfRangeException(nameof(knownFolder), @"Cannot translate this known folder to a value understood by systems prior to Windows 7.");
|
|
var path = new StringBuilder(MAX_PATH);
|
|
SHGetFolderPath(IntPtr.Zero, (int)csidl.Value, HTOKEN.NULL, SHGFP.SHGFP_TYPE_CURRENT, path).ThrowIfFailed();
|
|
Init(ShellUtil.GetShellItemForPath(path.ToString()));
|
|
}
|
|
}
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="ShellItem"/> class.</summary>
|
|
protected ShellItem() { }
|
|
|
|
/// <summary>Occurs when a property value changes.</summary>
|
|
public event PropertyChangedEventHandler PropertyChanged
|
|
{
|
|
add { ((INotifyPropertyChanged)Properties).PropertyChanged += value; }
|
|
remove { ((INotifyPropertyChanged)Properties).PropertyChanged -= value; }
|
|
}
|
|
|
|
/// <summary>Gets the attributes for the Shell item.</summary>
|
|
/// <value>The attributes of the Shell item.</value>
|
|
public ShellItemAttribute Attributes => (ShellItemAttribute)iShellItem.GetAttributes((SFGAO)0xFFFFFFFF);
|
|
|
|
/// <summary>Gets the <see cref="ShellFileInfo"/> corresponding to this instance.</summary>
|
|
public ShellFileInfo FileInfo => IsFileSystem ? new ShellFileInfo(PIDL) : throw new InvalidOperationException("Not file system objects do not have associated ShellFileInfo objects");
|
|
|
|
/// <summary>Gets the file system path if this item is part of the file system.</summary>
|
|
/// <value>The file system path.</value>
|
|
public string FileSystemPath => GetDisplayName(SIGDN.SIGDN_FILESYSPATH);
|
|
|
|
/// <summary>Gets a value indicating whether this instance is part of the file system.</summary>
|
|
/// <value><c>true</c> if this instance is part of the file system; otherwise, <c>false</c>.</value>
|
|
public bool IsFileSystem => iShellItem.GetAttributes(SFGAO.SFGAO_FILESYSTEM) != 0;
|
|
|
|
/// <summary>Gets a value indicating whether this instance is folder.</summary>
|
|
/// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value>
|
|
public bool IsFolder => iShellItem.GetAttributes(SFGAO.SFGAO_FOLDER) != 0;
|
|
|
|
/// <summary>Gets the IShellItem instance of the current ShellItem.</summary>
|
|
public IShellItem IShellItem => iShellItem;
|
|
|
|
/// <summary>Gets a value indicating whether this instance is link.</summary>
|
|
/// <value><c>true</c> if this instance is link; otherwise, <c>false</c>.</value>
|
|
public bool IsLink => iShellItem.GetAttributes(SFGAO.SFGAO_LINK) != 0;
|
|
|
|
/// <summary>Gets the name relative to the parent for the item.</summary>
|
|
public virtual string Name
|
|
{
|
|
get => GetDisplayName(SIGDN.SIGDN_NORMALDISPLAY);
|
|
protected set { }
|
|
}
|
|
|
|
/// <summary>Gets the parent for the current item.</summary>
|
|
/// <value>The parent item. If this is the desktop, this property will return <c>null</c>.</value>
|
|
public ShellFolder Parent
|
|
{
|
|
get
|
|
{
|
|
try { return new ShellFolder(iShellItem.GetParent()); } catch { }
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets a string that can be used to parse an absolute value from the Desktop.</summary>
|
|
/// <value>A parsable name for the item.</value>
|
|
public string ParsingName => GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING);
|
|
|
|
/// <summary>Gets the item's ID list.</summary>
|
|
/// <value>The ID list for the item.</value>
|
|
public PIDL PIDL
|
|
{
|
|
get
|
|
{
|
|
SHGetIDListFromObject(iShellItem, out var pidl).ThrowIfFailed();
|
|
return pidl;
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets the property store for the item.</summary>
|
|
/// <value>The dictionary of properties.</value>
|
|
public ShellItemPropertyStore Properties => props ?? (props = new ShellItemPropertyStore(this));
|
|
|
|
/// <summary>Gets a property description list object containing descriptions of all properties.</summary>
|
|
/// <returns>A complete <see cref="PropertyDescriptionList"/> instance.</returns>
|
|
public PropertyDescriptionList PropertyDescriptions => propDescList ?? (propDescList = GetPropertyDescriptionList(PROPERTYKEY.System.PropList.FullDetails));
|
|
|
|
/// <summary>Gets the normal tool tip text associated with this item.</summary>
|
|
/// <value>The tool tip text.</value>
|
|
public string ToolTipText => GetToolTip();
|
|
|
|
/// <summary>Gets the system bind context.</summary>
|
|
/// <value>The bind context.</value>
|
|
protected static IBindCtx BindContext
|
|
{
|
|
get
|
|
{
|
|
if (iBindCtx == null)
|
|
CreateBindCtx(0, out iBindCtx);
|
|
return iBindCtx;
|
|
}
|
|
}
|
|
|
|
/// <summary>Creates the most specialized derivative of ShellItem from an IShellItem object.</summary>
|
|
/// <param name="iItem">The IShellItem object.</param>
|
|
/// <returns>A ShellItem derivative for the supplied IShellItem.</returns>
|
|
public static ShellItem Open(IShellItem iItem)
|
|
{
|
|
if (iItem.GetAttributes(SFGAO.SFGAO_LINK) != 0)
|
|
return new ShellLink(iItem);
|
|
|
|
// If not a folder, get the ShellItem
|
|
if (iItem.GetAttributes(SFGAO.SFGAO_FOLDER) == 0)
|
|
return new ShellItem(iItem);
|
|
|
|
// Try to get specialized folder type from property
|
|
var pk = PROPERTYKEY.System.ItemType;
|
|
string itemType = null;
|
|
try { itemType = (iItem as IShellItem2)?.GetString(pk)?.ToString().ToLowerInvariant(); } catch { }
|
|
switch (itemType)
|
|
{
|
|
case ".library-ms":
|
|
return new ShellLibrary(iItem);
|
|
|
|
case ".searchconnector-ms":
|
|
// TODO: Return a search connector
|
|
case ".search-ms":
|
|
// TODO: Return a saved search connection
|
|
default:
|
|
return new ShellFolder(iItem);
|
|
}
|
|
}
|
|
|
|
/// <summary>Implements the operator !=.</summary>
|
|
/// <param name="left">The left operand.</param>
|
|
/// <param name="right">The right operand.</param>
|
|
/// <returns>The result of the operator.</returns>
|
|
public static bool operator !=(ShellItem left, ShellItem right) => !(left == right);
|
|
|
|
/// <summary>Implements the operator ==.</summary>
|
|
/// <param name="left">The left operand.</param>
|
|
/// <param name="right">The right operand.</param>
|
|
/// <returns>The result of the operator.</returns>
|
|
public static bool operator ==(ShellItem left, ShellItem right) => Equals(left?.iShellItem, right?.iShellItem);
|
|
|
|
/// <summary>
|
|
/// Compares the current instance with another object of the same type and returns an integer that indicates whether the current
|
|
/// instance precedes, follows, or occurs in the same position in the sort order as the other object.
|
|
/// </summary>
|
|
/// <param name="other">An object to compare with this instance.</param>
|
|
/// <param name="hint">Optional hint value that determines how to perform the comparison. The default compares all fields.</param>
|
|
/// <returns>
|
|
/// A value that indicates the relative order of the objects being compared. If the two items are the same this parameter equals
|
|
/// zero; if they are different the parameter is nonzero.
|
|
/// </returns>
|
|
public int CompareTo(ShellItem other, ShellItemComparison hint = ShellItemComparison.SecondaryFileSystemPath) => iShellItem.Compare(other?.iShellItem, (SICHINTF)hint);
|
|
|
|
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
|
public virtual void Dispose()
|
|
{
|
|
if (props != null) { props?.Dispose(); props = null; }
|
|
if (propDescList != null) { propDescList?.Dispose(); propDescList = null; }
|
|
if (qi != null) { Marshal.ReleaseComObject(qi); qi = null; }
|
|
if (iShellItem2 != null) { Marshal.ReleaseComObject(iShellItem2); iShellItem2 = null; }
|
|
if (iShellItem != null) { Marshal.ReleaseComObject(iShellItem); iShellItem = null; }
|
|
}
|
|
|
|
/// <summary>Determines whether the specified <see cref="System.Object"/>, is equal to this instance.</summary>
|
|
/// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
|
|
/// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.</returns>
|
|
public override bool Equals(object obj) => Equals(iShellItem, obj as IShellItem);
|
|
|
|
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
|
|
/// <param name="other">An object to compare with this object.</param>
|
|
/// <returns>true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.</returns>
|
|
public bool Equals(IShellItem other) => Equals(iShellItem, other);
|
|
|
|
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
|
|
/// <param name="other">An object to compare with this object.</param>
|
|
/// <returns>true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.</returns>
|
|
public bool Equals(ShellItem other) => Equals(iShellItem, other?.iShellItem);
|
|
|
|
/// <summary>Gets a formatted display name for this item.</summary>
|
|
/// <param name="option">The formatting options.</param>
|
|
/// <returns>A string with the formatted display name if successful; otherwise <c>null</c>.</returns>
|
|
public string GetDisplayName(ShellItemDisplayString option) => GetDisplayName((SIGDN)option);
|
|
|
|
/// <summary>Gets a handler interface.</summary>
|
|
/// <typeparam name="TInterface">The interface of the handler to return.</typeparam>
|
|
/// <param name="handler">The bind handler to retrieve.</param>
|
|
/// <returns>The requested interface.</returns>
|
|
public TInterface GetHandler<TInterface>(BHID handler = 0) where TInterface : class
|
|
{
|
|
if (handler == 0)
|
|
handler = GetBHIDForInterface<TInterface>();
|
|
if (handler == 0)
|
|
throw new ArgumentOutOfRangeException(nameof(handler));
|
|
return iShellItem.BindToHandler(BindContext, handler.Guid(), typeof(TInterface).GUID) as TInterface;
|
|
}
|
|
|
|
/// <summary>Returns a hash code for this instance.</summary>
|
|
/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
|
|
public override int GetHashCode() => GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING).GetHashCode();
|
|
|
|
/// <summary>
|
|
/// Gets an image that represents this item. The default behavior is to load a thumbnail. If there is no thumbnail for the current
|
|
/// item, it retrieves the icon of the item. The thumbnail or icon is extracted if it is not currently cached.
|
|
/// </summary>
|
|
/// <param name="size">A structure that specifies the size of the image to be received.</param>
|
|
/// <param name="flags">One or more of the option flags.</param>
|
|
/// <returns>The resulting image.</returns>
|
|
/// <exception cref="PlatformNotSupportedException"></exception>
|
|
public Image GetImage(Size size, ShellItemGetImageOptions flags)
|
|
{
|
|
if (!IsMinVista) throw new PlatformNotSupportedException();
|
|
if (iShellItem is IShellItemImageFactory fctry)
|
|
{
|
|
var hres = fctry.GetImage(size, (SIIGBF)flags, out var hbitmap);
|
|
if (hres == 0x8004B200 && flags.IsFlagSet(ShellItemGetImageOptions.ThumbnailOnly))
|
|
throw new InvalidOperationException("Thumbnails are not supported by this item.");
|
|
hres.ThrowIfFailed();
|
|
//Marshal.ReleaseComObject(fctry);
|
|
return GetTransparentBitmap(hbitmap);
|
|
}
|
|
if (!flags.IsFlagSet(ShellItemGetImageOptions.IconOnly))
|
|
return GetThumbnail(size.Width);
|
|
throw new InvalidOperationException("Unable to retrieve an image for this item.");
|
|
}
|
|
|
|
/// <summary>Gets a property description list object given a reference to a property key.</summary>
|
|
/// <param name="keyType">
|
|
/// A reference to a PROPERTYKEY structure. The values in <see cref="PROPERTYKEY.System.PropList"/> are all valid.
|
|
/// <see cref="PROPERTYKEY.System.PropList.FullDetails"/> will return all properties.
|
|
/// </param>
|
|
/// <returns>A <see cref="PropertyDescriptionList"/> instance for the supplied key.</returns>
|
|
public PropertyDescriptionList GetPropertyDescriptionList(PROPERTYKEY keyType)
|
|
{
|
|
ThrowIfNoShellItem2();
|
|
return new PropertyDescriptionList(iShellItem2.GetPropertyDescriptionList(keyType, typeof(IPropertyDescriptionList).GUID));
|
|
}
|
|
|
|
/// <summary>Gets the formatted tool tip text associated with this item.</summary>
|
|
/// <param name="options">The option flags.</param>
|
|
/// <returns>The tool tip text formatted as per <paramref name="options"/>.</returns>
|
|
public string GetToolTip(ShellItemToolTipOptions options = ShellItemToolTipOptions.Default)
|
|
{
|
|
if (qi == null)
|
|
try
|
|
{
|
|
qi = (Parent ?? ShellFolder.Desktop).GetChildrenUIObjects<IQueryInfo>(null, this);
|
|
}
|
|
catch { }
|
|
if (qi == null) return "";
|
|
qi.GetInfoTip((QITIP)options, out var ret);
|
|
return ret ?? "";
|
|
}
|
|
|
|
/// <summary>Returns a <see cref="System.String"/> that represents this instance.</summary>
|
|
/// <returns>A <see cref="System.String"/> that represents this instance.</returns>
|
|
public override string ToString() => Name;
|
|
|
|
/// <summary>Ensures that all cached information for this item is updated.</summary>
|
|
public void Update()
|
|
{
|
|
props?.Commit();
|
|
ThrowIfNoShellItem2();
|
|
iShellItem2.Update(BindContext);
|
|
}
|
|
|
|
/// <summary>Open a new Windows Explorer window with this item selected.</summary>
|
|
public void ViewInExplorer() => SHOpenFolderAndSelectItems(Parent.PIDL, 1, new IntPtr[] { (IntPtr)PIDL }, OFASI.OFASI_NONE);
|
|
|
|
/// <summary>
|
|
/// Compares the current instance with another object of the same type and returns an integer that indicates whether the current
|
|
/// instance precedes, follows, or occurs in the same position in the sort order as the other object.
|
|
/// </summary>
|
|
/// <param name="other">An object to compare with this instance.</param>
|
|
/// <returns>
|
|
/// A value that indicates the relative order of the objects being compared. If the two items are the same this parameter equals
|
|
/// zero; if they are different the parameter is nonzero.
|
|
/// </returns>
|
|
int IComparable<ShellItem>.CompareTo(ShellItem other) => CompareTo(other);
|
|
|
|
/// <summary>Gets the BHID for the supplied <typeparamref name="TInterface"/>.</summary>
|
|
/// <typeparam name="TInterface">The type of the interface to lookup.</typeparam>
|
|
/// <returns>The related BHID if found, 0 if not.</returns>
|
|
internal static BHID GetBHIDForInterface<TInterface>()
|
|
{
|
|
if (bhidLookup == null)
|
|
bhidLookup = new Dictionary<Type, BHID>
|
|
{
|
|
//{ typeof(??), BHID.BHID_SFObject },
|
|
{ typeof(IShellLinkW), BHID.BHID_SFUIObject },
|
|
//{ typeof(Others??), BHID.BHID_SFUIObject },
|
|
//{ typeof(??), BHID.BHID_SFViewObject },
|
|
{ typeof(IStorage), BHID.BHID_Storage },
|
|
{ typeof(IStream), BHID.BHID_Stream },
|
|
//{ typeof(IShellItem??), BHID.BHID_LinkTargetItem },
|
|
//{ typeof(IEnumShellItems), BHID.BHID_StorageEnum }, // Can't have multiple keys
|
|
// TODO: { typeof(ITransferSource), BHID.BHID_Transfer },
|
|
// TODO: { typeof(ITransferDestination), BHID.BHID_Transfer },
|
|
{ typeof(IPropertyStore), BHID.BHID_PropertyStore },
|
|
// TODO: { typeof(IPropertyStoreFactory), BHID.BHID_PropertyStore },
|
|
{ typeof(IExtractImage), BHID.BHID_ThumbnailHandler },
|
|
{ typeof(IThumbnailProvider), BHID.BHID_ThumbnailHandler },
|
|
{ typeof(IEnumShellItems), BHID.BHID_EnumItems },
|
|
{ typeof(IDataObject), BHID.BHID_DataObject },
|
|
// TODO: { typeof(IQueryAssociations), BHID.BHID_AssociationArray },
|
|
// TODO: { typeof(IFilter), BHID.BHID_Filter },
|
|
// TODO: { typeof(IEnumAssocHandlers), BHID.BHID_EnumAssocHandlers },
|
|
// TODO: { typeof(IRandomAccessStream), BHID.BHID_RandomAccessStream },
|
|
//{ typeof(??), BHID.BHID_FilePlaceholder },
|
|
};
|
|
return bhidLookup.TryGetValue(typeof(TInterface), out var value) ? value : 0;
|
|
}
|
|
|
|
internal static string GetStringValue(Action<StringBuilder, int> method, int buffSize = MAX_PATH)
|
|
{
|
|
while (true)
|
|
{
|
|
var ret = new StringBuilder(buffSize, buffSize);
|
|
try { method(ret, ret.Capacity); }
|
|
catch (COMException ex) { if (ex.ErrorCode == unchecked((int)0x8007007A) || ex.ErrorCode == unchecked((int)0x800700EA) || buffSize <= 8192) buffSize *= 2; else throw ex; }
|
|
return ret.ToString();
|
|
}
|
|
}
|
|
|
|
/// <summary>Throws an exception if no IShellItem2 instance can be retrieved.</summary>
|
|
internal void ThrowIfNoShellItem2()
|
|
{
|
|
if (iShellItem2 == null)
|
|
throw new InvalidOperationException("Unable to get access to this detail.");
|
|
}
|
|
|
|
/// <summary>Enumerates all the children of the current item. Not valid before Vista.</summary>
|
|
/// <returns>An enumeration of the child objects.</returns>
|
|
protected virtual IEnumerable<ShellItem> EnumerateChildren()
|
|
{
|
|
if (!IsMinVista) yield break;
|
|
IEnumShellItems ie = null;
|
|
try
|
|
{
|
|
ie = GetHandler<IEnumShellItems>(BHID.BHID_EnumItems);
|
|
}
|
|
catch (Exception e) { Debug.WriteLine($"Unable to enum children: {e.Message}"); }
|
|
if (ie != null)
|
|
{
|
|
var a = new IShellItem[1];
|
|
while (ie.Next(1, a, out var f).Succeeded && f == 1)
|
|
{
|
|
ShellItem i = null;
|
|
try { i = Open(a[0]); } catch (Exception e) { Debug.WriteLine($"Unable to open child: {e.Message}"); }
|
|
if (i != null) yield return i;
|
|
}
|
|
Marshal.ReleaseComObject(ie);
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets the display name.</summary>
|
|
/// <param name="dn">The display name option.</param>
|
|
/// <returns>The display name.</returns>
|
|
protected virtual string GetDisplayName(SIGDN dn)
|
|
{
|
|
try { return iShellItem?.GetDisplayName(dn); } catch { }
|
|
return null;
|
|
}
|
|
|
|
/// <summary>Initializes this instance with the specified IShellItem.</summary>
|
|
/// <param name="si">The IShellItem object.</param>
|
|
protected void Init(IShellItem si)
|
|
{
|
|
iShellItem = si ?? throw new ArgumentNullException(nameof(si));
|
|
iShellItem2 = si as IShellItem2;
|
|
}
|
|
|
|
private static bool Equals(IShellItem left, IShellItem right)
|
|
{
|
|
if (ReferenceEquals(left, right)) return true;
|
|
if (left is null || right is null) return false;
|
|
return left.Compare(right, SICHINTF.SICHINT_TEST_FILESYSPATH_IF_NOT_EQUAL) == 0;
|
|
}
|
|
|
|
private static Bitmap GetTransparentBitmap(HBITMAP hbitmap)
|
|
{
|
|
var dibsection = GetObject<DIBSECTION>(hbitmap);
|
|
var bitmap = new Bitmap(dibsection.dsBm.bmWidth, dibsection.dsBm.bmHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
using (var mstr = new MarshalingStream(dibsection.dsBm.bmBits, (long)dibsection.dsBm.bmBits))
|
|
{
|
|
for (var x = 0; x < dibsection.dsBmih.biWidth; x++)
|
|
for (var y = 0; y < dibsection.dsBmih.biHeight; y++)
|
|
{
|
|
var rgbquad = mstr.Read<RGBQUAD>();
|
|
if (rgbquad.rgbReserved != 0)
|
|
bitmap.SetPixel(x, y, rgbquad.Color);
|
|
}
|
|
}
|
|
return bitmap;
|
|
}
|
|
|
|
/// <summary>Gets the thumbnail image for the item using the specified characteristics.</summary>
|
|
/// <param name="width">The width, in pixels, of the Bitmap.</param>
|
|
/// <returns>The resulting Bitmap, on success, or <c>null</c> on failure.</returns>
|
|
private Image GetThumbnail(int width = 32)
|
|
{
|
|
IThumbnailProvider provider = null;
|
|
try
|
|
{
|
|
provider = GetHandler<IThumbnailProvider>(BHID.BHID_ThumbnailHandler);
|
|
if (provider == null) return null;
|
|
provider.GetThumbnail((uint)width, out var hbmp, out var alpha);
|
|
return hbmp.ToBitmap();
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
finally
|
|
{
|
|
if (provider != null) Marshal.ReleaseComObject(provider);
|
|
}
|
|
}
|
|
|
|
protected class ShellItemImpl : IDisposable, IShellItem
|
|
{
|
|
public ShellItemImpl(PIDL pidl, bool owner) => PIDL = owner ? pidl : new PIDL(pidl);
|
|
|
|
private PIDL PIDL { get; set; }
|
|
|
|
[return: MarshalAs(UnmanagedType.Interface)]
|
|
public object BindToHandler(IBindCtx pbc, in Guid bhid, in Guid riid)
|
|
{
|
|
if (riid == typeof(IShellFolder).GUID)
|
|
return Marshal.GetIUnknownForObject(GetIShellFolder());
|
|
throw new InvalidCastException();
|
|
}
|
|
|
|
public int Compare(IShellItem psi, SICHINTF hint)
|
|
{
|
|
var other = (ShellItemImpl)psi;
|
|
var p1 = InternalGetParent();
|
|
var p2 = other.InternalGetParent();
|
|
if (p1.PIDL.Equals(p2.PIDL))
|
|
return p1.GetIShellFolder().CompareIDs((IntPtr)(int)hint, PIDL.LastId, other.PIDL.LastId).Code;
|
|
return 1;
|
|
}
|
|
|
|
public void Dispose() => PIDL = null;
|
|
|
|
public SFGAO GetAttributes(SFGAO sfgaoMask)
|
|
{
|
|
var parentFolder = InternalGetParent().GetIShellFolder();
|
|
var result = sfgaoMask;
|
|
parentFolder.GetAttributesOf(1, new[] { (IntPtr)PIDL.LastId }, ref result);
|
|
return result & sfgaoMask;
|
|
}
|
|
|
|
public SafeCoTaskMemString GetDisplayName(SIGDN sigdnName)
|
|
{
|
|
if (sigdnName == SIGDN.SIGDN_FILESYSPATH)
|
|
{
|
|
var result = new StringBuilder(512);
|
|
if (!SHGetPathFromIDList(PIDL, result))
|
|
throw new ArgumentException();
|
|
return new SafeCoTaskMemString(result.ToString(), CharSet.Unicode, false);
|
|
}
|
|
|
|
var parentFolder = InternalGetParent().GetIShellFolder();
|
|
var child = PIDL.LastId;
|
|
return new SafeCoTaskMemString(parentFolder.GetDisplayNameOf(child, (SHGDNF)((int)sigdnName & 0xffff)), CharSet.Unicode, false);
|
|
}
|
|
|
|
public IShellItem GetParent()
|
|
{
|
|
var pidlCopy = new PIDL(PIDL);
|
|
if (!pidlCopy.RemoveLastId())
|
|
Marshal.ThrowExceptionForHR((int)new HRESULT(HRESULT.MK_E_NOOBJECT));
|
|
return new ShellItemImpl(pidlCopy, true);
|
|
}
|
|
|
|
private IShellFolder GetIShellFolder()
|
|
{
|
|
SHGetFolderLocation(IntPtr.Zero, 0, HTOKEN.NULL, 0, out var dtPidl);
|
|
if (ShellFolder.Desktop.PIDL.Equals(dtPidl))
|
|
return ShellFolder.Desktop.iShellFolder;
|
|
return (IShellFolder)ShellFolder.Desktop.iShellFolder.BindToObject(PIDL, null, typeof(IShellFolder).GUID);
|
|
}
|
|
|
|
private ShellItemImpl InternalGetParent()
|
|
{
|
|
var pidlCopy = new PIDL(PIDL);
|
|
return pidlCopy.RemoveLastId() ? new ShellItemImpl(pidlCopy, true) : this;
|
|
}
|
|
}
|
|
}
|
|
} |