Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get embedded Explorer IShellView to be browsable (i.e. trigger BrowseObject event)

i am "embedding Windows Explorer" in my Win32 application. (Technically i am hosting a ShellView of a folder in my application, which is what Windows Explorer does).

The problem is that the view is never calling IShellBrowser.BrowseObject. Rather than asking me to navigate to a new location (through the BrowseObject event), the shell view is launching a copy of Windows Explorer to view the folder.

i want the default shell view (colloquially known as DefView) to be browsable.


Sample code tutorial

First we need to get the IShellFolder for some folder that i want to display. The simplest folder to get is the Desktop folder, since there is an SHGetDesktopFolder API for it:

folder: IShellFolder;

SHGetDesktopFolder({out} folder);

Next we ask the desktop folder to hand us its IShellView:

view: IShellView;

folder.CreateViewObject(Self.Handle, IID_IShellView, {out}view);

Now that we have the IShellView of the folder (as opposed to the IContextMenu or IExtractIcon), we now want to show the shell view by calling IShellView.CreateViewWindow:

hostRect: TRect; //where the view is to display itself
folderSettings: TFolderSettings; //display settings for the view
hwndView: HWND; //the newly created view's window handle

folderSettings.ViewMode := FVM_DETAILS; //details mode please, rather than icon/list/etc
folderSettings.fFlags := 0;
hostRect := Rect(20, 20, 660, 500); //the view can position itself there

view.CreateViewWindow(nil, folderSettings, shellBrowser, {var}hostRect, {out}hView);
view.UIActivate(SVUIA_ACTIVATE_NOFOCUS);

et voila, the recognizable shell listview, showing my desktop:

enter image description here

complete with context menu handlers:

enter image description here

Except that when i click Open, rather than sending me a BrowseObject event through the IShellBrowser interface i supplied, it opens a new window:

enter image description here

The same happens when i double-click.

How can i get Microsoft's DefView to be browsable?


Update ShellBrowser implementation

When creating an IShellView you must give it an object that implements IShellBrowser. This ShellBrowser object is how the view communicates back to the hosting container.

Of the 15 methods, i only look carefully at four - the rest can return E_NOTIMPL (which of course may be the problem):

{IShellBrowser}
  • BrowseObject(PCUIDLIST_RELATIVE pidl, UINT wFlags);

      //Informs Windows Explorer to browse to another folder.
      //This is the notification i want!
      Result := BrowseToAnotherFolder(pidl, wFlags);
    
  • GetControlWindow(UINT id, HWND *lphwnd);

      //Gets the window handle to a browser control.
      //Since i don't have a toolbar, tree, status or progress windows, return 0
      lphwnd := 0;
      Result := S_OK;
    
  • SendControlMsg(UINT id, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pret);

      //Sends control messages to either the toolbar or the status bar in a Windows 
      //From MSDN: Notes to Implementers
      //    If your Windows Explorer does not have these controls, you can return E_NOTIMPL.
      Result := E_NOTIMPL;
    
  • GetViewStateStream(DWORD grfMode, IStream **ppStrm);

      //Gets an IStream interface that can be used for storage of view-specific state information.
      Result := E_NOTIMPL; //i'm don't have a stream to give you
    
  • TranslateAcceleratorSB(LPMSG lpmsg, WORD wID);

      //Translates accelerator keystrokes intended for the browser's frame 
      //    while the view is active.
      Result := E_NOTIMPL; //i won't be doing any translating
    
  • OnViewWindowActive(IShellView *ppshv);

      //Called by the Shell view when the view window or one of its child 
      //    windows gets the focus or becomes active.
      Result := S_OK; //i got the notification, thanks
    
  • QueryActiveShellView(IShellView **ppshv);

      //Retrieves the currently active (displayed) Shell view object.
      ppshv := view;
      Result := S_OK; //i would never view another, you know that
    
  • EnableModelessSB(BOOL fEnable);

      //Tells Windows Explorer to enable or disable its modeless dialog boxes.
      Result := S_OK; //You want to enable modeless dialog boxes? Interesting.
    
  • InsertMenusSB(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths);

      //Allows the container to insert its menu groups into the composite
      //  menu that is displayed when an extended namespace is being viewed or used.
      Result := E_NOTIMPL; //i no has menus
    
  • RemoveMenusSB(HMENU hmenuShared);

      //Permits the container to remove any of its menu elements 
      //   from the in-place composite menu and to free all associated resources.
      Result := E_NOTIMPL; //i no has menus
    
  • SetMenuSB(HMENU hmenuShared, HOLEMENU holemenuRes, HWND hwndActiveObject);

      //Installs the composite menu in the view window.
      Result := E_NOTIMPL; //i no has menus
    
  • SetStatusTextSB

      //Sets and displays status text about the in-place object 
      //in the container's frame-window status bar.
      Result := E_NOTIMPL; //i no has status bar
    
  • SetToolbarItems(LPTBBUTTONSB lpButtons, UINT nButtons, UINT uFlags);

      //Adds toolbar items to Windows Explorer's toolbar.
      Result := E_NOTIMPL; //i no has toolbar
    

