You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将托管MFC程序的顶部菜单迁移至主WPF应用?

解决方案:将MFC程序菜单迁移至WPF主窗口

你原来的代码里用SetParent处理菜单句柄是无效的——菜单不是窗口,SetParent仅对HWND(窗口句柄)生效,所以那行代码不会有任何作用。要实现菜单迁移,需要提取MFC菜单的结构,在WPF中重建菜单,并通过Windows消息桥接点击事件,具体步骤如下:

1. 提取MFC程序的菜单结构

首先通过GetMenu获取MFC窗口的菜单句柄,然后递归枚举所有菜单项,提取文本、命令ID、子菜单等信息:

必要的PInvoke声明

[DllImport("user32.dll")]
static extern IntPtr GetMenu(IntPtr hWnd);

[DllImport("user32.dll")]
static extern int GetMenuItemCount(IntPtr hMenu);

[DllImport("user32.dll")]
static extern bool GetMenuItemInfo(IntPtr hMenu, int uItem, bool fByPosition, ref MENUITEMINFO lpMenuItemInfo);

[DllImport("user32.dll")]
static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct MENUITEMINFO
{
    public uint cbSize;
    public uint fMask;
    public uint fType;
    public uint fState;
    public uint wID;
    public IntPtr hSubMenu;
    public IntPtr hbmpChecked;
    public IntPtr hbmpUnchecked;
    public IntPtr dwItemData;
    public string dwTypeData;
    public uint cch;
    public uint hbmpItem;
}

public const uint MIIM_TYPE = 0x00000010;
public const uint MIIM_ID = 0x00000002;
public const uint MIIM_SUBMENU = 0x00000004;

枚举菜单的方法

private List<MenuItemData> ExtractMenuItems(IntPtr hMenu)
{
    var menuItems = new List<MenuItemData>();
    int itemCount = GetMenuItemCount(hMenu);
    for (int i = 0; i < itemCount; i++)
    {
        var mii = new MENUITEMINFO();
        mii.cbSize = (uint)Marshal.SizeOf(mii);
        mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_SUBMENU;
        mii.cch = 256;
        mii.dwTypeData = new string('\0', 256);

        if (GetMenuItemInfo(hMenu, i, true, ref mii))
        {
            var itemData = new MenuItemData
            {
                Text = mii.dwTypeData.TrimEnd('\0'),
                CommandId = mii.wID,
                SubMenu = mii.hSubMenu != IntPtr.Zero ? ExtractMenuItems(mii.hSubMenu) : null
            };
            menuItems.Add(itemData);
        }
    }
    return menuItems;
}

public class MenuItemData
{
    public string Text { get; set; }
    public uint CommandId { get; set; }
    public List<MenuItemData> SubMenu { get; set; }
}

2. 在WPF主窗口中重建菜单

把提取到的MenuItemData转换成WPF的Menu控件:

// 添加SendMessage的PInvoke声明
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

private void BuildWpfMenu(List<MenuItemData> menuItems, MenuItem parentItem)
{
    foreach (var itemData in menuItems)
    {
        var wpfItem = new MenuItem { Header = itemData.Text };
        
        // 如果有子菜单,递归构建
        if (itemData.SubMenu != null && itemData.SubMenu.Count > 0)
        {
            BuildWpfMenu(itemData.SubMenu, wpfItem);
        }
        else
        {
            // 绑定点击事件,发送WM_COMMAND给MFC窗口触发原逻辑
            wpfItem.Click += (s, e) =>
            {
                const int WM_COMMAND = 0x0111;
                SendMessage(_hostedAppHwnd, WM_COMMAND, (IntPtr)itemData.CommandId, IntPtr.Zero);
            };
        }
        
        parentItem.Items.Add(wpfItem);
    }
}

// 在初始化流程中调用:
var mfcMenu = GetMenu(_hostedAppHwnd);
var menuData = ExtractMenuItems(mfcMenu);
var mainMenu = new Menu();
BuildWpfMenu(menuData, new MenuItem { Items = mainMenu.Items });
// 将mainMenu添加到WPF主窗口的布局中(比如放在Window的Top区域)

3. 同步菜单状态(可选)

如果MFC程序的菜单会动态变化(比如禁用、勾选状态),可以定期调用GetMenuItemInfo获取状态,同步到WPF菜单:

private void SyncMenuState(List<MenuItemData> menuItems, MenuItem parentItem)
{
    for (int i = 0; i < menuItems.Count; i++)
    {
        var itemData = menuItems[i];
        var wpfItem = parentItem.Items[i] as MenuItem;
        if (wpfItem == null) continue;

        var mii = new MENUITEMINFO();
        mii.cbSize = (uint)Marshal.SizeOf(mii);
        mii.fMask = 0x00000008; // MIIM_STATE
        GetMenuItemInfo(GetMenu(_hostedAppHwnd), i, true, ref mii);
        
        // 同步禁用状态
        wpfItem.IsEnabled = (mii.fState & 0x00000001) == 0; // MFS_DISABLED为0x00000001
        // 同步勾选状态
        wpfItem.IsChecked = (mii.fState & 0x00000008) != 0; // MFS_CHECKED为0x00000008

        // 递归同步子菜单
        if (itemData.SubMenu != null && itemData.SubMenu.Count > 0)
        {
            SyncMenuState(itemData.SubMenu, wpfItem);
        }
    }
}

注意事项

  • 确保MFC程序的菜单命令ID在托管后仍然有效,部分MFC程序可能依赖窗口上下文,发送WM_COMMAND时要确保目标HWND正确。
  • 如果MFC程序使用了加速键(比如Ctrl+S),需要在WPF主窗口中注册对应的快捷键,再转发给MFC窗口。
  • 动态菜单变化的场景,可以使用SetWinEventHook监听EVENT_OBJECT_MENUSTART等事件,触发同步逻辑。

内容的提问来源于stack exchange,提问作者Enric

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 02:36:04