Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Host Windows Explorer in your applications using the new Vista hosting APIs

4.92/5 (37 votes)
27 Feb 2007CPOL9 min read 4   4.2K  
This article describes in detail the new IExplorerBrowser interface and the Explorer Browser object available in Windows Vista. The Explorer Browser object allows developers to host Windows Explorer in their applications.

Screenshot - ExplorerBrowser1.jpg

Introduction

A neat but barely documented feature of Windows Vista is the API support to host a Windows Explorer window. The image above shows the screenshot of the sample MFC application hosting the Explorer window. The COM based API is quite simple, and it allows a considerable control over the Windows Explorer window, in the following ways:

  1. Ability to control the different view types: Large Icon, Small Icon, Tiles, Details, Thumbnails etc.
  2. Automatic TravelLog: The user's browser history is maintained by the hosted Explorer window, and the hosting application can control the forward/back/up navigation.
  3. Ability to handle the Explorer window navigation events and take appropriate actions to control the navigation from within the hosted application.
  4. Ability to display navigational frames like that in the Explorer.

This feature is extremely useful in applications such as Installer IDEs, CD Burners, or any other application which shows a view of the Shell or file system and allows users to drag and drop items from the Shell view to the application window. Another use of this feature may be in a Visual Studio add-in that can display a file system view within the IDE. There might be more uses in your custom application with the only (big) drawback being that this feature is only available in Windows Vista.

Preparing the Build Environment

To start, you need to install the Microsoft Windows Vista SDK. The installation is available as a web install or a DVD image. The web install is a small exe which you run to start the installation, and the exe downloads the files from internet on an as needed basis. The web install normally takes around 2-3 hours to complete. However, if you want to install the SDK on multiple machines, or reinstall on the same machine at a later time, you should download the DVD image of the installation. You can download the SDK from this link.

Once you have downloaded and installed the SDK, you need to integrate the SDK with VS2005. The SDK ships with new C++ header and library files that contain the definition of the new APIs available in Windows Vista. Thus, you need to set the path of your VS2005 header and library files to point to the installation directory of VS2005. The easiest way to do that is to launch a bat file that comes with the Windows SDK installation. This can be launched from the Start menu, through a shortcut as shown below:

Launching the integration tool

Once you have run this tool, you can verify that the path of the include, header, and executable file of VS2005 has changed, by launching the Tools -> Options dialog:

VS 2005 Tools Options

An important thing to note here is that the executable files directory, include directory, and the library files directory should be modified. The Windows SDK ships with newer versions of tools such as the MIDL compiler, which are needed to build applications for Windows Vista, and so that path of executable files should also be set correctly.

Once you have verified that the build environment is set up properly, you can start developing for Windows Vista. To get all the Vista APIs, you need to define the macros WINVER, WIN32_WINNT, and WIN32_IE appropriately:

#define WINVER 0x0600
#define _WIN32_WINNT 0x0600
#define _WIN32_IE 0x0700    

Now, you can start using the Windows Vista APIs in your application. Let's move next to see how the Explorer can be hosted in an MFC application.

Hosting the Explorer Window in the View

In this sample application, we will host the Explorer window in a CView derived class. There is no restriction on what window the Explorer can be hosted on. For example, it can even be hosted in a dialog. I found it the hard way that the view should have the WS_CLIPSIBLINGS and WS_CLIPCHILDREN Windows styles set, otherwise the hosted Explorer has some painting issues. This can be done in the PreCreateWindow function.

BOOL CExplorerBrowserView::PreCreateWindow(CREATESTRUCT& cs)
{
    if (!CView::PreCreateWindow(cs))
        return FALSE;

    cs.style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;

    return TRUE;
}

The actual hosting API is a COM API, and the interface which we need to use is the IExplorerBrowser interface. A pointer to this interface should be a member of the CView derived class, and it should have the same lifetime as the CView derived class. In the sample application, a smart pointer to the IExplorerBrowser interface is made a member of the CExplorerBrowserView class.

