views:

224

answers:

4

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 back 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.

A: 

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 .

Sheng Jiang 蒋晟
Perhaps he wants his program to work on pre-Vista platforms and would like to abstract this part of the code away from the rest so he can just use IExplorerBrowser. That's what we call "good design."
jeffamaphone
Unfortunately, our customer base is very slow to adopt new technology. They'd be using Windows 95 if they could! ;) Our application already requires at least Windows 2000 due to other OS constraints, but that's already a stretch. Suddenly jumping forward 2 OSes to Vista (which is widely seen as a horrible mess by many in the general population) would be beyond the pale.
Mordachai
Mordachai
Then you can start a Vista dialog if you detect you are running under Vista. This is what winform 2.0's file dialog is doing (after installing SPs, of course, the original version of .Net 2.0 predates Vista).
Sheng Jiang 蒋晟
Mordachai
I am confused, why you say it is not possible? As I said Windows Forms 2.0 SP2 display a Vista file dialog when running under Vista and display a win2000 file dialog when running under 2000/XP. I would guess it can display a win9x file dialog if it still supports Win98 like the RTM version does. It is a matter of platform detection and run platform-specific code after that. What you need to do is to load different UI (perhaps in another dll) on different platforms.
Sheng Jiang 蒋晟
+1  A: 

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.

jeffamaphone
A: 

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! ;)

Mordachai
A: 

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.

Mark Ransom
Thanks Mark. I was really just adding an "answer" to myself for another person who had written me asking where I went with all this. And yes, you're exactly correct: all I really needed was to be able to directly get at the underlying objects that comprise the standard file dialogs, and I have since discovered various ways which allow me to. MFC is purely a convenience given our code base.
Mordachai