Closed zhuxb711 closed 3 years ago
ShellAssociation
is simply a wrapper around IQueryAssociations
. It does not include a capability to walk the association's list. There are numerous articles on StackOverflow on how to do what you are asking. If you find a simple way, send me the code and I will add it to that class.
@zhuxb711 I think you are looking for SHAssocEnumHandlers. https://github.com/dahall/Vanara/blob/c3a63c8c1b8ce1c49aadfa0ad1126078b8aac745/PInvoke/Shell32/ShObjIdl.cs#L1819
@vessd That's what I need! Thanks a lot~ @dahall Consider the following code:
if (Shell32.SHAssocEnumHandlers(".png", Shell32.ASSOC_FILTER.ASSOC_FILTER_NONE, out Shell32.IEnumAssocHandlers AssocHandlers) == HRESULT.S_OK)
{
Shell32.IAssocHandler[] Handlers = new Shell32.IAssocHandler[100];
if (AssocHandlers.Next(100, Handlers, out uint FetchedNum) == HRESULT.S_OK)
{
Array.Resize(ref Handlers, Convert.ToInt32(FetchedNum));
foreach (Shell32.IAssocHandler Handler in Handlers)
{
try
{
if (Handler.GetName(out string FullPath) == HRESULT.S_OK && Handler.GetUIName(out string DisplayName) == HRESULT.S_OK)
{
Debug.WriteLine($"Associate Name: {DisplayName}, Executable Path: {FullPath}");
}
}
finally
{
Marshal.ReleaseComObject(Handler);
}
}
}
}
Yep. Instead I added the following:
/// <summary>Gets a list of file name extension handlers.</summary>
/// <value>The handlers for this association.</value>
public IReadOnlyList<ShellAssociationHandler> Handlers
{
get
{
if (SHAssocEnumHandlers(Extension, ASSOC_FILTER.ASSOC_FILTER_NONE, out var ieah).Failed)
return (IReadOnlyList<ShellAssociationHandler>)new List<ShellAssociationHandler>();
using var pieah = ComReleaserFactory.Create(ieah);
var e = new Vanara.Collections.IEnumFromCom<IAssocHandler>(ieah.Next, () => { });
return (IReadOnlyList<ShellAssociationHandler>)e.Select(i => new ShellAssociationHandler(i)).ToList();
}
}
/// <summary>Represents a handler (executable) for a <see cref="ShellAssociation"/>.</summary>
public class ShellAssociationHandler : ComObjWrapper<ShellAssociationHandler, IAssocHandler>
{
internal ShellAssociationHandler(IAssocHandler h) : base(h)
{
}
/// <summary>Retrieves the location of the icon associated with the application.</summary>
/// <value>
/// An <see cref="IconLocation"/> instance that contains the path and the index of the icon within the resource file for the
/// application's icon.
/// </value>
public IconLocation IconLocation => ComInterface.GetIconLocation(out var p, out var i).Succeeded ? new IconLocation(p, i) : null;
/// <summary>Indicates whether the application is registered as a recommended handler for the queried file type.</summary>
/// <value><see langword="true"/> if this instance is recommended; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// <para>
/// 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 <c>Open With</c> dialog
/// separates entries into <c>Recommended Programs</c> and <c>Other Programs</c>.
/// </para>
/// <para>
/// Note that program recommendations may change over time. One example is provided when the user chooses an application from
/// the <c>Other Programs</c> of the <c>Open With</c> 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.
/// </para>
/// </remarks>
public bool IsRecommended => ComInterface.IsRecommended() == HRESULT.S_OK;
/// <summary>Retrieves the full path and file name of the executable file associated with the file type.</summary>
/// <value>A string that contains the full path of the file, including the file name.</value>
public string Name => ComInterface.GetName(out var n).Succeeded ? n : null;
/// <summary>Retrieves the display name of an application.</summary>
/// <value>A string that contains the display name of the application.</value>
public string UIName => ComInterface.GetUIName(out var n).Succeeded ? n : null;
/// <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 override bool Equals(IAssocHandler other) => Name.Equals(other.GetName(out var n).Succeeded ? n : null);
/// <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() => Name.GetHashCode();
/// <summary>Directly invokes the associated handler.</summary>
/// <param name="items">A sequence of selected items on which to invoke the handler.</param>
/// <remarks>
/// <para>
/// IAssocHandler objects are typically used to populate an <c>Open With</c> menu. When one of those menu items is selected,
/// this method is called to launch the chosen application.
/// </para>
/// <para>Invoke and CreateInvoker</para>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// </remarks>
public void Invoke(params ShellItem[] items)
{
if (items.Length == 0) throw new ArgumentException("", nameof(items));
// TODO: Call for multiple invokeable items
if (items.Length > 1) throw new NotImplementedException();
ComInterface.Invoke(new ShellDataObject(items)).ThrowIfFailed();
}
/// <summary>Sets an application as the default application for this file type.</summary>
/// <param name="description">
/// <para>A string that contains the display name of the application.</para>
/// </param>
public void MakeDefault(string description) => ComInterface.MakeDefault(description).ThrowIfFailed();
}
Thanks @vessd for the pointer. This will get pushed once I'm done testing.
new IconLocation(p, i)
This will failed if it is UWP
@zhuxb711 Why will the IconLocation
constructor fail?
Because it throw an exception and the message is "resourceIndex could not be 0"
And other problem is you should use CreateInvoker when items.Length >1
It is recommended to increase a new property "IsDefault" to determine whether it is the default association. You could get it from the code below:
uint Length = 0;
if (ShlwApi.AssocQueryString(ShlwApi.ASSOCF.ASSOCF_VERIFY, ShlwApi.ASSOCSTR.ASSOCSTR_EXECUTABLE, System.IO.Path.GetExtension(Path).ToLower(), null, null, ref Length) == HRESULT.S_FALSE)
{
StringBuilder Builder = new StringBuilder(Convert.ToInt32(Length));
if (ShlwApi.AssocQueryString(ShlwApi.ASSOCF.ASSOCF_VERIFY, ShlwApi.ASSOCSTR.ASSOCSTR_EXECUTABLE, System.IO.Path.GetExtension(Path).ToLower(), null, Builder, ref Length) == HRESULT.S_OK)
{
return Builder.ToString();
}
else
{
return string.Empty;
}
}
else
{
return string.Empty;
}
I have an extension, but I don’t know how to find all applications that support this extension.
“ShellAssociation" only returns the default application, but I need all of them...
Here is what I want:
.pdf ->
"Microsoft Edge" (full path) "Drawboard" (full path) "Adobe Acrobat Reader" (full path)