检查Windows资源管理器是否已在给定路径上打开

时间:2017-08-01 11:13:14

标签: delphi windows-explorer

如何查看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;

1 个答案:

答案 0 :(得分:4)

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. 获取显示的Shell视图对象的IShellBrowser查询。

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

  7. 查询当前显示的文件夹对象的IFolderView界面的获取IPersistFolder2参考。

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

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

  10. 这是一个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');