class CExplorerBrowserView : public CView
{
private:
    CComPtr<IExplorerBrowser> m_spExplorerBrowser;

Next, we need to create the actual Explorer window as a child of the view window. This can be done in the OnCreate method which gets called when the window is created. Also, we can handle any error that may occur when hosting the browser. First, we need to instantiate the ExplorerBrowser COM class which has the CLSID of CLSID_ExplorerBrowser. Once the object is instantiated successfully, a call to the Initialize method creates the Explorer window as a child of the view window. The following is the code to create the Explorer window:

int CExplorerBrowserView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;

    HRESULT hr = SHCoCreateInstance(NULL, &CLSID_ExplorerBrowser, NULL, 
                                 IID_PPV_ARGS(&m_spExplorerBrowser));

    if (SUCCEEDED(hr))
    {
        if (theApp.ShowFrames())
            m_spExplorerBrowser->SetOptions(EBO_SHOWFRAMES);
        
        FOLDERSETTINGS fs;
        fs.fFlags = 0;
        fs.ViewMode = FVM_DETAILS;
        hr = m_spExplorerBrowser->Initialize(m_hWnd, CRect(0, 0, 0, 0), &fs);
    }

    return SUCCEEDED(hr) ? 0 : -1;
}

Notice the call to the SetOptions method before calling the Initialize method. The SetOptions allows you to specify more options on how the display should look like. For example, you can include frames when displaying the Explorer window. The frames consist of the main details view, the places bar, the folders tree, and the details pane, as shown in the screenshot below:

Framed Explorer Browser Views

The default is to not show the frames, and the view looks like the following:

NormalExplorer Browser Views

The call to SetOptions(EBO_SHOWFRAMES) displays the frames. Once the Initialize method is called, it is not possible to turn on or turn off the frames. If you intend to show frames dynamically, you have to destroy the Explorer browser and call the Initialize method again.

Cleaning Up After the View is Destroyed

When the view is destroyed, the Explorer browser object should be cleaned up. Note that merely releasing the COM interface pointer is not sufficient. You have to call IExplorerBrowser::Destroy in the OnDestroy method as shown below:

void CExplorerBrowserView::OnDestroy()
{
    CView::OnDestroy();
    m_spExplorerBrowser->Destroy();
}

Sizing the Explorer Browser Window

If the view consists of entirely the Explorer browser window, it needs to be sized to fit the view. The Explorer Browser object provides the SetRect method that can be used to resize the window. The code to resize is placed in the OnSize method of the view object, and is shown below:

void CExplorerBrowserView::OnSize(UINT nType, int cx, int cy)
{
    CView::OnSize(nType, cx, cy);

    m_spExplorerBrowser->SetRect(NULL, CRect(0, 0, cx, cy));
}

So far, we have learnt how to create the Explorer browser window, size it, and destroy it. But it still does not do anything interesting. Now in the next section, we will see how to navigate using the Explorer browser.

Navigating Using the Explorer Browser

The hosted Explorer can be navigated to any Shell folder which includes file system folders and non-file system folders like Network, Printers, and Control Panel. Because the Windows Shell items are not limited to just the file system, items in the Shell are not identified by the path, rather they are identified by item ID lists (PIDL). (The Item ID List structure is described in more detail in the Shell documentation available on MSDN.) The IExplorerBrowser provides a function named BrowseToIDList which accepts the item ID list of a Shell folder to which the browser navigates. To navigate to a file system path, we have to convert the file system path to an item ID list. The following code shows how the Explorer browser can be navigated to a folder specified by a file path.

bool CExplorerBrowserView::NavigateTo(LPCTSTR szPath)
{
    HRESULT hr = S_OK;
    LPITEMIDLIST pidlBrowse;

    if (FAILED(hr = SHParseDisplayName(szPath, NULL, &pidlBrowse, 0, NULL)))
    {
        ATLTRACE("SHParseDisplayName Failed! hr = %d\n", hr);
        return false;
    }

    if (FAILED(hr = m_spExplorerBrowser->BrowseToIDList(pidlBrowse, 0)))
        ATLTRACE("BrowseToIDList Failed! hr = %d\n", hr);

    ILFree(pidlBrowse);

    return SUCCEEDED(hr);
}

