0

I have a namespace extension, which provides a virtual view of files/folders in a server.

In the IContextMenu::QueryContextMenu() I have added some of the custom menu items.

I have also set couple of SGAOF flags in the IShellFolder::GetAttributesOf() to get the rename, delete, and properties, in the context menu.

Is there any way I can get the "Send To" option in the context menu for items in my namespace extension? and How do I handle these commands once these are enabled?. Please advise.

This is the code I tried as Denis Anisimov suggested

    const CLSID SendToCLSID = { 0x7BA4C740, 0x9E81, 0x11CF, { 0x99, 0xD3, 0x00, 0xAA, 0x00, 0x4A, 0xE8, 0x37 } };

    HRESULT CMyNSEContextMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder , IDataObject *pDataObj, HKEY  hkeyProgID )
    {
        OutputDebugString(L"CMyNSEContextMenu::Initialize\n");
        //Other initialization code
        ...
        ...

        if (_pdtobj)
        {
            _pdtobj->Release();
            _pdtobj = NULL;
        }

        _mpidlFolder = pidlFolder;
        _pdtobj = pDataObj;
        if (pDataObj)
        {
            _pdtobj->AddRef();
            CoCreateInstance(SendToCLSID, NULL, CLSCTX_INPROC_SERVER, IID_IContextMenu, (LPVOID*)&_pSendToMenu);
        }
        return S_OK;
    }

    HRESULT CMyNSEContextMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT  idCmdLast , UINT  uFlags )
    {
        OutputDebugString(L"CMyNSEContextMenu::QueryContextMenu\n");
        UNREFERENCED_PARAMETER(indexMenu);
        UNREFERENCED_PARAMETER(idCmdFirst);
        //Get File Name
        IShellItemArray *psia=NULL;
        HRESULT    hr;
        USHORT items = 0;

        //Adding other menu items


        AddMenuItem(hmenu, 
                    indexMenu++, 
                    idCmdFirst + MENUVERB_XXX, 
                    IDS_COMMAND_XXX, 
                    IDB_XXX);
        items++;

        IShellExtInit *pShellExtInitSendTo = NULL;

        _pSendToMenu->QueryInterface(IID_IShellExtInit, (LPVOID*)&pShellExtInitSendTo);
        pShellExtInitSendTo->Initialize(NULL, _pdtobj, 0); // your IDataObject with CFSTR_SHELLIDLIST format)
        hr = _pSendToMenu->QueryContextMenu(hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
        if (SUCCEEDED(hr))
        {
            items += HRESULT_CODE(hr);
        }

        return MAKE_HRESULT(SEVERITY_SUCCESS, 0, (USHORT)(items));
    }

    HRESULT CMyNSEContextMenu::HandleMenuMsg(
        UINT uMsg,
        WPARAM wParam,
        LPARAM lParam
        )
    {
        IContextMenu2 *pSendToMenu = NULL;
        _pSendToMenu->QueryInterface(IID_IContextMenu2, (LPVOID*)&pSendToMenu);

        return pSendToMenu->HandleMenuMsg(uMsg,wParam,lParam);
    }

    HRESULT CMyNSEContextMenu::HandleMenuMsg2(
        UINT uMsg,
        WPARAM wParam,
        LPARAM lParam,
        LRESULT *plResult
        )
    {
        IContextMenu3 *pSendToMenu = NULL;
        _pSendToMenu->QueryInterface(IID_IContextMenu3, (LPVOID*)&pSendToMenu);
        return pSendToMenu->HandleMenuMsg2(uMsg, wParam, lParam, plResult);
    }
HRESULT CMyNSEContextMenu::GetCommandString(UINT_PTR  idCmd , UINT uType , UINT *  pRes , LPSTR  pszName , UINT  cchMax )
{
    OutputDebugString(L"CMyNSEContextMenu::GetCommandString\n");

    return _pSendToMenu->GetCommandString(idCmd, uType, pRes, pszName, cchMax);

}

The default context menu is created as part of GetUIObjectOf. and the instance of MyNSEContextMenu class is through the Classfactory.

HRESULT CMyNSEShellFolder::GetUIObjectOf(HWND hwnd, UINT cidl, PCUITEMID_CHILD_ARRAY apidl,
                                             REFIID riid, UINT * /* prgfInOut */, void **ppv)
{
    OutputDebugString(L"CMyNSEShellFolder::GetUIObjectOf\n");
    *ppv = NULL;
    HRESULT hr = E_NOINTERFACE;

    if (riid == IID_IContextMenu)
    {
        // The default context menu will call back for IQueryAssociations to determine the
        // file associations with which to populate the menu.
        DEFCONTEXTMENU const dcm = { hwnd, NULL, m_pidl, static_cast<IShellFolder2 *>(this),
                               cidl, apidl, NULL, 0, NULL };
        hr = SHCreateDefaultContextMenu(&dcm, riid, ppv);
    }   
    //Others
    ....
    ....
    else if (riid == IID_IQueryAssociations)
    {
            else
            {
                ASSOCIATIONELEMENT const rgAssocItem[] =
                {
                    { ASSOCCLASS_PROGID_STR, NULL, L"MyNSE_Type"},
                };
                hr = AssocCreateForClasses(rgAssocItem, ARRAYSIZE(rgAssocItem), riid, ppv);
            }
    }
    ...
    ...
    return hr;
}

//Called from the class factory     
HRESULT CMyNSEContextMenu_CreateInstance(REFIID riid, void **ppv)
{
    *ppv = NULL;
    CMyNSEContextMenu* pContextMenu = new (std::nothrow) CMyNSEContextMenu();
    HRESULT hr = pContextMenu ? S_OK : E_OUTOFMEMORY;
    if (SUCCEEDED(hr))
    {
        hr = pContextMenu->QueryInterface(riid, ppv);
        pContextMenu->Release();
    }
    return hr;
}

Related registries written are as follows

HKEY_LOCAL_MACHINE,   L"Software\\Classes\\CLSID\\%s",                  szContextMenuClassID,    NULL,                   (LPBYTE)g_szExtTitle,       REG_SZ,
HKEY_LOCAL_MACHINE,   L"Software\\Classes\\CLSID\\%s\\InprocServer32",  szContextMenuClassID,    NULL,                   (LPBYTE)L"%s",              REG_SZ,
HKEY_LOCAL_MACHINE,   L"Software\\Classes\\CLSID\\%s\\InprocServer32",  szContextMenuClassID,    L"ThreadingModel",      (LPBYTE)L"Apartment",       REG_SZ,

        HKEY_LOCAL_MACHINE, L"Software\\Classes\\CLSID\\%s\\ProgID", szFolderViewImplClassID, NULL, (LPBYTE)L"MyNSE_Type", REG_SZ,

// For performance, only context menu verbs that register this are considered when the user double-clicks.
HKEY_CLASSES_ROOT,   L"CLSID\\%s\\ShellEx\\MayChangeDefaultMenu",                szContextMenuClassID, NULL,  (LPBYTE)L"",                  REG_SZ,
// register the context menu handler under the MyNSE_Type type.
HKEY_CLASSES_ROOT,   L"MyNSE_Type\\shellex\\ContextMenuHandlers\\%s",  szContextMenuClassID, NULL,  (LPBYTE)szContextMenuClassID, REG_SZ,
  • Do you create your own context menu or use system implementation? – Denis Anisimov Apr 10 '14 at 14:39
  • Who calls CMyNSEContextMenu::Initialize? How is pDataObj passed to Initialize created? – Denis Anisimov Apr 18 '14 at 04:41
  • It is IShellExtInit::Initialize of my class that gets called from the windows shell. Here is the call stack shell32.dll!IShellExtInit_Initialize shell32.dll!HDXA_QueryContextMenu shell32.dll!CDefFolderMenu::QueryContextMenu shell32.dll!CDefView::_DoContextMenuPopup shell32.dll!CDefView::OnSelectionContextMenu ExplorerFrame.dll!000007fef0cf14f7() ExplorerFrame.dll!000007fef0ceb6ef() shell32.dll!CDefView::_OnContextMenu – Madhusudan Narayan Apr 18 '14 at 06:46
  • What your shell extension return when explorer calls IShellFolder::GetUIObjectOf with IID_IContextMenu? How object which implements IContextMenu is created? – Denis Anisimov Apr 18 '14 at 08:42
  • @DenisAnisimov updated additional info in the question – Madhusudan Narayan Apr 18 '14 at 11:41
  • Do you increase idCmdFirst before _pSendToMenu->QueryContextMenu? Is "static_cast(this)" correct? psf must be IShellFolder. – Denis Anisimov Apr 18 '14 at 16:00
  • @DenisAnisimov I tried with and without increasing idCmdFirst; and no change in result. Also tried with static_cast; still no luck – Madhusudan Narayan Apr 22 '14 at 13:41
  • What will be if inside CMyNSEContextMenu::QueryContextMenu you don`t add your menu items? – Denis Anisimov Apr 22 '14 at 20:40

2 Answers2

2

SendTo is just simple shell extension which implements IContextMenu(2,3). CLSID of extension is {7BA4C740-9E81-11CF-99D3-00AA004AE837} in Windows 7 (dont forget to check correct CLSID in other Windows versions you want to support). So just use something like this:

function TMenuWithSentTo.QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult;
const
  SendToCLSID: TGUID = '{7BA4C740-9E81-11CF-99D3-00AA004AE837}';
var
  ShellExtInit: IShellExtInit;
begin
  Result := 0;

  // Add you menu items here

  CoCreateInstance(SendToCLSID, nil, CLSCTX_INPROC_SERVER, IContextMenu, FSendToMenu);
  FSendToMenu.QueryInterface(IShellExtInit, ShellExtInit);
  ShellExtInit.Initialize(nil, FDataObject, 0); // your IDataObject with CFSTR_SHELLIDLIST format 
  Result := Result + FSendToMenu.QueryContextMenu(Menu, indexMenu, idCmdFirst, idCmdLast, uFlags);

  // Add you menu items here
end;

function TMenuWithSentTo.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult;
begin
  if IsMyCommand(lpici) then
    begin
      // Process your command here
      Result := S_OK;
    end
  else
    Result := FSendToMenu.InvokeCommand(lpici);
end;

function TMenuWithSentTo.GetCommandString(idCmd: UINT_PTR; uFlags: UINT; pwReserved: PUINT; pszName: LPSTR; cchMax: UINT): HResult;
begin
  if IsMyCommandID(idCmd) then
    begin
      // Process your command here
      Result := S_OK;
    end
  else
    FSendToMenu.GetCommandString(idCmd);
end;

function TMenuWithSentTo.HandleMenuMsg(uMsg: UINT; WParam: WPARAM; LParam: LPARAM): HResult;
var
  SendToMenu2: IContextMenu2;
begin
  if IsMyMessage(uMsg, WParam, LParam) then
    begin
      // Process your command here
      Result := S_OK;
    end
  else
    begin
      FSendToMenu.QueryInterface(IContextMenu2, SendToMenu2);
      Result := SendToMenu2.HandleMenuMsg(uMsg, WParam, LParam);
    end;
end;

function TMenuWithSentTo.HandleMenuMsg2(uMsg: UINT; wParam: WPARAM; lParam: LPARAM; var lpResult: LRESULT): HResult;
var
  SendToMenu3: IContextMenu3;
begin
  if IsMyMessage(uMsg, WParam, LParam) then
    begin
      // Process your command here
      Result := S_OK;
    end
  else
    begin
      FSendToMenu.QueryInterface(IContextMenu3, SendToMenu3);
      Result := SendToMenu3.HandleMenuMsg(uMsg, WParam, LParam);
    end;
end;

But your should be ready that some command of SendTo will be hidden and some will not work correctly because some of them requests real files but you have virtual only.

Normal Send to menu:

enter image description here

Send to menu in NSE:

enter image description here

Denis Anisimov
  • 3,297
  • 1
  • 10
  • 18
  • Is this Delphi? Can you indicate how much of this is Delphi-specific? – david.pfx Apr 10 '14 at 23:03
  • This is Delphi but this fact changes nothing. It is "algorithm" only. – Denis Anisimov Apr 11 '14 at 02:51
  • Sorry, but I don't think someone writing in C++ could easily use this information to solve the problem. The type TMenuWithSentTo is not easily translated, and seems to have much of the important behaviour. – david.pfx Apr 11 '14 at 08:35
  • TMenuWithSentTo is object which implements IContextMenu, IContextMenu2 and IContextMenu3 interfaces. QueryContextMenu, InvokeCommand and GetCommandString are methods of IContextMenu, HandleMenuMsg is methods of IContextMenu2, HandleMenuMsg2 is methods of IContextMenu2. I think it is easy to understand main idea if @MadhusudanNarayan wrote IContextMenu implementation before. – Denis Anisimov Apr 11 '14 at 09:02
  • It would be better to edit your answer and include that information, which makes for a better answer for others to read. – david.pfx Apr 11 '14 at 09:50
  • That really helped. Now I am able to see the send to option. But then no items are seen in the Send to Submenu. Any idea, how I would solve this? – Madhusudan Narayan Apr 16 '14 at 05:16
  • @DenisAnisimov: I added the code in question, due to number of characters limited in the comments – Madhusudan Narayan Apr 18 '14 at 04:07
1

The simple way is to add a shortcut to the SendTo folder. To find that, simply paste %APPDATA%\Microsoft\Windows\SendTo into an Explorer window.

This only works if you have a command line program that takes a file name as an argument. If this is not what you need please edit your question with further details of how your extension code is accessed. Also, if this is C# please tag it so.


The registry key for SendTo can be found at HKEY_CLASSES_ROOT\AllFilesystemObjects\shellex\ContextMenuHandlers. The value since at least Vista and up to Windows 8 is {7BA4C740-9E81-11CF-99D3-00AA004AE837}. You can write a shell extension for this key. I have done this in the past, but don't have any source code that would help. The documentation is here: http://msdn.microsoft.com/en-us/library/windows/desktop/cc144067%28v=vs.85%29.aspx.

david.pfx
  • 10,520
  • 3
  • 30
  • 63
  • This is when you want to add something to the SendTo menu. OP is trying to have the standard SendTo menu (with whatever should be in it) displayed when right-clicking in his own "virtual items" in "virtual folders". This is kinda the other way around... – Simon Mourier Apr 10 '14 at 15:51