【问题标题】:Check if windows explorer already opened on given path检查 Windows 资源管理器是否已在给定路径上打开
【发布时间】:2017-08-01 11:13:13
【问题描述】:

如何查看 Windows 资源管理器是否已使用特定路径打开?我不希望我的应用程序打开许多重复的窗口。我无法用这种方式做到这一点:

var
  H: hwnd;
begin
  if FileExists(edt8.Text) then
  begin
    H := FindWindow(0, PChar(ExtractFilePath(edt8.Text)));
    if H <> 0 then
      ShowMessage('explorer already opened')//explorer bring to front
    else
      ShellExecute(Application.Handle, nil, 'explorer.exe',
        PChar(ExtractFilePath(edt8.Text)), nil, SW_NORMAL);
  end;
end;

【问题讨论】:

  • 枚举IShellWindows,搜索SWC_EXPLORER项目,如果找到,询问其服务提供商IShellViewIFolderViewIPersistFolder2.GetCurFolder是否返回您感兴趣的文件夹。如有必要,您甚至可以获取其窗口句柄以将窗口置于前面。参见例如this article 寻求灵感。
  • 谢谢@Victoria,会找到的。对我来说听起来很新:)
  • 不客气!如果您需要有关代码示例的帮助,我可以尝试为您制作一些。让我知道 ;)
  • 读了一会儿,是的,我需要一个例子。整个阅读对于这项任务来说是非常过分的。谢谢。
  • @Victoria 应该作为答案而不是评论发布。

标签: delphi windows-explorer


【解决方案1】:

IShellWindows::FindWindowSW 方法

我想说,有一个很好的方法FindWindowSW 应该找到一个现有的 Shell 窗口,其中也包括 Windows 资源管理器窗口。因此,希望我能轻松找到现有的窗口,我编写了以下代码:

uses
  ActiveX, ShlObj, SHDocVw, ComObj;

function IDListFromPath(const Path: WideString): PItemIDList;
var
  Count: ULONG;
  Attributes: ULONG;
  ShellFolder: IShellFolder;
begin
  OleCheck(SHGetDesktopFolder(ShellFolder));
  OleCheck(ShellFolder.ParseDisplayName(0, nil, PWideChar(Path), Count, Result, Attributes));
end;

function GetExplorerHandle(const Path: WideString): HWND;
var
  IDList: PItemIDList;
  Unused: OleVariant;
  Location: OleVariant;
  ShellWindows: IShellWindows;
begin
  OleCheck(CoCreateInstance(CLASS_ShellWindows, nil, CLSCTX_LOCAL_SERVER, IID_IShellWindows, ShellWindows));
  Unused := Unassigned;
  IDList := IDListFromPath(Path);
  PVariantArg(@Location).vt := VT_VARIANT or VT_BYREF;
  PVariantArg(@Location).pvarVal := PVariant(IDList);
  ShellWindows.FindWindowSW(Location, Unused, SWC_EXPLORER, Integer(Result), SWFO_INCLUDEPENDING);
end;

但它永远不会找到具有给定文件夹路径的 Windows 资源管理器窗口(它总是返回 0)。我使用SWC_EXPLORER 类仅搜索 Windows 资源管理器窗口,构建绝对 ID 列表,使用适当的 VT_VARIANT | VT_BYREF 位置变量(至少我希望如此,如果没有,请告诉我)。而且我还尝试通过包含 SWFO_NEEDDISPATCH 选项返回IDispatch(方法总是返回 nil 引用)。所以我放弃了这种方法(还没有找到任何例子)。

IShellWindows 枚举

以下代码的灵感来自this articlethis example。这是一个方案:

1. IShellWindows.Item(n)
2. ⤷ IDispatch.QueryInterface(IWebBrowserApp)
3.   ⤷ IWebBrowserApp.QueryInterface(IServiceProvider)
4.     ⤷ IServiceProvider.QueryService(STopLevelBrowser, IShellBrowser)
5.       ⤷ IShellBrowser.QueryActiveShellView
6.         ⤷ IShellView.QueryInterface(IFolderView)
7.           ⤷ IFolderView.GetFolder(IPersistFolder2)
8.             ⤷ IPersistFolder2.GetCurFolder
9.               ⤷ ITEMIDLIST