The NavigateTo function accepts the path of a folder. It first converts the folder to PIDL using the SHParseDisplayName function, and finally it calls the BrowseToIDList function to make the Explorer browser navigate to that folder. Finally, it frees the allocated PIDL by calling ILFree.

The Explorer browser also maintains a travel log of all the folders visited. It is possible to programmatically navigate back or forward. This can be done using the BrowseToIDList function as shown below:

void CExplorerBrowserView::OnViewBack()
{
    m_spExplorerBrowser->BrowseToIDList(NULL, SBSP_NAVIGATEBACK);
}

void CExplorerBrowserView::OnViewForward()
{
    m_spExplorerBrowser->BrowseToIDList(NULL, SBSP_NAVIGATEFORWARD);
}

Listening to Navigation Events

In addition to programmatic navigation, the Explorer browser also provides means to listen to navigation events which occur when the user navigates using the Explorer user interface (e.g., by double clicking on a folder). This can be done by supplying the Explorer browser object with an object implementing the IExplorerBrowserEvents. The CExplorerBrowserEvents class in the sample implements the IExplorerBrowserEvents and delegates to the main view class. Here is the code for CExplorerBrowserEvents:

class CExplorerBrowserEvents :
    public CComObjectRootEx<CComSingleThreadModel>,
    public IExplorerBrowserEvents
{
private:
    CExplorerBrowserView* m_pView;

public:
    BEGIN_COM_MAP(CExplorerBrowserEvents)
        COM_INTERFACE_ENTRY(IExplorerBrowserEvents)
    END_COM_MAP()

public:
    void SetView(CExplorerBrowserView* pView)
    {
        m_pView = pView;
    }

public:    
    STDMETHOD(OnNavigationPending)(PCIDLIST_ABSOLUTE pidlFolder)
    {
        return m_pView ? m_pView->OnNavigationPending(pidlFolder) : E_FAIL;
    }

    STDMETHOD(OnViewCreated)(IShellView *psv)
    {
        if (m_pView)
            m_pView->OnViewCreated(psv);
        return S_OK;
    }

    STDMETHOD(OnNavigationComplete)(PCIDLIST_ABSOLUTE pidlFolder)
    {
        if (m_pView)
            m_pView->OnNavigationComplete(pidlFolder);
        return S_OK;
    }

    STDMETHOD(OnNavigationFailed)(PCIDLIST_ABSOLUTE pidlFolder)
    {
        if (m_pView)
            m_pView->OnNavigationFailed(pidlFolder);
        return S_OK;
    }
};

All four events fired by the Explorer browser and handled in the CExplorerBrowserEvents class are delegated back to the hosting CExplorerBrowserView instance. The view itself declares the event handling methods as virtual so that they can be overridden by the derived class.

class CExplorerBrowserView : public CView
{
    protected:
    virtual HRESULT OnNavigationPending(PCIDLIST_ABSOLUTE pidlFolder);
    virtual void OnNavigationComplete(PCIDLIST_ABSOLUTE pidlFolder);
    virtual void OnViewCreated(IShellView *psv);
    virtual void OnNavigationFailed(PCIDLIST_ABSOLUTE pidlFolder);
    ...
}

Let's take a look at these event methods in a little more detail. Three of these methods take the PIDL of the folder as parameter.

  • The OnNavigationPending method is called when the navigation is initiated by either the user or programmatically. The actual navigation process can be canceled by returning a failure HRESULT from the method.
  • The OnNavigationComplete method is called when the navigation has completed; this method is the right place to update the UI so that it is in sync with the navigated folder.
  • The OnViewCreated method is called just after the navigation pending method. The Explorer destroys and recreates the list view window each time it navigates to a folder. This method takes a pointer to the IShellView interface which provides additional methods to query and control the view.
  • Finally, the OnNavigationFailed method is called if the folder navigation fails for some reason.