There's then the ancestor IOleWindow:

  • GetWindow([out] HWND *phwnd);

      //Retrieves a handle to one of the windows participating in 
      //   in-place activation (frame, document, parent, or in-place object window).
      phwnd := Self.Handle; //here's the handle, again, of your parent
      Result := S_OK; 
    
  • `ContextSensitiveHelp([in] BOOL fEnterMode);

      //Determines whether context-sensitive help mode should be entered 
      //during an in-place activation session.
    
      Result := S_OK; //Ok, thanks, i'll make a note of it
    

IServiceProvider

function TShellBrowser.QueryService(const rsid, iid: TGuid; out Obj): HResult;
var
    sb: IShellBrowser;
    s: string;
const
    SID_SInPlaceBrowser: TGUID = '{1D2AE02B-3655-46CC-B63A-285988153BCA}';
    SID_IShellBrowser: TGUID = '{000214E2-0000-0000-C000-000000000046}';
begin
    {
        This code is executed when you double click a folder.
        It's needed to implement inline browsing.
        If you double click a folder the default action of IShellBrowser is to open a new Windows Explorer.
        To open the folder in the current window you must implement IServiceProvider.

        http://blogs.msdn.com/b/ieinternals/archive/2009/12/30/windows-7-web-browser-control-will-not-browse-file-system.aspx
    }
    Result := E_NOINTERFACE; //Return $E_NOINTERFACE

    OutputDebugString(PChar('TShellBrowser.QueryService: '+IIDToString(rsid)));
    {
        Make a small change to your application to enable the filesystem object to navigate in-place within the WebOC
        when running on Windows 7.
        To do so, your hosting application will implement the IServiceProvider interface,
        and hand back the WebBrowser control’s SID_SShellBrowser when asked for SID_SInPlaceBrowser
    }
    if IsEqualGUID(rsid, SID_SInPlaceBrowser) then
    begin
        sb := Self as IShellBrowser;
        Pointer(Obj) := Pointer(sb);
        sb._AddRef;
        Result := S_OK;
    end;
end;

Bonus Reading

  • ex-MSFT Eric Law documents this behavior as an instantional change in Windows 7, and how to work around it. archive (Except it doesn't work)
  • fogbit having the same problem
  • more talk on how to implement IServiceProvider to respond to SID_SInPlaceBrowser

See also

  • Implementing IShellBrowser to host IShellView
like image 925
Ian Boyd Avatar asked Oct 08 '11 17:10

Ian Boyd


2 Answers

On Vista or higher you can switch to hosting ExplorerBrowser, from which you can QI IObjectWithSite and pass an object that implements IServiceProvier with services like SID_SShellBrowser or SID_SInPlaceBrowser.

On XP you probably need to handle DDE messages originated from your process as the default folder association is DDE back then.

like image 112
Sheng Jiang 蒋晟 Avatar answered Oct 02 '22 20:10

Sheng Jiang 蒋晟


Some missing code I used to reproduce this example.

TForm1 = class(TForm, IShellBrowser)

After that implement the IShellBrowser interface and get the FShellBrowser variable.

self.GetInterface(IID_IShellBrowser, FShellBrowser)

use FShellBrowser in:

FShellView.CreateViewWindow(FPreviousView, FFolderSettings, FShellBrowser, FHostRect, FViewHandle)

Clicking a directory only generates:

OnViewWindowActive

GetControlWindow

Another problem with this code is: How to communicate to Explorer that the form is resized and you want to resize the hosted Explorer. Explorer browser has the SetRect method.

I agree use IExplorerBrowser.

like image 24
Raf Bakker Avatar answered Oct 02 '22 20:10

Raf Bakker