mirror of https://github.com/dahall/Vanara.git
Initial commit on ShellView wrapper
parent
3c4e5fe12c
commit
2b8ccf1438
|
@ -0,0 +1,59 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Vanara.Windows.Shell
|
||||||
|
{
|
||||||
|
/// <summary></summary>
|
||||||
|
/// <seealso cref="System.IComparable{T}"/>
|
||||||
|
/// <seealso cref="System.IDisposable"/>
|
||||||
|
/// <seealso cref="System.IEquatable{T}"/>
|
||||||
|
/// <seealso cref="System.IEquatable{T}"/>
|
||||||
|
/// <seealso cref="System.ComponentModel.INotifyPropertyChanged"/>
|
||||||
|
public abstract class ComObjWrapper<TObj, TComType> : IDisposable, IEquatable<TComType>, INotifyPropertyChanged where TObj : ComObjWrapper<TObj, TComType> where TComType : class
|
||||||
|
{
|
||||||
|
/// <summary>The internal reference to the COM object.</summary>
|
||||||
|
protected TComType iObj;
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="ComObjWrapper{TObj, TComType}"/> class.</summary>
|
||||||
|
/// <param name="baseInterface">The base interface.</param>
|
||||||
|
protected ComObjWrapper(TComType baseInterface) => iObj = baseInterface ?? throw new ArgumentNullException(nameof(baseInterface));
|
||||||
|
|
||||||
|
/// <summary>Occurs when a property value changes.</summary>
|
||||||
|
public virtual event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
/// <summary>Gets the COM interface supporting this type.</summary>
|
||||||
|
/// <value>The COM interface.</value>
|
||||||
|
public virtual TComType ComInterface => iObj;
|
||||||
|
|
||||||
|
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||||
|
public virtual void Dispose()
|
||||||
|
{
|
||||||
|
Marshal.ReleaseComObject(iObj);
|
||||||
|
iObj = 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><see langword="true"/> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <see langword="false"/>.</returns>
|
||||||
|
public override bool Equals(object obj) => ReferenceEquals(this, obj) || (obj is TObj view ? Equals(view) : obj is TComType com && Equals(com));
|
||||||
|
|
||||||
|
/// <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 abstract bool Equals(TComType 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 virtual bool Equals(TObj other) => Equals(other.iObj);
|
||||||
|
|
||||||
|
/// <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 abstract override int GetHashCode();
|
||||||
|
|
||||||
|
/// <summary>Called when a property's value has changed.</summary>
|
||||||
|
/// <param name="propName">Name of the property.</param>
|
||||||
|
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.InteropServices.ComTypes;
|
||||||
|
using Vanara.Extensions;
|
||||||
|
using Vanara.PInvoke;
|
||||||
|
using static Vanara.PInvoke.Shell32;
|
||||||
|
|
||||||
|
namespace Vanara.Windows.Shell
|
||||||
|
{
|
||||||
|
internal class ShellBrowser : IShellBrowser, IOleCommandTarget, Shell32.IServiceProvider
|
||||||
|
{
|
||||||
|
private readonly ShellView shellView;
|
||||||
|
|
||||||
|
internal ShellBrowser(ShellView view) => shellView = view ?? throw new ArgumentNullException(nameof(view));
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.BrowseObject(IntPtr pidl, SBSP wFlags)
|
||||||
|
{
|
||||||
|
if (wFlags.IsFlagSet(SBSP.SBSP_PARENT))
|
||||||
|
{
|
||||||
|
shellView.NavigateParent();
|
||||||
|
}
|
||||||
|
else if (wFlags.IsFlagSet(SBSP.SBSP_NAVIGATEBACK))
|
||||||
|
{
|
||||||
|
shellView.NavigateBack();
|
||||||
|
}
|
||||||
|
else if (wFlags.IsFlagSet(SBSP.SBSP_NAVIGATEFORWARD))
|
||||||
|
{
|
||||||
|
shellView.NavigateForward();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shellView.Navigate(new ShellFolder(pidl));
|
||||||
|
}
|
||||||
|
return HRESULT.S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.ContextSensitiveHelp(bool fEnterMode) => HRESULT.E_NOTIMPL;
|
||||||
|
|
||||||
|
HRESULT Ole32.IOleWindow.ContextSensitiveHelp(bool fEnterMode) => HRESULT.E_NOTIMPL;
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.EnableModelessSB(bool fEnable) => HRESULT.E_NOTIMPL;
|
||||||
|
|
||||||
|
HRESULT IOleCommandTarget.Exec(in Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, in object pvaIn, ref object pvaOut) => HRESULT.E_NOTIMPL;
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.GetControlWindow(FCW id, out HWND phwnd)
|
||||||
|
{
|
||||||
|
phwnd = HWND.NULL;
|
||||||
|
return HRESULT.E_NOTIMPL;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.GetViewStateStream(STGM grfMode, out IStream ppStrm)
|
||||||
|
{
|
||||||
|
ppStrm = null;
|
||||||
|
return HRESULT.E_NOTIMPL;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.GetWindow(out HWND phwnd)
|
||||||
|
{
|
||||||
|
phwnd = shellView.shellViewWindow;
|
||||||
|
return HRESULT.E_NOTIMPL;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT Ole32.IOleWindow.GetWindow(out HWND phwnd) => shellView.iObj.GetWindow(out phwnd);
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.InsertMenusSB(HMENU hmenuShared, ref Ole32.OLEMENUGROUPWIDTHS lpMenuWidths) => HRESULT.E_NOTIMPL;
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.OnViewWindowActive(IShellView ppshv) => HRESULT.E_NOTIMPL;
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.QueryActiveShellView(out IShellView ppshv)
|
||||||
|
{
|
||||||
|
ppshv = null;
|
||||||
|
return HRESULT.E_NOTIMPL;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT Shell32.IServiceProvider.QueryService(in Guid guidService, in Guid riid, out IntPtr ppvObject)
|
||||||
|
{
|
||||||
|
if (riid == typeof(IOleCommandTarget).GUID)
|
||||||
|
{
|
||||||
|
ppvObject = Marshal.GetComInterfaceForObject(this, typeof(IOleCommandTarget));
|
||||||
|
return HRESULT.S_OK;
|
||||||
|
}
|
||||||
|
else if (riid == typeof(IShellBrowser).GUID)
|
||||||
|
{
|
||||||
|
ppvObject = Marshal.GetComInterfaceForObject(this, typeof(IShellBrowser));
|
||||||
|
return HRESULT.S_OK;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ppvObject = IntPtr.Zero;
|
||||||
|
return HRESULT.E_NOINTERFACE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT IOleCommandTarget.QueryStatus(in Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, OLECMDTEXT pCmdText) => HRESULT.E_NOTIMPL;
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.RemoveMenusSB(HMENU hmenuShared) => HRESULT.E_NOTIMPL;
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.SendControlMsg(FCW id, uint uMsg, IntPtr wParam, IntPtr lParam, out IntPtr pret)
|
||||||
|
{
|
||||||
|
pret = default;
|
||||||
|
return HRESULT.E_NOTIMPL;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.SetMenuSB(HMENU hmenuShared, IntPtr holemenuRes, HWND hwndActiveObject) => HRESULT.E_NOTIMPL;
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.SetStatusTextSB(string pszStatusText) => HRESULT.E_NOTIMPL;
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.SetToolbarItems(ComCtl32.TBBUTTON[] lpButtons, uint nButtons, FCT uFlags) => HRESULT.E_NOTIMPL;
|
||||||
|
|
||||||
|
HRESULT IShellBrowser.TranslateAcceleratorSB(ref MSG pmsg, ushort wID) => HRESULT.E_NOTIMPL;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,278 @@
|
||||||
|
// Credit due to Gong-Shell from which this was largely taken.
|
||||||
|
#if !NETCOREAPP3_1
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Vanara.PInvoke;
|
||||||
|
using static Vanara.PInvoke.Shell32;
|
||||||
|
using static Vanara.PInvoke.User32;
|
||||||
|
|
||||||
|
namespace Vanara.Windows.Shell
|
||||||
|
{
|
||||||
|
/// <summary>Provides support for displaying the context menu of a shell item.</summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>Use this class to display a context menu for a shell item, either as a popup menu, or as a main menu.</para>
|
||||||
|
/// <para>
|
||||||
|
/// To display a popup menu, simply call <see cref="ShowContextMenu"/> with the parent control and the position at which the menu should
|
||||||
|
/// be shown.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// To display a shell context menu in a Form's main menu, call the <see cref="Populate"/> method to populate the menu. In addition, you
|
||||||
|
/// must intercept a number of special messages that will be sent to the menu's parent form. To do this, you must override <see
|
||||||
|
/// cref="Form.WndProc"/> like so:
|
||||||
|
/// </para>
|
||||||
|
/// <code>
|
||||||
|
///protected override void WndProc(ref Message m) {
|
||||||
|
///if ((m_ContextMenu == null) || (!m_ContextMenu.HandleMenuMessage(ref m))) {
|
||||||
|
///base.WndProc(ref m);
|
||||||
|
///}
|
||||||
|
///}
|
||||||
|
/// </code>
|
||||||
|
/// <para>Where m_ContextMenu is the <see cref="ShellContextMenu"/> being shown.</para>
|
||||||
|
/// Standard menu commands can also be invoked from this class, for example <see cref="InvokeDelete"/> and <see cref="InvokeRename"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public class ShellContextMenu
|
||||||
|
{
|
||||||
|
private const int m_CmdFirst = 0x8000;
|
||||||
|
private readonly IContextMenu2 m_ComInterface2;
|
||||||
|
private readonly IContextMenu3 m_ComInterface3;
|
||||||
|
private readonly MessageWindow m_MessageWindow;
|
||||||
|
|
||||||
|
/// <summary>Initialises a new instance of the <see cref="ShellContextMenu"/> class.</summary>
|
||||||
|
/// <param name="item">The item to which the context menu should refer.</param>
|
||||||
|
public ShellContextMenu(ShellItem item) : this(new ShellItem[] { item }) { }
|
||||||
|
|
||||||
|
/// <summary>Initialises a new instance of the <see cref="ShellContextMenu"/> class.</summary>
|
||||||
|
/// <param name="items">The items to which the context menu should refer.</param>
|
||||||
|
public ShellContextMenu(ShellItem[] items)
|
||||||
|
{
|
||||||
|
var pidls = new IntPtr[items.Length];
|
||||||
|
ShellFolder parent = null;
|
||||||
|
|
||||||
|
for (var n = 0; n < items.Length; ++n)
|
||||||
|
{
|
||||||
|
pidls[n] = ILFindLastID((IntPtr)items[n].PIDL);
|
||||||
|
|
||||||
|
if (parent is null)
|
||||||
|
{
|
||||||
|
if (items[n] == ShellFolder.Desktop)
|
||||||
|
{
|
||||||
|
parent = ShellFolder.Desktop;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parent = items[n].Parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (items[n].Parent != parent)
|
||||||
|
{
|
||||||
|
throw new Exception("All shell items must have the same parent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ComInterface = parent.IShellFolder.GetUIObjectOf<IContextMenu>(HWND.NULL, pidls);
|
||||||
|
m_ComInterface2 = ComInterface as IContextMenu2;
|
||||||
|
m_ComInterface3 = ComInterface as IContextMenu3;
|
||||||
|
m_MessageWindow = new MessageWindow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the underlying COM <see cref="IContextMenu"/> interface.</summary>
|
||||||
|
public IContextMenu ComInterface { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Handles context menu messages when the <see cref="ShellContextMenu"/> is displayed on a Form's main menu bar.</summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// To display a shell context menu in a Form's main menu, call the <see cref="Populate"/> method to populate the menu with the
|
||||||
|
/// shell item's menu items. In addition, you must intercept a number of special messages that will be sent to the menu's parent
|
||||||
|
/// form. To do this, you must override <see cref="Form.WndProc"/> like so:
|
||||||
|
/// </para>
|
||||||
|
/// <code>
|
||||||
|
///protected override void WndProc(ref Message m) {
|
||||||
|
///if ((m_ContextMenu == null) || (!m_ContextMenu.HandleMenuMessage(ref m))) {
|
||||||
|
///base.WndProc(ref m);
|
||||||
|
///}
|
||||||
|
///}
|
||||||
|
/// </code>
|
||||||
|
/// <para>Where m_ContextMenu is the <see cref="ShellContextMenu"/> being shown.</para>
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="m">The message to handle.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true"/> if the message was a Shell Context Menu message, <see langword="false"/> if not. If the method returns
|
||||||
|
/// false, then the message should be passed down to the base class's <see cref="Form.WndProc"/> method.
|
||||||
|
/// </returns>
|
||||||
|
public bool HandleMenuMessage(ref Message m)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if ((m.Msg == (int)WindowMessage.WM_COMMAND) && ((int)m.WParam >= m_CmdFirst))
|
||||||
|
{
|
||||||
|
InvokeCommand((int)m.WParam - m_CmdFirst);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_ComInterface3 != null)
|
||||||
|
{
|
||||||
|
m_ComInterface3.HandleMenuMsg2((uint)m.Msg, m.WParam, m.LParam, out IntPtr result);
|
||||||
|
m.Result = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (m_ComInterface2 != null)
|
||||||
|
{
|
||||||
|
m_ComInterface2.HandleMenuMsg((uint)m.Msg, m.WParam, m.LParam);
|
||||||
|
m.Result = IntPtr.Zero;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Invokes the Copy command on the shell item(s).</summary>
|
||||||
|
public void InvokeCopy() => InvokeVerb("copy");
|
||||||
|
|
||||||
|
/// <summary>Invokes the Copy command on the shell item(s).</summary>
|
||||||
|
public void InvokeCut() => InvokeVerb("cut");
|
||||||
|
|
||||||
|
/// <summary>Invokes the Delete command on the shell item(s).</summary>
|
||||||
|
public void InvokeDelete()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InvokeVerb("delete");
|
||||||
|
}
|
||||||
|
catch (COMException e)
|
||||||
|
{
|
||||||
|
// Ignore the exception raised when the user cancels a delete operation.
|
||||||
|
if (e.ErrorCode != (HRESULT)(Win32Error)Win32Error.ERROR_CANCELLED &&
|
||||||
|
e.ErrorCode != HRESULT.COPYENGINE_E_USER_CANCELLED)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Invokes the Paste command on the shell item(s).</summary>
|
||||||
|
public void InvokePaste() => InvokeVerb("paste");
|
||||||
|
|
||||||
|
/// <summary>Invokes the Rename command on the shell item.</summary>
|
||||||
|
public void InvokeRename() => InvokeVerb("rename");
|
||||||
|
|
||||||
|
/// <summary>Invokes the specified verb on the shell item(s).</summary>
|
||||||
|
public void InvokeVerb(string verb)
|
||||||
|
{
|
||||||
|
var invoke = new CMINVOKECOMMANDINFOEX();
|
||||||
|
invoke.cbSize = (uint)Marshal.SizeOf(invoke);
|
||||||
|
invoke.lpVerb = new SafeResourceId(verb);
|
||||||
|
ComInterface.InvokeCommand(invoke);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Populates a <see cref="Menu"/> with the context menu items for a shell item.</summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If this method is being used to populate a Form's main menu then you need to call <see cref="HandleMenuMessage"/> in the Form's
|
||||||
|
/// message handler.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="menu">The menu to populate.</param>
|
||||||
|
public void Populate(Menu menu)
|
||||||
|
{
|
||||||
|
RemoveShellMenuItems(menu);
|
||||||
|
ComInterface.QueryContextMenu(menu.Handle, 0, m_CmdFirst, int.MaxValue, CMF.CMF_EXPLORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Shows a context menu for a shell item.</summary>
|
||||||
|
/// <param name="control">The parent control.</param>
|
||||||
|
/// <param name="pos">The position on <paramref name="control"/> that the menu should be displayed at.</param>
|
||||||
|
public void ShowContextMenu(Control control, Point pos)
|
||||||
|
{
|
||||||
|
using var menu = new ContextMenu();
|
||||||
|
Populate(menu);
|
||||||
|
var command = TrackPopupMenuEx(menu.Handle, TrackPopupMenuFlags.TPM_RETURNCMD, pos.X, pos.Y, m_MessageWindow.Handle);
|
||||||
|
if (command > 0)
|
||||||
|
{
|
||||||
|
InvokeCommand((int)command - m_CmdFirst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InvokeCommand(int index)
|
||||||
|
{
|
||||||
|
var invoke = new CMINVOKECOMMANDINFOEX(index) { nShow = ShowWindowCommand.SW_SHOWNORMAL };
|
||||||
|
m_ComInterface2.InvokeCommand(invoke);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveShellMenuItems(Menu menu)
|
||||||
|
{
|
||||||
|
const int tag = 0xAB;
|
||||||
|
|
||||||
|
var menuInfo = new MENUINFO();
|
||||||
|
menuInfo.cbSize = (uint)Marshal.SizeOf(menuInfo);
|
||||||
|
menuInfo.fMask = MenuInfoMember.MIM_MENUDATA;
|
||||||
|
|
||||||
|
var itemInfo = new MENUITEMINFO();
|
||||||
|
itemInfo.cbSize = (uint)Marshal.SizeOf(itemInfo);
|
||||||
|
itemInfo.fMask = MenuItemInfoMask.MIIM_ID | MenuItemInfoMask.MIIM_SUBMENU;
|
||||||
|
|
||||||
|
// First, tag the managed menu items with an arbitary value (0xAB).
|
||||||
|
TagManagedMenuItems(menu, tag);
|
||||||
|
|
||||||
|
var remove = new List<uint>();
|
||||||
|
var count = GetMenuItemCount(menu.Handle);
|
||||||
|
for (uint n = 0; n < count; ++n)
|
||||||
|
{
|
||||||
|
GetMenuItemInfo(menu.Handle, n, true, ref itemInfo);
|
||||||
|
|
||||||
|
if (itemInfo.hSubMenu.IsNull)
|
||||||
|
{
|
||||||
|
// If the item has no submenu we can't get the tag, so check its ID to determine if it was added by the shell.
|
||||||
|
if (itemInfo.wID >= m_CmdFirst) remove.Add(n);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GetMenuInfo(itemInfo.hSubMenu, ref menuInfo);
|
||||||
|
if ((int)menuInfo.dwMenuData != tag) remove.Add(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the unmanaged menu items.
|
||||||
|
remove.Reverse();
|
||||||
|
foreach (var position in remove)
|
||||||
|
{
|
||||||
|
DeleteMenu(menu.Handle, (uint)position, MenuFlags.MF_BYPOSITION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TagManagedMenuItems(Menu menu, int tag)
|
||||||
|
{
|
||||||
|
var info = new MENUINFO();
|
||||||
|
info.cbSize = (uint)Marshal.SizeOf(info);
|
||||||
|
info.fMask = MenuInfoMember.MIM_MENUDATA;
|
||||||
|
info.dwMenuData = (UIntPtr)tag;
|
||||||
|
|
||||||
|
foreach (MenuItem item in menu.MenuItems)
|
||||||
|
{
|
||||||
|
SetMenuInfo(item.Handle, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MessageWindow : Control
|
||||||
|
{
|
||||||
|
private readonly ShellContextMenu m_Parent;
|
||||||
|
|
||||||
|
public MessageWindow(ShellContextMenu parent) => m_Parent = parent;
|
||||||
|
|
||||||
|
protected override void WndProc(ref Message m)
|
||||||
|
{
|
||||||
|
if (!m_Parent.HandleMenuMessage(ref m))
|
||||||
|
{
|
||||||
|
base.WndProc(ref m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,96 @@
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Vanara.Collections;
|
||||||
|
using Vanara.Extensions;
|
||||||
|
using static Vanara.PInvoke.Shell32;
|
||||||
|
|
||||||
|
namespace Vanara.Windows.Shell
|
||||||
|
{
|
||||||
|
/// <summary>The navigation log is a history of the locations visited by a shell view object.</summary>
|
||||||
|
public class ShellNavigationHistory : Vanara.Collections.IHistory<ShellItem>
|
||||||
|
{
|
||||||
|
private readonly Vanara.Collections.History<PIDL> pidls = new Collections.History<PIDL>();
|
||||||
|
|
||||||
|
internal ShellNavigationHistory()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Occurs when [collection changed].</summary>
|
||||||
|
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||||
|
|
||||||
|
/// <summary>Occurs when a property value changes.</summary>
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
/// <summary>Indicates the presence of items in the history that can be reached by calling <see cref="M:Vanara.Collections.IHistory`1.SeekBackward"/>.</summary>
|
||||||
|
/// <value><see langword="true"/> if this instance can seek backward; otherwise, <see langword="false"/>.</value>
|
||||||
|
public bool CanSeekBackward => pidls.CanSeekBackward;
|
||||||
|
|
||||||
|
/// <summary>Indicates the presence of items in the history that can be reached by calling <see cref="M:Vanara.Collections.IHistory`1.SeekForward"/>.</summary>
|
||||||
|
/// <value><see langword="true"/> if this instance can seek forward; otherwise, <see langword="false"/>.</value>
|
||||||
|
public bool CanSeekForward => pidls.CanSeekForward;
|
||||||
|
|
||||||
|
/// <summary>Gets the items in the history.</summary>
|
||||||
|
/// <value>The number of items.</value>
|
||||||
|
public int Count => pidls.Count;
|
||||||
|
|
||||||
|
/// <summary>Gets the shell object in the Locations collection pointed to by CurrentLocationIndex.</summary>
|
||||||
|
public ShellItem Current => new ShellItem(pidls.Current);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the specified item as the last history entry and sets the <see cref="P:Vanara.Collections.IHistory`1.Current"/> property to
|
||||||
|
/// it's value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to add to the history.</param>
|
||||||
|
public void Add(ShellItem item) => pidls.Add(item.PIDL, true);
|
||||||
|
|
||||||
|
/// <summary>Clears the history of all items.</summary>
|
||||||
|
public void Clear() => pidls.Clear();
|
||||||
|
|
||||||
|
/// <summary>Returns an enumerator that iterates through the collection.</summary>
|
||||||
|
/// <returns>A <see cref="IEnumerator{T}"/> that can be used to iterate through the collection.</returns>
|
||||||
|
public IEnumerator<ShellItem> GetEnumerator() => pidls.Select(p => new ShellItem(p)).GetEnumerator();
|
||||||
|
|
||||||
|
/// <summary>Gets a specified number of items starting at a location within the history.</summary>
|
||||||
|
/// <param name="count">The maximum number of items to retrieve. The actual number of items returned may be less if not avaialable.</param>
|
||||||
|
/// <param name="origin">The reference point within the history at which to start fetching items.</param>
|
||||||
|
/// <returns>A read-only list of items.</returns>
|
||||||
|
public IReadOnlyList<ShellItem> GetItems(int count, SeekOrigin origin) => (IReadOnlyList<ShellItem>)new List<ShellItem>(pidls.GetItems(count, origin).Select(ShIFromPIDL));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Seeks through the history a given number of items starting at a known location within the history. This updates the <see
|
||||||
|
/// cref="P:Vanara.Collections.IHistory`1.Current"/> property.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">The number of items to move. This value can be negative to search backwards or positive to search forwards.</param>
|
||||||
|
/// <param name="origin">The reference point within the history at which to start seeking.</param>
|
||||||
|
/// <returns>The value at the new current pointer position.</returns>
|
||||||
|
public ShellItem Seek(int count, SeekOrigin origin) => ShIFromPIDL(pidls.Seek(count, origin));
|
||||||
|
|
||||||
|
/// <summary>Seeks one position backwards.</summary>
|
||||||
|
/// <returns>The value at the new current pointer position.</returns>
|
||||||
|
public ShellItem SeekBackward() => ShIFromPIDL(pidls.SeekBackward());
|
||||||
|
|
||||||
|
/// <summary>Seeks one position forwards.</summary>
|
||||||
|
/// <returns>The value at the new current pointer position.</returns>
|
||||||
|
public ShellItem SeekForward() => ShIFromPIDL(pidls.SeekForward());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the specified item as the last history entry and sets the <see cref="P:Vanara.Collections.IHistory`1.Current"/> property to
|
||||||
|
/// it's value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to add to the history.</param>
|
||||||
|
/// <param name="removeForwardItems">
|
||||||
|
/// <see langword="true"/> indicates to remove all items forward of the current pointer; <see langword="false"/> leaves the history intact.
|
||||||
|
/// </param>
|
||||||
|
void IHistory<ShellItem>.Add(ShellItem item, bool removeForwardItems) => pidls.Add(item?.PIDL, removeForwardItems);
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
internal void Add(PIDL pidl) => pidls.Add(pidl, true);
|
||||||
|
|
||||||
|
private static ShellItem ShIFromPIDL(PIDL pidl) => pidl is null ? null : new ShellItem(pidl);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,402 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Vanara.InteropServices;
|
||||||
|
using Vanara.PInvoke;
|
||||||
|
using static Vanara.PInvoke.Shell32;
|
||||||
|
|
||||||
|
namespace Vanara.Windows.Shell
|
||||||
|
{
|
||||||
|
/// <summary>Event argument for The Navigated event</summary>
|
||||||
|
public class NavigatedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="NavigatedEventArgs"/> class.</summary>
|
||||||
|
/// <param name="folder">The folder.</param>
|
||||||
|
public NavigatedEventArgs(ShellFolder folder) => NewLocation = folder;
|
||||||
|
|
||||||
|
/// <summary>The new location of the explorer browser</summary>
|
||||||
|
public ShellItem NewLocation { get; private set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Event argument for The Navigating event</summary>
|
||||||
|
public class NavigatingEventArgs : CancelEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="NavigatingEventArgs"/> class.</summary>
|
||||||
|
/// <param name="pendingLocation">The pending location.</param>
|
||||||
|
public NavigatingEventArgs(ShellItem pendingLocation) => PendingLocation = pendingLocation;
|
||||||
|
|
||||||
|
/// <summary>The location being navigated to.</summary>
|
||||||
|
public ShellItem PendingLocation { get; private set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Event argument for the NavigatinoFailed event</summary>
|
||||||
|
public class NavigationFailedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>The location the browser would have navigated to.</summary>
|
||||||
|
public ShellItem FailedLocation { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary></summary>
|
||||||
|
/// <seealso cref="System.IComparable{T}"/>
|
||||||
|
/// <seealso cref="System.IDisposable"/>
|
||||||
|
/// <seealso cref="System.IEquatable{T}"/>
|
||||||
|
/// <seealso cref="System.IEquatable{T}"/>
|
||||||
|
/// <seealso cref="System.ComponentModel.INotifyPropertyChanged"/>
|
||||||
|
public class ShellView : Control, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
internal IShellView iObj;
|
||||||
|
internal HWND shellViewWindow;
|
||||||
|
private ShellFolder currentFolder;
|
||||||
|
private IShellBrowser iBrowser;
|
||||||
|
|
||||||
|
/// <summary>Creates a new <see cref="ShellView"/> from a shell folder and assigns it to a window.</summary>
|
||||||
|
/// <param name="folder">The shell folder.</param>
|
||||||
|
/// <param name="owner">The owner window.</param>
|
||||||
|
/// <returns>A new <see cref="ShellView"/> instance for the supplied shell folder.</returns>
|
||||||
|
public ShellView(ShellFolder folder, IWin32Window owner) : this(CreateViewObject(folder, owner?.Handle ?? IntPtr.Zero)) => Navigate(folder);
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="ShellView"/> class.</summary>
|
||||||
|
public ShellView() : this(ShellFolder.Desktop, null) { }
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="ShellView"/> class.</summary>
|
||||||
|
/// <param name="baseInterface">The base interface.</param>
|
||||||
|
internal ShellView(IShellView baseInterface)
|
||||||
|
{
|
||||||
|
iObj = baseInterface;
|
||||||
|
History = new ShellNavigationHistory();
|
||||||
|
Items = new ShellItemArray(GetItemArray(iObj, SVGIO.SVGIO_ALLVIEW));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Fires when the Items collection changes.</summary>
|
||||||
|
[Category("Action"), Description("Items changed.")]
|
||||||
|
public event EventHandler ItemsChanged;
|
||||||
|
|
||||||
|
/// <summary>Fires when the ExplorerBorwser view has finished enumerating files.</summary>
|
||||||
|
[Category("Behavior"), Description("View is done enumerating files.")]
|
||||||
|
public event EventHandler ItemsEnumerated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires when a navigation has been 'completed': no Navigating listener has canceled, and the ExplorerBorwser has created a new
|
||||||
|
/// view. The view will be populated with new items asynchronously, and ItemsChanged will be fired to reflect this some time later.
|
||||||
|
/// </summary>
|
||||||
|
[Category("Action"), Description("Navigation complete.")]
|
||||||
|
public event EventHandler<NavigatedEventArgs> Navigated;
|
||||||
|
|
||||||
|
/// <summary>Fires when a navigation has been initiated, but is not yet complete.</summary>
|
||||||
|
[Category("Action"), Description("Navigation initiated, but not complete.")]
|
||||||
|
public event EventHandler<NavigatingEventArgs> Navigating;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires when either a Navigating listener cancels the navigation, or if the operating system determines that navigation is not possible.
|
||||||
|
/// </summary>
|
||||||
|
[Category("Action"), Description("Navigation failed.")]
|
||||||
|
public event EventHandler<NavigationFailedEventArgs> NavigationFailed;
|
||||||
|
|
||||||
|
/// <summary>Occurs when a property value changes.</summary>
|
||||||
|
[Category("Behavior"), Description("Property changed.")]
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
/// <summary>Fires when the item selected in the view has changed (i.e., a rename ). This is not the same as SelectionChanged.</summary>
|
||||||
|
[Category("Action"), Description("Selected item has changed.")]
|
||||||
|
public event EventHandler SelectedItemModified;
|
||||||
|
|
||||||
|
/// <summary>Fires when the SelectedItems collection changes.</summary>
|
||||||
|
[Category("Behavior"), Description("Selection changed.")]
|
||||||
|
public event EventHandler SelectionChanged;
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the <see cref="ShellFolder"/> currently being browsed by the <see cref="ShellView"/>.</summary>
|
||||||
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||||
|
public ShellFolder CurrentFolder { get => currentFolder ??= new ShellFolder(GetFolderForView(iObj)); set => Navigate(value); }
|
||||||
|
|
||||||
|
/// <summary>A set of flags that indicate the options for the folder.</summary>
|
||||||
|
public FOLDERFLAGS Flags => iObj.GetCurrentInfo().fFlags;
|
||||||
|
|
||||||
|
/// <summary>Contains the navigation history of the ExplorerBrowser</summary>
|
||||||
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||||
|
public ShellNavigationHistory History { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets all the items in the view.</summary>
|
||||||
|
/// <value>An array with all the items.</value>
|
||||||
|
public ShellItemArray Items { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the currently selected items in the view.</summary>
|
||||||
|
/// <value>An array with the selected items.</value>
|
||||||
|
/// <exception cref="System.ArgumentException">All items must belong to the folder hosted by this view. - SelectedItems</exception>
|
||||||
|
public ShellItem[] SelectedItems
|
||||||
|
{
|
||||||
|
get => Array.ConvertAll(GetItems(iObj, SVGIO.SVGIO_SELECTION), i => new ShellItem(i));
|
||||||
|
set
|
||||||
|
{
|
||||||
|
// Deselect all
|
||||||
|
iObj.SelectItem(default, SVSIF.SVSI_DESELECTOTHERS);
|
||||||
|
if (value is null || value.Length == 0) return;
|
||||||
|
// Get parent folder of view items
|
||||||
|
PIDL pidl = CurrentFolder.PIDL;
|
||||||
|
// Ensure all have this parent
|
||||||
|
if (!value.All(shi => shi.PIDL.Parent.Equals(pidl)))
|
||||||
|
throw new ArgumentException("All items must belong to the folder hosted by this view.", nameof(SelectedItems));
|
||||||
|
// Select all provided
|
||||||
|
foreach (ShellItem item in value)
|
||||||
|
iObj.SelectItem((IntPtr)item.PIDL, SVSIF.SVSI_SELECT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a handle to one of the windows participating in in-place activation (frame, document, parent, or in-place object window).
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The window handle.</returns>
|
||||||
|
public HWND WindowHandle => shellViewWindow;
|
||||||
|
|
||||||
|
/// <summary>Folder view mode.</summary>
|
||||||
|
public FOLDERVIEWMODE ViewMode => iObj.GetCurrentInfo().ViewMode;
|
||||||
|
|
||||||
|
/// <summary>Gets the default size of the control.</summary>
|
||||||
|
protected override Size DefaultSize => new Size(250, 200);
|
||||||
|
|
||||||
|
private IShellBrowser Browser => iBrowser ??= new ShellBrowser(this);
|
||||||
|
|
||||||
|
/// <summary>Implements the operator !=.</summary>
|
||||||
|
/// <param name="left">The left.</param>
|
||||||
|
/// <param name="right">The right.</param>
|
||||||
|
/// <returns>The result of the operator.</returns>
|
||||||
|
public static bool operator !=(ShellView left, ShellView right) => !(left == right);
|
||||||
|
|
||||||
|
/// <summary>Implements the operator ==.</summary>
|
||||||
|
/// <param name="left">The left.</param>
|
||||||
|
/// <param name="right">The right.</param>
|
||||||
|
/// <returns>The result of the operator.</returns>
|
||||||
|
public static bool operator ==(ShellView left, ShellView right) => EqualityComparer<ShellView>.Default.Equals(left, right);
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// <exception cref="NotImplementedException"></exception>
|
||||||
|
public bool Equals(IShellView other)
|
||||||
|
{
|
||||||
|
HWND w1 = HWND.NULL, w2 = HWND.NULL;
|
||||||
|
other?.GetWindow(out w1);
|
||||||
|
iObj?.GetWindow(out w2);
|
||||||
|
return w1 == w2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Determines whether the specified <see cref="object"/>, is equal to this instance.</summary>
|
||||||
|
/// <param name="obj">The <see cref="object"/> to compare with this instance.</param>
|
||||||
|
/// <returns><see langword="true"/> if the specified <see cref="object"/> is equal to this instance; otherwise, <see langword="false"/>.</returns>
|
||||||
|
public override bool Equals(object obj) => ReferenceEquals(this, obj) || (obj is ShellView sv && Equals(sv.iObj)) || (obj is IShellView isv && Equals(isv));
|
||||||
|
|
||||||
|
/// <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() => (int)(IntPtr)WindowHandle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the Explorer Browser of existing content, fills it with content from the specified container, and adds a new point to the
|
||||||
|
/// Travel Log.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder">The shell container to navigate to.</param>
|
||||||
|
public void Navigate(ShellFolder folder)
|
||||||
|
{
|
||||||
|
if (!OnNavigating(folder)) return;
|
||||||
|
|
||||||
|
ShellFolder previous = currentFolder;
|
||||||
|
currentFolder = folder;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RecreateShellView();
|
||||||
|
History.Add(folder.PIDL);
|
||||||
|
OnNavigated();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
currentFolder = previous;
|
||||||
|
RecreateShellView();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
OnPropertyChanged(nameof(CurrentFolder));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Navigates to the last item in the navigation history list. This does not change the set of locations in the navigation log.
|
||||||
|
/// </summary>
|
||||||
|
public bool NavigateBack() { if (History.CanSeekBackward) { Navigate(new ShellFolder(History.SeekBackward())); return true; } return false; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Navigates to the next item in the navigation history list. This does not change the set of locations in the navigation log.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the navigation succeeded, false if it failed for any reason.</returns>
|
||||||
|
public bool NavigateForward() { if (History.CanSeekForward) { Navigate(new ShellFolder(History.SeekForward())); return true; } return false; }
|
||||||
|
|
||||||
|
/// <summary>Navigates to the parent of the currently displayed folder.</summary>
|
||||||
|
public void NavigateParent() { if (CurrentFolder != ShellFolder.Desktop) Navigate(CurrentFolder.Parent); }
|
||||||
|
|
||||||
|
/// <summary>Refreshes the view's contents in response to user input.</summary>
|
||||||
|
public override void Refresh() => iObj.Refresh();
|
||||||
|
|
||||||
|
/// <summary>Saves the Shell's view settings so the current state can be restored during a subsequent browsing session.</summary>
|
||||||
|
public void SaveState() => iObj.SaveViewState();
|
||||||
|
|
||||||
|
/// <summary>Raises the <see cref="ItemsChanged"/> event.</summary>
|
||||||
|
protected internal virtual void OnItemsChanged() => ItemsChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
|
/// <summary>Raises the <see cref="ItemsEnumerated"/> event.</summary>
|
||||||
|
protected internal virtual void OnItemsEnumerated() => ItemsEnumerated?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
|
/// <summary>Raises the <see cref="Navigated"/> event.</summary>
|
||||||
|
protected internal virtual void OnNavigated() => Navigated?.Invoke(this, new NavigatedEventArgs(CurrentFolder));
|
||||||
|
|
||||||
|
/// <summary>Raises the <see cref="Navigating"/> event.</summary>
|
||||||
|
protected internal virtual bool OnNavigating(ShellFolder pendingLocation)
|
||||||
|
{
|
||||||
|
var e = new NavigatingEventArgs(pendingLocation);
|
||||||
|
Navigating?.Invoke(this, e);
|
||||||
|
return !e.Cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Raises the <see cref="NavigationFailed"/> event.</summary>
|
||||||
|
protected internal virtual void OnNavigationFailed(NavigationFailedEventArgs nfevent)
|
||||||
|
{
|
||||||
|
if (nfevent?.FailedLocation is null) return;
|
||||||
|
NavigationFailed?.Invoke(this, nfevent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Raises the <see cref="SelectedItemModified"/> event.</summary>
|
||||||
|
protected internal virtual void OnSelectedItemModified() => SelectedItemModified?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
|
/// <summary>Raises the <see cref="SelectionChanged"/> event.</summary>
|
||||||
|
protected internal virtual void OnSelectionChanged() => SelectionChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases the unmanaged resources used by the <see cref="T:System.Windows.Forms.Control"/> and its child controls and optionally
|
||||||
|
/// releases the managed resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">
|
||||||
|
/// <see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.
|
||||||
|
/// </param>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if (!shellViewWindow.IsNull)
|
||||||
|
{
|
||||||
|
User32.DestroyWindow(shellViewWindow);
|
||||||
|
shellViewWindow = HWND.NULL;
|
||||||
|
}
|
||||||
|
if (iBrowser != null)
|
||||||
|
{
|
||||||
|
Marshal.ReleaseComObject(iBrowser);
|
||||||
|
iBrowser = null;
|
||||||
|
}
|
||||||
|
if (Items != null)
|
||||||
|
{
|
||||||
|
Items.Dispose();
|
||||||
|
Items = null;
|
||||||
|
}
|
||||||
|
if (History != null)
|
||||||
|
{
|
||||||
|
History = null;
|
||||||
|
}
|
||||||
|
if (iObj != null)
|
||||||
|
{
|
||||||
|
Marshal.ReleaseComObject(iObj);
|
||||||
|
iObj = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Raises the <see cref="Control.CreateControl"/> method.</summary>
|
||||||
|
protected override void OnCreateControl()
|
||||||
|
{
|
||||||
|
base.OnCreateControl();
|
||||||
|
CreateShellView();
|
||||||
|
OnNavigated();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Raises the <see cref="Control.Resize"/> event.</summary>
|
||||||
|
/// <param name="eventargs">The <see cref="EventArgs"/> instance containing the event data.</param>
|
||||||
|
protected override void OnResize(EventArgs eventargs)
|
||||||
|
{
|
||||||
|
base.OnResize(eventargs);
|
||||||
|
User32.SetWindowPos(shellViewWindow, HWND.HWND_TOP, 0, 0, ClientRectangle.Width, ClientRectangle.Height, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Processes Windows messages.</summary>
|
||||||
|
/// <param name="m">The Windows <see cref="Message"/> to process.</param>
|
||||||
|
protected override void WndProc(ref Message m)
|
||||||
|
{
|
||||||
|
const int CWM_GETISHELLBROWSER = 0x407;
|
||||||
|
|
||||||
|
// Windows 9x sends the CWM_GETISHELLBROWSER message and expects the IShellBrowser for the window to be returned or an Access
|
||||||
|
// Violation occurs. This is pseudo-documented in knowledge base article Q157247.
|
||||||
|
if (m.Msg == CWM_GETISHELLBROWSER)
|
||||||
|
{
|
||||||
|
m.Result = Marshal.GetComInterfaceForObject(iBrowser, typeof(IShellBrowser));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base.WndProc(ref m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IShellView CreateViewObject(ShellFolder folder, HWND owner) =>
|
||||||
|
folder?.iShellFolder.CreateViewObject<IShellView>(owner);
|
||||||
|
|
||||||
|
private static PIDL GetFolderForView(IShellView iView)
|
||||||
|
{
|
||||||
|
PIDL pidl = GetItems(iView, SVGIO.SVGIO_ALLVIEW).FirstOrDefault();
|
||||||
|
pidl?.RemoveLastId();
|
||||||
|
return pidl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IShellItemArray GetItemArray(IShellView iView, SVGIO uItem) => ((IFolderView)iView).Items<IShellItemArray>(uItem);
|
||||||
|
|
||||||
|
private static PIDL[] GetItems(IShellView iView, SVGIO uItem)
|
||||||
|
{
|
||||||
|
using ComReleaser<System.Windows.Forms.IDataObject> ido = ComReleaserFactory.Create(iView.GetItemObject<System.Windows.Forms.IDataObject>(uItem));
|
||||||
|
var shdo = new ShellDataObject(ido.Item);
|
||||||
|
return shdo.GetShellIdList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateShellView()
|
||||||
|
{
|
||||||
|
IShellView prev = iObj;
|
||||||
|
iObj = CreateViewObject(CurrentFolder, Handle);
|
||||||
|
Items = new ShellItemArray(GetItemArray(iObj, SVGIO.SVGIO_ALLVIEW));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fsettings = new FOLDERSETTINGS(ViewMode, Flags);
|
||||||
|
shellViewWindow = iObj.CreateViewWindow(prev, fsettings, Browser, ClientRectangle);
|
||||||
|
}
|
||||||
|
catch (COMException ex)
|
||||||
|
{
|
||||||
|
// If the operation was cancelled by the user (for example because an empty removable media drive was selected, then
|
||||||
|
// "Cancel" pressed in the resulting dialog) convert the exception into something more meaningfil.
|
||||||
|
if (ex.ErrorCode == unchecked((int)0x800704C7U))
|
||||||
|
{
|
||||||
|
throw new OperationCanceledException("User cancelled.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iObj.UIActivate(SVUIA.SVUIA_ACTIVATE_NOFOCUS);
|
||||||
|
|
||||||
|
if (DesignMode) User32.EnableWindow(shellViewWindow, false);
|
||||||
|
|
||||||
|
if (prev != null) prev.DestroyViewWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
|
||||||
|
private void RecreateShellView()
|
||||||
|
{
|
||||||
|
if (iObj != null)
|
||||||
|
{
|
||||||
|
CreateShellView();
|
||||||
|
OnNavigated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue