views:

152

answers:

3

Motivation: Creating our own file dialog that looks & acts much like the std common dialog

Problem: How to obtain the view pull-down for the current folder/shell container

Apparent Dead Ends:

  • Query the IShellFolder for its IContextMenu < NULL interface pointer.
  • Query the IShellView for its IContextMenu < NULL interface pointer.
  • IShellFolder::CreateViewObject(IID_IContextMenu...) < very limited context menu (new).
  • IShellFolder::GetUIObjectOf(IID_IContextMenu...) < limited context menu (open, explore,...).
  • Implement IShellBrowser's InsertMenusSB, RemoveMenusSB, and SetMenuSB < The menu is never populated beyond what I populate it with

I have spent some time reading Implementing a Folder View and How to host an IContextMenu. This seems to indicate that the final approach above (implementing InsertMenuSB, ...) should work. The IShellView should be populating the shared menu for the IShellBrowser, including its View submenu, with the appropriate items. However, so far all I get from that is an empty menu (unless I populate it with items - in which case, I just get the items I populate it with).

Surely there is a way to do this. Windows Explorer arrives at the menu it displays (if you press down ALT on Vista or above) from somewhere. And I cannot imagine that this menu is statically built by Explorer itself - it surely is dynamically created somehow in concert with the currently displayed IShellView to allow for shell extensions to display the correct list of view options (and other menu options).

But the documentation on InsertMenuSB, RemoveMenuSB, and SetMenuSB is confusing. It seems to indicate that, as the container server, I should populate the supplied OLEMENUGROUPWIDTHS, "in elements 0, 2, and 4 to reflect the number of menu elements it provided in the File, View, and Window menu groups."

I have implemented the following to attempt to properly fulfill this contract:

HRESULT STDMETHODCALLTYPE ShellBrowserDlgImpl::InsertMenusSB(__RPC__in HMENU hmenuShared, /* [out][in] */ __RPC__inout LPOLEMENUGROUPWIDTHS lpMenuWidths)
{
    TRACE("IShellBrowser::InsertMenusSB\n");

    // insert our main pull-downs
    struct  
    {
     UINT id;
     LPCTSTR label;
    } pull_downs[] = {
     { FCIDM_MENU_FILE, "File" },
     { FCIDM_MENU_EDIT, "Edit" },
     { FCIDM_MENU_VIEW, "View" },
     { FCIDM_MENU_TOOLS, "Tools" },
     { FCIDM_MENU_HELP, "Help" },
    };
    for (size_t i = 0; i < countof(pull_downs); ++i)
    {
     VERIFY(AppendMenu(hmenuShared, MF_POPUP, pull_downs[i].id, pull_downs[i].label));
     ASSERT(GetMenuItemID(hmenuShared, i) == pull_downs[i].id);
    }

    // set the count of menu items we've inserted into each *group*
    lpMenuWidths->width[0] = 2; // FILE: File, Edit
    lpMenuWidths->width[2] = 2; // VIEW: View, Tools
    lpMenuWidths->width[4] = 1; // WINDOW: Help

    return S_OK;
}

Has anyone implemented an Explorer like project that properly exposes the current IShellView's menus to the end-user?

Is there documentation / examples on IOLEInPlaceFrame implementations that might shed some light on this murky subject?

Ugh!@ - I feel like I must be close - yet not close enough!

A: 

You are reimplementing a notoriously difficult control to get right, and one that many, many people know and have used since they started using Windows. Any failure to get it exactly right will annoy at least some subset of your users, and the definition of 'exactly right' is going to change from windows release to windows release.

Why can't you use the default one? What are you implementing that adds so much value to this dialog that the standard one is impossible to use?

DDaviesBrackett
Mordachai
+1  A: 

use SVGIO_BACKGROUND to get the background menu of the folder, which should have a view submenu. the index, name and the command id of the "view" menu item may vary between windows versions and local languages, so this is kind of hack.

Sheng Jiang 蒋晟
I don't suppose you have any ideas of how to ensure that I can get the view submenu regardless of OS I'm running under (both version and language).
Mordachai
In English versions of Windows you can search submenus for the one whose text is "View", and hope Microsoft won't change it to something else later. I doubt Microsoft would change it any time soon, but this is still undocumented behavior.
Sheng Jiang 蒋晟
A: 

For those who might be interested, here's the implementation of the given answer I'm using:

void ShellBrowserDlgImpl::ViewModeDropDown(const CPoint & pt)
{
    // ask the view for its context menu interface
    CComPtr<IContextMenu> pcm;
    if (FAILED(m_hresult = m_shell_view->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&pcm))) || !pcm)
     throw CLabeledException("Unable to query the context menu interface from the shell view: ");

    // create a blank menu to store it in
    CMenu menu;
    if (!menu.CreateMenu())
     throw CContextException("Unable to create an empty menu in which to store the context menu: ");

    // populate the context menu
    if (FAILED(m_hresult = pcm->QueryContextMenu(menu, 0, SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST, CMF_NORMAL)))
     throw CLabeledException("Unable to query the context menu for the current folder");

    // obtain the "view" submenu to use as our drop-down menu
    //HACK: we assume that the view pop-up is the first entry (true in English)
    //TODO: We need some way to scan for the correct submenu
    // if we knew of a given command that exists under view - we could FindMenuContaining()
    // of if we could scan for an invariant command name using a similar technique
    // or we could possibly...?
    CMenu * pViewMenu = menu.GetSubMenu(0);

    // get the proper orientation for the drop-menu
    UINT uFlags = ::GetSystemMetrics(SM_MENUDROPALIGNMENT) ? TPM_RIGHTALIGN|TPM_HORNEGANIMATION : TPM_LEFTALIGN|TPM_HORPOSANIMATION;

    // display the menu to the user
    BOOL nCmdID = ::TrackPopupMenu(*pViewMenu, TPM_RETURNCMD|uFlags, pt.x, pt.y, 0, m_shell_view_hwnd, NULL);

    // check if the user canceled the menu
    if (!nCmdID)
     return;

    // create the command to execute
    CMINVOKECOMMANDINFO ici = {0};
    ici.cbSize = sizeof(ici);
    ici.hwnd = m_shell_view_hwnd;
    ici.lpVerb = MAKEINTRESOURCE(nCmdID-1); //NOTE: not sure if the -1 is due to the position of the submenu we're pulling out, or something else - might be invalid for other OSes or languages
    if (FAILED(m_hresult = pcm->InvokeCommand(&ici)))
     throw CLabeledException("Unable to execute your command");
}
Mordachai