How to implement IExplorerBrowser in MFC
Apparently, there's a fairly easy way to host Explorer in your app starting with Vista: http://www.codeproject.com/KB/vista/ExplorerBrowser.aspx
However, that interface is only available starting with Vista.
I see that there is another way to do it: "going all the way b开发者_开发问答ack to 95, but it requires more work - implement IExplorerBrowser and obtain a view from the data source via IShellFolder::CreateViewObject(IID_IShellView)"
So I'd like to go this latter route: implement IExplorerBrowser.
Where do I get a IShellFolder * from to get the ball rolling in the first place? How do I specify the host window to house the shell view control? How do I specify the bounds rect for the shell view (and resize it)?
Is there a comprehensive set of docs - or a whitepages - someplace that documents these interfaces for the Windows Shell? The information that I've gleaned so far seems to be very fractured, with a few examples that are very outdated and won't even compile (they require extensive rewriting to the current version of ATL), and no examples that I can find for MFC at all.
You can get the ball rolling by first calling SHGetDesktopFolder(). This will give you the IShellFolder for the desktop. Then call ISF::BindToObject() to get the IShellFolder for the particular sub-folder that you want a view of. If you don't have a PIDL for the child folder you want, you can call SHParseDisplayName() to get that PIDL.
Unfortunately, I never did end up going this route. Instead, I adapted the CFileDialog to achieve what I wanted, in an XP..Windows 7 compatible way.
The crux of the solution is to obtain the IShellBrowser* instance from the common dialog control:
// return the IShellBrowser for the common dialog
// NOTE: we force CComPtr to create a new AddRef'd copy (since the one that this gives us is synthesized, and hasn't had an AddRef on our behalf)
CComPtr<IShellBrowser> GetShellBrowser() const { return (IShellBrowser*)::SendMessage(GetCommonDialogHwnd(), CDM_GETISHELLBROWSER, 0, 0); }
In order to do fancier stuff (like figure out what really is selected - what is its true identity regardless of whether the user has hidden file extensions or not) - I use the resulting IShellBrowser*.
For example:
//////////////////////////////////////////////////////////////////////////
// Get display name of item in file open dialog. Flags tell how.
// SHGDN_FORPARSING gets the full path name even when user has
// checked `Hide extensions for known file types` in Explorer.
//
CString CMFCToolboxAdvancedFileDialog::GetDisplayNameOfItem(int nItem) const
{
// get the item ID of the given item from the list control
LPITEMIDLIST pidlAbsolute = GetItemIDListOf(nItem);
// no PIDL = no display name
if (!pidlAbsolute)
return "";
// get the display name of our item from the folder IShellFolder interface
CString path = GetDisplayNameOf(pidlAbsolute);
// deallocate the PIDL
ILFree(pidlAbsolute);
// return the pathname
return path;
}
Which calls:
// return the ITEMIDLIST for the item at the specified index in the list view (caller is responsible for freeing)
LPITEMIDLIST CMFCToolboxAdvancedFileDialog::GetItemIDListOf(UINT nItem) const
{
// This can only succeed if there is an IShellView currently (which implies there is a list control)
CListCtrl * pListCtrl = GetListCtrl();
if (!pListCtrl)
return NULL;
// Use undocumented method (the pidl is stored in the item data)
// NOTE: Much thanks to Paul DiLascia for this technique (worked up until Vista)
// http://www.dilascia.com/index.htm
if (LPCITEMIDLIST pidlChild = (LPCITEMIDLIST)pListCtrl->GetItemData(nItem))
{
// get PIDL of current folder from the common dialog
LRESULT len = ::SendMessage(GetCommonDialogHwnd(), CDM_GETFOLDERIDLIST, 0, NULL);
if (!len)
return NULL;
LPCITEMIDLIST pidlFolder = (LPCITEMIDLIST)CoTaskMemAlloc(len);
::SendMessage(GetCommonDialogHwnd(), CDM_GETFOLDERIDLIST, len, (LPARAM)(void*)pidlFolder);
// return the absolute ITEMIDLIST
return ILCombine(pidlFolder, pidlChild);
}
// Use another undocumented feature: WM_GETISHELLBROWSER
CComPtr<IShellBrowser> pShellBrowser(GetShellBrowser());
if (!pShellBrowser)
return NULL;
// attempt to get access to the view
CComPtr<IShellView> pShellView;
if (FAILED(pShellBrowser->QueryActiveShellView(&pShellView)))
return NULL;
// attempt to get an IDataObject of all items in the view (in view-order)
CComPtr<IDataObject> pDataObj;
if (FAILED(pShellView->GetItemObject(SVGIO_ALLVIEW|SVGIO_FLAG_VIEWORDER, IID_IDataObject, (void**)&pDataObj)))
return NULL;
// attempt to get the ITEMIDLIST from our clipboard data object
const UINT cfFormat = RegisterClipboardFormat(CFSTR_SHELLIDLIST);
FORMATETC fmtetc = { cfFormat, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
ClipboardStorageMedium stgmed;
if (FAILED(pDataObj->GetData(&fmtetc, &stgmed)))
return NULL;
// cast to the actual data requested
CIDA * pida = (CIDA*)stgmed.hGlobal;
// ensure we have that index
ASSERT(pida->cidl > nItem);
if (nItem >= pida->cidl)
return NULL;
// find the data for the item requested
const ITEMIDLIST * pidlParent = GetPIDLFolder(pida);
const ITEMIDLIST * pidlChild = GetPIDLItem(pida, nItem);
// return the absolute PIDL
return ILCombine(pidlParent, pidlChild);
}
Which calls:
// NOTE: this is the only way I know to get the actual list control!
CListCtrl * GetListCtrl() const
{
// return &GetListView()->GetListCtrl();
// we have to be a window to answer such a question
ASSERT(IsWindow(GetCommonDialogHwnd()));
HWND hwnd = ::GetDlgItem(GetCommonDialogHwnd(), IDC_FILE_LIST_VIEW);
if (hwnd)
return static_cast<CListCtrl*>(CListCtrl::FromHandle(::GetWindow(hwnd, GW_CHILD)));
return NULL;
}
Well, hopefully that gives you the idea, and you can go from here. G/L! ;)
You don't really want to implement IExplorerBrowser, you want to know how to work with IShellView directly.
jeffamaphone's answer should suffice for getting the initial interface from which to obtain the IShellView. After that the IShellView::CreateViewWindow method is probably a good place to get started.
By the way, MFC is probably irrelevant to this process. Use the ATL smart pointers CComPtr or CComQIPtr to hold the interface pointers. MFC is a wrapper for pure Windows objects, but the COM interfaces hide all that from you.
Doing so could make some shell namespace extensions think they are running on Vista and trigger undesired results.
Why you think you need to implement IExplorerBrowser for Window versions earlier than Vista? Who would be your interface's clients? This interface is guarded in Windows SDK header files to prevent it to be used in earlier versions.
There are some shell view hosting examples at http://www.codeproject.com/KB/shell/. I am afraid shell view hosting in earlier versions is not as easy as using Vista's IExplorerBrowser .
精彩评论