如何将托管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
相关产品推荐
相关产品推荐