还有一些描述:

  1. 首先获取IShellWindows 接口引用并迭代其项。

  2. 对于每个项目,IShellWindows 接口返回窗口的IDispatch 接口,然后您可以查询 IWebBrowserApp 接口参考。

  3. 获得的 IWebBrowserApp 接口(文档请参阅IWebBrowser2,因为它是他们的实现)除了其他人还提供有关主机窗口的信息,例如句柄,以后可以用于将窗口置于前台。不过,我们需要更深入。因此,让我们查询IServiceProvider 接口(它是获取给定服务的接口的访问器)的接口引用。

  4. 现在从最顶层的浏览器实现服务查询其IShellBrowser 接口。对于我们的目标来说,这个接口的引用仍然没有意义。

  5. 获得的IShellBrowser查询显示的Shell视图对象。

  6. 现在我们终于可以说,如果迭代的 Shell 窗口不是 Internet Explorer 窗口。到目前为止,他们已经实现了通用接口。现在如果我们在获取到的IShellView 中查询IFolderView 接口并且成功,则不是Internet Explorer,我们可以继续。

  7. 查询获取到的IFolderView对当前显示的文件夹对象的IPersistFolder2接口的引用。

  8. 如果我们成功了,并且我们得到了IPersistFolder2 引用,那么让我们获取当前文件夹对象的ITEMIDLIST

  9. 如果我们在最后一步成功了,我们有一个Windows资源管理器实例(或相同的接口实现者)当前显示的文件夹的ITEMIDLIST,我们最终可以检查获得的ITEMIDLIST是否等于到我们为输入路径解析的那个。如果是,则将该窗口置于前台,如果不是,则继续下一次迭代。

这是一个 Delphi 代码。我不知道你的 Delphi 版本需要多少;这是我对 D2009 的最低要求(从 Windows SDK 10.0.15063.0 手动翻译)。这不是一个最好的例子。在实际代码中,您可能更喜欢将其包装到一个类中并拥有更灵活的界面,但这取决于您的设计偏好。最后,如果你的 Delphi 比 2009 年新,你可能不需要导入的原型,如果旧的,你可能会丢失一些:

uses
  ActiveX, ShlObj, SHDocVw, ComObj;

{ because of Win32Check }
{$WARN SYMBOL_PLATFORM OFF}
const
  IID_IFolderView: TGUID = '{CDE725B0-CCC9-4519-917E-325D72FAB4CE}';
  IID_IPersistFolder2: TGUID = '{1AC3D9F0-175C-11D1-95BE-00609797EA4F}';
  IID_IServiceProvider: TGUID = '{6D5140C1-7436-11CE-8034-00AA006009FA}';
  SID_STopLevelBrowser: TGUID = '{4C96BE40-915C-11CF-99D3-00AA004AE837}';

type
  IFolderView = interface(IUnknown)
  ['{CDE725B0-CCC9-4519-917E-325D72FAB4CE}']
    function GetCurrentViewMode(out pViewMode: UINT): HRESULT; stdcall;
    function SetCurrentViewMode(ViewMode: UINT): HRESULT; stdcall;
    function GetFolder(const riid: TIID; out ppv): HRESULT; stdcall;
    function Item(iItemIndex: Integer; out ppidl: PItemIDList): HRESULT; stdcall;
    function ItemCount(uFlags: UINT; out pcItems: Integer): HRESULT; stdcall;
    function Items(uFlags: UINT; const riid: TIID; out ppv): HRESULT; stdcall;
    function GetSelectionMarkedItem(out piItem: Integer): HRESULT; stdcall;
    function GetFocusedItem(out piItem: Integer): HRESULT; stdcall;
    function GetItemPosition(pidl: PItemIDList; out ppt: TPoint): HRESULT; stdcall;
    function GetSpacing(var ppt: TPoint): HRESULT; stdcall;
    function GetDefaultSpacing(out ppt: TPoint): HRESULT; stdcall;
    function GetAutoArrange: HRESULT; stdcall;
    function SelectItem(iItem: Integer; dwFlags: DWORD): HRESULT; stdcall;
    function SelectAndPositionItems(cidl: UINT; var apidl: PItemIDList; var apt: TPoint; dwFlags: DWORD): HRESULT; stdcall;
  end;

  EShObjectNotFolder = class(Exception);

