From b36a8e72ce5fe333df2324d411f2df2607b5797d Mon Sep 17 00:00:00 2001 From: dahall Date: Mon, 7 Dec 2020 19:50:48 -0700 Subject: [PATCH] Added access to handlers for ShellAssociation #181 --- Windows.Shell/Registration/ShellAssociation.cs | 232 +++++++++++++++++++++---- 1 file changed, 196 insertions(+), 36 deletions(-) diff --git a/Windows.Shell/Registration/ShellAssociation.cs b/Windows.Shell/Registration/ShellAssociation.cs index 4721d2e8..c9a1ac2a 100644 --- a/Windows.Shell/Registration/ShellAssociation.cs +++ b/Windows.Shell/Registration/ShellAssociation.cs @@ -1,7 +1,11 @@ using Microsoft.Win32.SafeHandles; using System; using System.Collections.Generic; +using System.Linq; using System.Text; +using Vanara.InteropServices; +using Vanara.PInvoke; +using static Vanara.PInvoke.Shell32; using static Vanara.PInvoke.ShlwApi; namespace Vanara.Windows.Shell @@ -34,6 +38,12 @@ namespace Vanara.Windows.Shell /// public string AppPublisher => GetString(ASSOCSTR.ASSOCSTR_APPPUBLISHER); + /// + /// Introduced in Internet Explorer 6. Describes a general type of MIME file association, such as image and bmp, so that + /// applications can make general assumptions about a specific file type. + /// + public string ContentType => GetString(ASSOCSTR.ASSOCSTR_CONTENTTYPE); + /// /// Introduced in Internet Explorer 6. Returns the path to the icon resources to use by default for this association. Positive /// numbers indicate an index into the dll's resource table, while negative numbers indicate a resource ID. An example of the syntax @@ -50,6 +60,20 @@ namespace Vanara.Windows.Shell /// The friendly name of a document type. public string FriendlyDocName => GetString(ASSOCSTR.ASSOCSTR_FRIENDLYDOCNAME); + /// Gets a list of file name extension handlers. + /// The handlers for this association. + public IReadOnlyList Handlers + { + get + { + if (SHAssocEnumHandlers(Extension, ASSOC_FILTER.ASSOC_FILTER_NONE, out var ieah).Failed) + return (IReadOnlyList)new List(); + using var pieah = ComReleaserFactory.Create(ieah); + var e = new Vanara.Collections.IEnumFromCom(ieah.Next, () => { }); + return (IReadOnlyList)e.Select(i => new ShellAssociationHandler(i)).ToList(); + } + } + /// /// Corresponds to the InfoTip registry value. Returns an info tip for an item, or list of properties in the form of an /// IPropertyDescriptionList from which to create an info tip, such as when hovering the cursor over a file name. The list of @@ -62,6 +86,15 @@ namespace Vanara.Windows.Shell /// public ProgId ProgId => ProgId.Open(GetString(ASSOCSTR.ASSOCSTR_PROGID), true, true, true); + /// + /// Introduced in Internet Explorer 6. Corresponds to the QuickTip registry value. Same as ASSOCSTR_INFOTIP, except that it always + /// returns a list of property names in the form of an IPropertyDescriptionList. The difference between this value and + /// ASSOCSTR_INFOTIP is that this returns properties that are safe for any scenario that causes slow property retrieval, such as + /// offline or slow networks. Some of the properties returned from ASSOCSTR_INFOTIP might not be appropriate for slow property + /// retrieval scenarios. The list of properties can be parsed with PSGetPropertyDescriptionListFromString. + /// + public string QuickTip => GetString(ASSOCSTR.ASSOCSTR_QUICKTIP); + /// /// Introduced in Internet Explorer 6. For an object that has a Shell extension associated with it, you can use this to retrieve the /// CLSID of that Shell extension object by passing a string representation of the IID of the interface you want to retrieve as the @@ -73,27 +106,6 @@ namespace Vanara.Windows.Shell /// Introduced in Windows 8. public Guid? SupportedUriProtocols { get { try { return new Guid(GetString(ASSOCSTR.ASSOCSTR_SUPPORTED_URI_PROTOCOLS)); } catch { return null; } } } - /// Gets the command verbs for this file association. - /// Returns a value. - public IReadOnlyDictionary Verbs => throw new NotImplementedException(); // TODO - - #region AllPropLists // TODO: Enhance - - /// - /// Introduced in Internet Explorer 6. Describes a general type of MIME file association, such as image and bmp, so that applications - /// can make general assumptions about a specific file type. - /// - public string ContentType => GetString(ASSOCSTR.ASSOCSTR_CONTENTTYPE); - - /// - /// Introduced in Internet Explorer 6. Corresponds to the QuickTip registry value. Same as ASSOCSTR_INFOTIP, except that it always - /// returns a list of property names in the form of an IPropertyDescriptionList. The difference between this value and - /// ASSOCSTR_INFOTIP is that this returns properties that are safe for any scenario that causes slow property retrieval, such as - /// offline or slow networks. Some of the properties returned from ASSOCSTR_INFOTIP might not be appropriate for slow property - /// retrieval scenarios. The list of properties can be parsed with PSGetPropertyDescriptionListFromString. - /// - public string QuickTip => GetString(ASSOCSTR.ASSOCSTR_QUICKTIP); - /// /// Introduced in Internet Explorer 6. Corresponds to the TileInfo registry value. Contains a list of properties to be displayed for /// a particular file type in a Windows Explorer window that is in tile view. This is the same as ASSOCSTR_INFOTIP, but, like @@ -102,7 +114,9 @@ namespace Vanara.Windows.Shell /// public string TileInfo => GetString(ASSOCSTR.ASSOCSTR_TILEINFO); - #endregion AllPropLists // TODO: Enhance + /// Gets the command verbs for this file association. + /// Returns a value. + public IReadOnlyDictionary Verbs => throw new NotImplementedException(); // TODO /// Initializes a new instance of the class based on the supplied executable name. /// The full path of the application executable. @@ -114,7 +128,9 @@ namespace Vanara.Windows.Shell /// A instance if exists; otherwise. public static ShellAssociation CreateFromCLSID(Guid classId) => CreateAndInit(0, classId.ToString("B")); - /// Initializes a new instance of the class based on the supplied programmatic identifier (ProgId). + /// + /// Initializes a new instance of the class based on the supplied programmatic identifier (ProgId). + /// /// The ProgId. /// A instance if exists; otherwise. public static ShellAssociation CreateFromProgId(string progId) => CreateAndInit(0, progId); @@ -129,6 +145,71 @@ namespace Vanara.Windows.Shell return CreateAndInit(ASSOCF.ASSOCF_INIT_DEFAULTTOSTAR, ext); } + /// Searches for and retrieves file or protocol association-related binary data from the registry. + /// The ASSOCDATA value that specifies the type of data that is to be returned. + /// + /// An optional string with information about the location of the data. It is normally set to a Shell verb such as open. Set this + /// parameter to if it is not used. + /// + /// A value that, when this method returns successfully, receives the requested data value. + public Vanara.InteropServices.SafeCoTaskMemHandle GetData(ASSOCDATA data, string extra = null) + { + try + { + const ASSOCF flags = 0; + var sz = 0U; + qassoc.GetData(flags, data, extra, default, ref sz); + if (sz == 0) return null; + var ret = new Vanara.InteropServices.SafeCoTaskMemHandle(sz); + qassoc.GetData(flags, data, extra, ret, ref sz); + return ret; + } + catch (System.Runtime.InteropServices.COMException e) when (e.ErrorCode == (HRESULT)(Win32Error)Win32Error.ERROR_NO_ASSOCIATION) + { + return null; + } + } + + /// Searches for and retrieves a file or protocol association-related key from the registry. + /// The ASSOCKEY value that specifies the type of key that is to be returned. + /// + /// An optional string with information about the location of the key. It is normally set to a Shell verb such as open. Set this + /// parameter to if it is not used. + /// + /// A handle to the resulting registry key. + public SafeRegistryHandle GetKey(ASSOCKEY key, string extra = null) + { + const ASSOCF flags = 0; + qassoc.GetKey(flags, key, extra, out var hkey); + return new SafeRegistryHandle((IntPtr)hkey, true); + } + + /// Searches for and retrieves a file or protocol association-related string from the registry. + /// An ASSOCSTR value that specifies the type of string that is to be returned. + /// + /// An optional string with information about the location of the string. It is typically set to a Shell verb such as open. Set this + /// parameter to if it is not used. + /// + /// + /// A string used to return the requested string. If there are no results for this value, is returned. + /// + public string GetString(ASSOCSTR astr, string extra = null) + { + try + { + const ASSOCF flags = ASSOCF.ASSOCF_NOTRUNCATE | ASSOCF.ASSOCF_REMAPRUNDLL; + var sz = 0U; + qassoc.GetString(flags, astr, extra, null, ref sz); + var sb = new StringBuilder((int)sz, (int)sz); + qassoc.GetString(flags, astr, extra, sb, ref sz); + return sb.ToString(); + } + catch (System.Runtime.InteropServices.COMException e) when (e.ErrorCode == (HRESULT)(Win32Error)Win32Error.ERROR_NO_ASSOCIATION) + { + return null; + } + } + private static ShellAssociation CreateAndInit(ASSOCF flags, string assoc) { // if (Environment.OSVersion.Version.Major >= 6) @@ -148,21 +229,100 @@ namespace Vanara.Windows.Shell } } - private SafeRegistryHandle GetKey(ASSOCKEY key, string extra = null) + /// Represents a handler (executable) for a . + public class ShellAssociationHandler : ComObjWrapper { - const ASSOCF flags = 0; - qassoc.GetKey(flags, key, extra, out var hkey); - return new SafeRegistryHandle((IntPtr)hkey, true); - } + internal ShellAssociationHandler(IAssocHandler h) : base(h) + { + } - private string GetString(ASSOCSTR astr, string extra = null) - { - const ASSOCF flags = ASSOCF.ASSOCF_NOTRUNCATE | ASSOCF.ASSOCF_REMAPRUNDLL; - var sz = 0U; - qassoc.GetString(flags, astr, extra, null, ref sz); - var sb = new StringBuilder((int)sz, (int)sz); - qassoc.GetString(flags, astr, extra, sb, ref sz); - return sb.ToString(); + /// Retrieves the location of the icon associated with the application. + /// + /// An instance that contains the path and the index of the icon within the resource file for the + /// application's icon. + /// + public IconLocation IconLocation => ComInterface.GetIconLocation(out var p, out var i).Succeeded ? new IconLocation(p, i) : null; + + /// Indicates whether the application is registered as a recommended handler for the queried file type. + /// if this instance is recommended; otherwise, . + /// + /// + /// Applications that register themselves as handlers for particular file types can specify whether they are recommended + /// handlers. This has no effect on the actual behavior of the applications when launched. It is simply provided as a hint to + /// the user and a value that the UI can utilize programmatically, if desired. For example, the Shell's Open With dialog + /// separates entries into Recommended Programs and Other Programs. + /// + /// + /// Note that program recommendations may change over time. One example is provided when the user chooses an application from + /// the Other Programs of the Open With dialog to open a particular file type. That may cause the Shell to + /// "promote" that application to recommended status for that file type. Because the recommended status may change over time, + /// applications should not cache this value, but query it each time it is needed. + /// + /// + public bool IsRecommended => ComInterface.IsRecommended() == HRESULT.S_OK; + + /// Retrieves the full path and file name of the executable file associated with the file type. + /// A string that contains the full path of the file, including the file name. + public string Name => ComInterface.GetName(out var n).Succeeded ? n : null; + + /// Retrieves the display name of an application. + /// A string that contains the display name of the application. + public string UIName => ComInterface.GetUIName(out var n).Succeeded ? n : null; + + /// Indicates whether the current object is equal to another object of the same type. + /// An object to compare with this object. + /// true if the current object is equal to the parameter; otherwise, false. + public override bool Equals(IAssocHandler other) => Name.Equals(other.GetName(out var n).Succeeded ? n : null); + + /// Returns a hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + public override int GetHashCode() => Name.GetHashCode(); + + /// Directly invokes the associated handler. + /// A sequence of selected items on which to invoke the handler. + /// + /// + /// IAssocHandler objects are typically used to populate an Open With menu. When one of those menu items is selected, + /// this method is called to launch the chosen application. + /// + /// Invoke and CreateInvoker + /// + /// The IDataObject used by these methods can represent either a single file or a selection of multiple files. Not all + /// applications support the multiple file option. The applications that do support that scenario might impose other + /// restrictions, such as the number of files that can be opened simultaneously, or the acceptable combination of file types. + /// + /// + /// Therefore, an application often must determine whether the handler supports the selection before trying to invoke the + /// handler. For example, an application might enable a menu item only if it has verified that the selection in question was + /// supported by that handler. + /// + /// + public void Invoke(params ShellItem[] items) + { + if (items.Length == 0) + throw new ArgumentException("", nameof(items)); + + if (items.Length == 1) + { + ComInterface.Invoke(new ShellDataObject(items)).ThrowIfFailed(); + } + else + { + ComInterface.CreateInvoker(new ShellDataObject(items), out var invoker).ThrowIfFailed(); + using var pInvoker = ComReleaserFactory.Create(invoker); + var hr = invoker.SupportsSelection(); + if (hr == HRESULT.S_FALSE) + throw new ArgumentException("This handler is unable to support the selections provided.", nameof(items)); + hr.ThrowIfFailed(); + invoker.Invoke().ThrowIfFailed(); + } + } + + /// Sets an application as the default application for this file type. + /// + /// A string that contains the display name of the application. + /// + public void MakeDefault(string description) => ComInterface.MakeDefault(description).ThrowIfFailed(); } } } \ No newline at end of file