是不是不能在CHtmlView上下文菜单中添加我们自己的菜单项?

Is it not possible to add our own menu items on the CHtmlView context menu?

所以我一直回到 CodeProject 上的这篇文章:

https://www.codeproject.com/Articles/4758/How-to-customize-the-context-menus-of-a-WebBrowser

然后我在文章的顶部意识到了这句话:

The revised sample projects are using a new, much better customization approach that is going to be comprehensively discussed in the next update of this article, which will hopefully be ready in a couple of weeks. I am publishing this semi-documented and not fully-tested code, because I am having indications that some developers may need to have this code much sooner than the day of my next update. For each revised sample there is also a Readme.htm file that briefly describes how the sample works.

我以为我很难理解文章片段中的代码与下载的源代码!所以我读了自述文件,它说:

In MFC 7 CHtmlView has embedded support for IDocHostUIHandler, thus I simply override the CHtmlView::OnShowContextMenu method and afterwards I call the ::CustomShowContextMenu() function, (inside CustomMenus.cpp) which works like described in the section 5 of my original article.

所以,我决定在我的项目中添加我自己的函数覆盖:

HRESULT CChristianLifeMinistryHtmlView::OnShowContextMenu(DWORD dwID, LPPOINT ppt,
    LPUNKNOWN pcmdtReserved, LPDISPATCH pdispReserved)
{
    return CustomContextMenu(ppt, pcmdtReserved);
}

并且我添加了类似的自定义菜单功能:

HRESULT CustomContextMenu(POINT* ppt, IUnknown* pcmdtReserved)
{
    IOleWindow* oleWnd = NULL;
    HWND        hwnd = NULL;
    HMENU       hMainMenu = NULL;
    HMENU       hPopupMenu = NULL;
    HRESULT     hr = 0;
    INT         iSelection = 0;

    if ((ppt == NULL) || (pcmdtReserved == NULL))
        goto error;

    hr = pcmdtReserved->QueryInterface(IID_IOleWindow, (void**)&oleWnd);
    if ((hr != S_OK) || (oleWnd == NULL))
        goto error;

    hr = oleWnd->GetWindow(&hwnd);
    if ((hr != S_OK) || (hwnd == NULL))
        goto error;

    hMainMenu = LoadMenu(AfxGetInstanceHandle(),
        MAKEINTRESOURCE(IDR_MENU_HTML_POPUP));
    if (hMainMenu == NULL)
        goto error;

    hPopupMenu = GetSubMenu(hMainMenu, 0);
    if (hPopupMenu == NULL)
        goto error;

    // Show shortcut menu
    iSelection = ::TrackPopupMenu(hPopupMenu,
        TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
        ppt->x,
        ppt->y,
        0,
        hwnd,
        (RECT*)NULL);

    // Send selected shortcut menu item command to shell
    if (iSelection != 0)
        (void) ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);

error:

    if (hMainMenu != NULL)
        ::DestroyMenu(hMainMenu);

    return S_OK;
}

最后,我添加了一个菜单资源:

IDR_MENU_HTML_POPUP MENU
BEGIN
    POPUP "CustomPopup"
    BEGIN
        MENUITEM "View Source",                 2139
        MENUITEM SEPARATOR
        MENUITEM "Select All",                  31
    END
END

菜单 ID 值基于 IDM_ 版本,它们都有效。

然后我尝试使用我自己的事件处理程序将我自己的菜单项添加到该列表中,但它显示为已禁用。

在CHtmlView上下文菜单中不能添加我们自己的菜单项吗?

我想添加我自己的菜单项 "Print Preview",它又简单地向我的 parent "Editor" 发送一条消息来模拟点击 "Print Preview"。但是似乎添加到此菜单的任何自定义项目总是灰色的。

如果我添加一个 "Print Preview" 菜单项并为其赋值 2003(IDM_PRINTPREVIEW),它只会触发原始打印预览机制。而且我无法将我自己的事件处理程序添加到我的 CChristianLifeMinistryHtmlView class 中,因为它不被接受。

我发现这个 article 其中提到:

Should you choose to replace the standard menu with your own, you can still append menu extensions to your custom menu. Simply include a blank IDM_MENUEXT_PLACEHOLDER menu option in your menu definition to indicate where the custom commands are to be inserted. Menu extensions are inserted just before this placeholder. You can also add your own custom command to the standard menu by inserting the menu option before IDM_MENUEXT_PLACEHOLDER, as shown in the following example.

#define IDM_MENUEXT_PLACEHOLDER  6047