Once we have implemented the IExplorerBrowserEvents, we need to create an instance of the implementing class, CExplorerBrowserEvents in this case, and supply it to the Explorer browser object. This is done by calling the IExplorerBrowser::Advise method. This method should be called before navigating the browser to a particular folder. In the case of CExploreBrowserView, this function is called in the OnCreate function just after the call to Initialize.

int CExplorerBrowserView::OnCreate(LPCREATESTRUCT lpCreateStruct) {
    ....
    CComObject<CExplorerBrowserEvents>* pExplorerEvents;
    if (SUCCEEDED(CComObject<CExplorerBrowserEvents>::
                             CreateInstance(&pExplorerEvents)))
    {
        pExplorerEvents->AddRef();
        
        pExplorerEvents->SetView(this);
        m_spExplorerBrowser->Advise(pExplorerEvents, &m_dwAdviseCookie);
        
        pExplorerEvents->Release();
    }
}

The call to the Advise method ensures that the CExplorerBrowserEvents class receives a notification for each of the events. A matching call to the Unadvise methods stops the Explorer browser from notifying events to the object. The Advise method returns a DWORD cookie which can be supplied to the Unadvise method. The call to Unadvise is in the OnDestroy method of CView:

void CExplorerBrowserView::OnDestroy()
{
  CView::OnDestroy();
   
  if(m_dwAdviseCookie)
  {
      m_spExplorerBrowser->Unadvise(m_dwAdviseCookie);
  }

  m_spExplorerBrowser->Destroy();
}

Obtaining the List of Selected Items

In some applications, it might be necessary to obtain the selected items in the Explorer browser and take some action on them. To obtain the selected items is a two step process:

  1. Obtain an interface pointer to the IShellView interface by calling the GetCurrenView method.
    CComPtr<IShellView> spSV;
    if (SUCCEEDED(m_spExplorerBrowser->GetCurrentView(IID_PPV_ARGS(&spSV))))
  2. The IShellView pointer thus obtained can be used to get the selected items as an OLE DataObject.
    CComPtr<IDataObject> spDataObject;
    if (SUCCEEDED(spSV->GetItemObject(SVGIO_SELECTION, 
                  IID_PPV_ARGS(&spDataObject))))
  3. Finally, the data object can be used to get the selected items as shown below:
  4. //Code adapted from http://www.codeproject.com/shell/shellextguide1.asp
    FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT,
                      -1, TYMED_HGLOBAL };
    STGMEDIUM stg;
    stg.tymed =  TYMED_HGLOBAL;
    
    if (SUCCEEDED(spDataObject->GetData(&fmt, &stg)))
    {
        HDROP hDrop = (HDROP) GlobalLock ( stg.hGlobal );
    
        UINT uNumFiles = DragQueryFile ( hDrop, 0xFFFFFFFF, NULL, 0 );
        HRESULT hr = S_OK;
    
        for(UINT i = 0; i < uNumFiles; i++)
        {
            TCHAR szPath[_MAX_PATH];
            szPath[0] = 0;
            DragQueryFile(hDrop, i, szPath, MAX_PATH);
            
            if (szPath[0] != 0)
                arrSelection.Add(szPath);    
        }
        
        GlobalUnlock ( stg.hGlobal );
        ReleaseStgMedium ( &stg );

    The above code adds the path of the selected items to a CStringArray object.

The CExploreBrowserView class provides a method called GetSelectedItems which accepts a CStringArray reference; on return, the string array is filled with the path of each of the selected items.

Conclusion and Future Work

Windows Vista has lot of such nice goodies in the Shell, but unfortunately, they are not very well documented. I hope that this article and the sample will help someone to use the Explorer browser in their own applications. I selected MFC for this article because it was easy to develop the sample using MFC, but I am working on porting the application to .NET and C#. So stay tuned for future updates.

History

  • 2/27/2006 - Initially posted.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)