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

2022-01-12 00:00:00 c++ webbrowser-control mfc

所以我会继续回到 CodeProject 上的这篇文章:

So I keep coming back to this article on CodeProject:

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

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

I then realised this statement at the top of the article:

修改后的示例项目使用了一种新的更好的自定义方法,将在本文的下一次更新中进行全面讨论,希望在几周内准备好.我正在发布这个半文档化且未经过全面测试的代码,因为我有迹象表明一些开发人员可能需要比我下一次更新的那一天更早地拥有这个代码.对于每个修改后的示例,还有一个 Readme.htm 文件,简要描述了示例的工作原理.

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.

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

I thought I was struggling to understand the code in the article snippets vs the downloaded source! So I read the readme and it stated:

在 MFC 7 CHtmlView 中嵌入了对 IDocHostUIHandler 的支持,因此我只需重写 CHtmlView::OnShowContextMenu 方法,然后我调用 ::CustomShowContextMenu() 函数,(在 CustomMenus.cpp 中),其工作方式与我原始文章第 5 节中的描述相同.

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.

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

So, I decided to add my own function override in my project:

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

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

And I added the similar custom menu function:

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;
}

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

Finally, I added a menu resource:

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

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

The menu ID values are based on the IDM_ versions and they all work.

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

I then tried to add my own menu item into that list with my own event handler and it shows as disabled.

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

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

我想添加我自己的菜单项打印预览",然后它只是向我的父编辑器"发布一条消息,以模拟在那里单击打印预览".但似乎任何添加到此菜单的自定义项始终是灰色的.

I wanted to add my own menu item "Print Preview" which in turn simply posted a message to my parent "Editor" to simulate clicking "Print Preview" there. But it seems that any custom item that is added to this menu is always greyed out.

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

If I add a "Print Preview" menu item and give it a value of 2003(IDM_PRINTPREVIEW) it just triggers the original print preview mechanism. And I can't add my own event handler for the same to my CChristianLifeMinistryHtmlView class as it is not honoured.

我发现这篇文章提到:

如果您选择用自己的菜单替换标准菜单,您可以仍然将菜单扩展附加到您的自定义菜单.只需包括一个菜单定义中的空白 IDM_MENUEXT_PLACEHOLDER 菜单选项指示要插入自定义命令的位置.菜单扩展在此占位符之前插入.您也可以添加自己的通过插入菜单选项自定义命令到标准菜单在IDM_MENUEXT_PLACEHOLDER之前,如下例所示.

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

扩展的菜单 ID 介于 IDM_MENUEXT_FIRST__IDM_MENUEXT_LAST__ 之间,最多 33 个自定义命令.

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__.然后我向它添加了一个菜单处理程序.菜单项不再被禁用,这很好.但是点击它什么也没做.

I know I didn't design it right but I added a menu item for the place holder and then another for Print Preview with a menu item id of IDM_MENUEXT_FIRST__. I then added a menu handler to it. The menu item is no longer disabled so that is good. But clicking it does nothing.

这个问题涉及到:

  • CHtmlView 和打印预览和上下文菜单

我想我已经找到了解决办法,很快就会给出答案.

I think I have found a solution and will provide an answer shortly.

推荐答案

我已经深入了解了.在此过程中,我学到了一些关键的东西.

I have got to the bottom of this. There were a few key things that I learned along the way.

我并没有离开 CHtmlView::OnShowContextMenu 我需要实现的函数:

I was not away of the CHtmlView::OnShowContextMenu function which I needed to implement:

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

在我的辩护中,Visual Studio 中的 IDE 并未在列表中提供它作为可能的覆盖.但它确实存在.

In my defence, the IDE in Visual Studio did not offer it in the list as a possibly override. But it existed non-the-less.

所有 custom 菜单项的菜单 ID 位于 IDM_MENUEXT_FIRST__IDM_MENUEXT_LAST__ 之间,最多可包含 33 个自定义命令.就我而言,我在 resource.h 文件中手动创建了一些 #define 值:

The menu IDs for all custom menu items fall between IDM_MENUEXT_FIRST__ and IDM_MENUEXT_LAST__ for a maximum of 33 custom commands. In my case I manually create some #define values in my resource.h file:

#define CUSTOM_MENU_PRINT_PREVIEW       3700
#define CUSTOM_MENU_EXPORT              3701

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

Note that I am not happy using literal values. I wish I could use IDM_MENU_EXT_FIRST + 1 etc. for my definitions but I do not know how to do that. Possible?

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

When you design your custom menu you can infill existing commands with the IDM_XXX values too:

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 而不是使用文字值.

The same note still applies though. I can't work out how to assign the IDM_* constants to my own #defines rather than using literal values.

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

Your custom menu items will be greyed out if you don't use the right ID values.

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

Then you have to create the context menu (see original question for that code).

这与第4项密切相关.TrackMenuPopup在我的情况下需要调整如下:

This is closely related to item 4. TrackMenuPopup needed to be adjusted in my situation as follows:

// 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的返回值并进行自定义处理.事实上,在写这个答案时,我现在意识到我的打印预览"是菜单项可以定义为 IDM_PRINTPREVIEW 的值,我再次测试,如下所示:

The key was testing the return value of TrackMenuPopup and doing custom handling. In-fact, whilst writing this answer I now realise that my "Print Preview" menu item can be defined as the value of IDM_PRINTPREVIEW and I test again that, like this:

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 是使用文字数字的值,如果可能的话,我希望它们基于 IDM 常量.但不管怎样,一切都很好!

So my only gripe is that my own #define are values are using literal numbers and I would prefer them to be based on the IDM constants if possible. But either way, it all works fine!

相关文章