// If the placeholder is gone or was never there, then just exit if
(GetMenuState(hMenu, IDM_MENUEXT_PLACEHOLDER, MF_BYCOMMAND) != (UINT)
  -1) {  InsertMenu(hMenu,                    // The Context Menu
             IDM_MENUEXT_PLACEHOLDER,         // The item to insert before
             MF_BYCOMMAND|MF_STRING,          // by item ID and str value
             IDM_MENUEXT_FIRST__ + nExtCur,   // the command ID
             (LPTSTR)aOpts[nExtCur].pstrText);// The menu command text

// Remove placeholder  DeleteMenu(hMenu, IDM_MENUEXT_PLACEHOLDER,
MF_BYCOMMAND); }

The menu IDs for extensions fall between IDM_MENUEXT_FIRST__ and IDM_MENUEXT_LAST__ for a maximum of 33 custom commands.

我知道我没有设计正确,但我为占位符添加了一个菜单项,然后为打印预览添加了另一个菜单项 ID 为 IDM_MENUEXT_FIRST__ 的菜单项。然后我向它添加了一个菜单处理程序。菜单项不再被禁用,这很好。但是点击它没有任何反应。


这个问题涉及:

更新

我想我已经找到了解决方案,很快就会提供答案。

我已经查清楚了。我在此过程中学到了一些关键的东西。

概念 1

我没有离开我需要实现的 CHtmlView::OnShowContextMenu 功能:

HRESULT CChristianLifeMinistryHtmlView::OnShowContextMenu(DWORD dwID, LPPOINT ppt,
    LPUNKNOWN pcmdtReserved, LPDISPATCH pdispReserved)
{
    return CustomContextMenu(ppt, pcmdtReserved);
}

在我的辩护中,Visual Studio 中的 IDE 没有在列表中提供它作为可能的覆盖。但它仍然存在。

概念 2

所有 自定义 菜单项的菜单 ID 介于 IDM_MENUEXT_FIRST__IDM_MENUEXT_LAST__ 之间,最多 33 个自定义命令。在我的例子中,我在 resource.h 文件中手动创建了一些 #define 值:

#define CUSTOM_MENU_PRINT_PREVIEW       3700
#define CUSTOM_MENU_EXPORT              3701

请注意,我不喜欢使用文字值。我希望我可以使用 IDM_MENU_EXT_FIRST + 1 等作为我的定义,但我不知道该怎么做。可能吗?

概念 3

设计自定义菜单时,您也可以使用 IDM_XXX 值填充现有命令:

IDR_MENU_HTML_POPUP MENU
BEGIN
    POPUP "CustomPopup"
    BEGIN
        MENUITEM "Select All",                  31
        MENUITEM SEPARATOR
        MENUITEM "Export",                      CUSTOM_MENU_EXPORT
        MENUITEM SEPARATOR
        MENUITEM "Page Setup",                  2004
        MENUITEM "Print Preview",               CUSTOM_MENU_PRINT_PREVIEW
        MENUITEM SEPARATOR
        MENUITEM "Refresh",                     2300
        MENUITEM SEPARATOR
        MENUITEM "View Source",                 2139
    END
END

同样的注意事项仍然适用。我不知道如何将 IDM_* 常量分配给我自己的 #defines 而不是使用文字值。

如果您没有使用正确的 ID 值,您的自定义菜单项将显示为灰色。

概念 4

然后您必须创建上下文菜单(请参阅该代码的原始问题)。

概念 5

这与第4条密切相关,TrackMenuPopup根据我的情况需要调整如下:

// Show shortcut menu
iSelection = ::TrackPopupMenu(hPopupMenu,
    TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
    ppt->x,
    ppt->y,
    0,
    hwnd,
    (RECT*)NULL);

// Send selected shortcut menu item command to shell
if (iSelection != 0)
{
    if (iSelection == CUSTOM_MENU_PRINT_PREVIEW)
    {
        ::SendMessage(GetParent()->GetSafeHwnd(), WM_COMMAND, ID_FILE_PRINTPREVIEW, NULL);
    }
    else if (iSelection == CUSTOM_MENU_EXPORT)
    {
        ::SendMessage(GetParent()->GetSafeHwnd(), WM_COMMAND, ID_FILE_EXPORT, NULL);
    }
    else
    {
        (void) ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);
    }
}

关键是测试 TrackMenuPopup 的 return 值并进行自定义处理。事实上,在写这个答案时,我现在意识到我的“打印预览”菜单项可以定义为 IDM_PRINTPREVIEW 的值,我再次测试,如下所示:

if (iSelection != 0)
{
    if (iSelection == IDM_PRINTPREVIEW)
    {
        ::SendMessage(GetParent()->GetSafeHwnd(), WM_COMMAND, ID_FILE_PRINTPREVIEW, NULL);
    }
    else if (iSelection == CUSTOM_MENU_EXPORT)
    {
        ::SendMessage(GetParent()->GetSafeHwnd(), WM_COMMAND, ID_FILE_EXPORT, NULL);
    }
    else
    {
        (void) ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);
    }
}

所以我唯一的抱怨是我自己的#define are values are using literal numbers and I would prefer them to be based on the IDM constants if possible.但不管怎样,一切正常!