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:
complete with context menu handlers:
Except that when i click Open, rather than sending me a BrowseObject
event through the IShellBrowser
interface i supplied, it opens a new window:
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.
开发者_如何学JAVA 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
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.
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.
精彩评论