function ILGetSize(pidl: PItemIDList): UINT; stdcall;
  external 'shell32.dll' name 'ILGetSize';
function ILIsEqual(pidl1: PItemIDList; pidl2: PItemIDList): BOOL; stdcall;
  external 'shell32.dll' name 'ILIsEqual';
function InitVariantFromBuffer(pv: Pointer; cb: UINT; out pvar: OleVariant): HRESULT; stdcall;
  external 'propsys.dll' name 'InitVariantFromBuffer';
function CoAllowSetForegroundWindow(pUnk: IUnknown; lpvReserved: Pointer): HRESULT; stdcall;
  external 'ole32.dll' name 'CoAllowSetForegroundWindow';

resourcestring
  rsObjectNotFolder = 'Object "%s" is not a folder.';

{ this parses the input folder path and creates ITEMIDLIST structure if the given
  folder path is a valid absolute path to an existing folder }
function GetFolderIDList(const Folder: string): PItemIDList;
const
  SFGAO_STREAM = $00400000;
var
  Count: ULONG;
  Attributes: ULONG;
  ShellFolder: IShellFolder;
begin
  OleCheck(SHGetDesktopFolder(ShellFolder));
  Attributes := SFGAO_FOLDER or SFGAO_STREAM;
  OleCheck(ShellFolder.ParseDisplayName(0, nil, PWideChar(WideString(Folder)), Count, Result, Attributes));
  if not ((Attributes and SFGAO_FOLDER = SFGAO_FOLDER) and (Attributes and SFGAO_STREAM <> SFGAO_STREAM)) then
  begin
    CoTaskMemFree(Result);
    raise EShObjectNotFolder.CreateFmt(rsObjectNotFolder, [Folder]);
  end;
end;

{ translated from the link mentioned in this comment; D2009 does not allow me to
  create an OleVariant of type VT_ARRAY|VT_UI1 which is needed for the Navigate2
  method so I've imported and used the InitVariantFromBuffer function here
  https://msdn.microsoft.com/en-us/library/windows/desktop/gg314982(v=vs.85).aspx }
procedure OpenNewExplorer(IDList: PItemIDList);
var
  Location: OleVariant;
  WebBrowser: IWebBrowser2;
begin
  OleCheck(CoCreateInstance(CLASS_ShellBrowserWindow, nil, CLSCTX_LOCAL_SERVER, IID_IWebBrowser2, WebBrowser));
  OleCheck(CoAllowSetForegroundWindow(WebBrowser, nil));
  OleCheck(InitVariantFromBuffer(IDList, ILGetSize(IDList), Location));
  try
    WebBrowser.Navigate2(Location, Unassigned, Unassigned, Unassigned, Unassigned);
  finally
    VariantClear(Location);
  end;
  WebBrowser.Visible := True;
end;

{ translated from the link mentioned in this comment
  https://blogs.msdn.microsoft.com/oldnewthing/20040720-00/?p=38393 }
procedure BrowseInExplorer(const Folder: string);
var
  I: Integer;
  WndIface: IDispatch;
  ShellView: IShellView;
  FolderView: IFolderView;
  SrcFolderID: PItemIDList;
  CurFolderID: PItemIDList;
  ShellBrowser: IShellBrowser;
  ShellWindows: IShellWindows;
  WebBrowserApp: IWebBrowserApp;
  PersistFolder: IPersistFolder2;
  ServiceProvider: IServiceProvider;
begin
  SrcFolderID := GetFolderIDList(Folder);
  try
    OleCheck(CoCreateInstance(CLASS_ShellWindows, nil, CLSCTX_LOCAL_SERVER, IID_IShellWindows, ShellWindows));
    { iterate all Shell windows }
    for I := 0 to ShellWindows.Count - 1 do
    begin
      WndIface := ShellWindows.Item(VarAsType(I, VT_I4));
      { do not use OleCheck here; windows like Internet Explorer do not implement
        all the interfaces; it is the way to distinguish Windows Explorer windows
        actually; so let's get all the references and if we succeed, check if the
        obtained folder equals to the passed one; if so, bring that window to top
        and exit this procedure }
      if Assigned(WndIface) and
        Succeeded(WndIface.QueryInterface(IID_IWebBrowserApp, WebBrowserApp)) and
        Succeeded(WebBrowserApp.QueryInterface(IID_IServiceProvider, ServiceProvider)) and
        Succeeded(ServiceProvider.QueryService(SID_STopLevelBrowser, IID_IShellBrowser, ShellBrowser)) and
        Succeeded(ShellBrowser.QueryActiveShellView(ShellView)) and
        Succeeded(ShellView.QueryInterface(IID_IFolderView, FolderView)) and
        Succeeded(FolderView.GetFolder(IID_IPersistFolder2, PersistFolder)) and
        Succeeded(PersistFolder.GetCurFolder(CurFolderID)) and
        ILIsEqual(SrcFolderID, CurFolderID) then
      begin
        { restore the window if minimized, try to bring it to front and exit this
          procedure }
        if IsIconic(WebBrowserApp.HWnd) then
          Win32Check(ShowWindow(WebBrowserApp.HWnd, SW_RESTORE));
        {$IFNDEF IBelieveThatIWebBrowserAppVisiblePropertyBringsWindowToFront}
        Win32Check(SetForegroundWindow(WebBrowserApp.HWnd));
        {$ELSE}
        OleCheck(CoAllowSetForegroundWindow(WebBrowserApp, nil));
        WebBrowserApp.Visible := True;
        {$ENDIF}
        Exit;
      end;
    end;
    { the procedure was not exited, hence an existing window was not found, so go
      and open the new one }
    OpenNewExplorer(SrcFolderID);
  finally
    CoTaskMemFree(SrcFolderID);
  end;
end;
{$WARN SYMBOL_PLATFORM ON}

可能的用法:

BrowseInExplorer('C:\MyFolder');

【讨论】:

  • 您可以将Folder 传递给OpenNewExplorer 而不是PItemIDList 参数,并避免InitVariantFromBuffer - IWebBrowser2.Navigate2 接受BSTR URL 作为参数。不过+1!顺便说一句,我不确定我是否完全理解了 IBelieveThatIWebBrowserAppVisiblePropertyBringsWindowToFront 指令……它为什么存在,它有什么用? CoAllowSetForegroundWindow 似乎工作得很好。
  • @kobik,再次感谢!该指令说明了一切 :) IWebBrowserApp 的实现者似乎是 IWebBrowser2 (不确定,它用于链接的官方示例中)并且没有人明确表示启用 Visible 属性将带来主机窗口也位于前面(在 Win 7 上对我有用)。而CoAllowSetForegroundWindow 很可能被使用,因为Visible 属性在其设置器中仅调用SetForegroundWindow 并且需要为此特权。来自this example
  • @kobik,当然你对 URL 的看法是对的(我正在考虑),但是解析该字符串将比传递已经解析的 ID 列表更昂贵(这需要额外的一次导入和函数调用;也许较新的 Delphi 准备好创建这种变体类型,我不能说)。但是,很遗憾FindWindowSW 方法并没有像我预期的那样工作(它返回S_OK,但仅此而已,返回的窗口调度是nil 和输出句柄0)。我试图从迭代中直接传递 ID 列表,但没有改变。当我削弱那个变体时,调用失败了。
  • 谢谢你!关于我知道的单位。我试图展示哪些需要添加到创建表单单元时自动使用的那些。也许有重复的东西。您可以尝试注释掉定义的原型,看看项目是否仍然可以编译。我的意思是,EMBT 可以添加例如IID_IPersistFolder2常量、IFolderView接口、ILGetSize函数等导入Delphi XE。
  • 我将这 3 个项目注释为您的方向,它仍在编译,并且可以正常工作。再次感谢。我获得了新的教训:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-12
  • 2012-06-25
  • 2018-08-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多