关于在WinUI 3应用中嵌入现有WinForms应用的技术咨询
嘿,刚好我之前研究过类似的需求,WinUI 3虽然没有WPF那种现成的WindowsFormsHost控件,但通过桌面端的互操作能力,完全可以实现把WinForms应用嵌入到TabView里、在标签页之间切换的效果!下面给你梳理下具体的实现思路和步骤:
前提准备
首先得确保你的WinUI 3项目是Desktop应用模型(也就是创建项目时选“WinUI 3 Desktop App”),只有桌面模型才能和Win32/WinForms进行底层互操作。另外要给项目安装必要的NuGet包:System.Windows.Forms,如果需要更方便调用Win32 API,还可以装PInvoke.User32(当然也可以自己声明Win32函数)。
核心实现思路
核心逻辑是把WinForms窗体作为子窗口嵌入到WinUI 3的TabViewItem容器中,通过Win32 API来绑定窗口父子关系,再同步两者的布局大小。具体步骤如下:
1. 创建并配置WinForms窗体
当你要新增一个TabView标签页时,先实例化你的WinForms应用主窗体(或者现有WinForms程序的入口窗体),记得把窗体边框去掉,避免和WinUI的TabView样式冲突:
var winFormsAppForm = new YourWinFormsMainForm(); winFormsAppForm.FormBorderStyle = FormBorderStyle.None; // 去掉自带边框 winFormsAppForm.Text = "嵌入的WinForms应用";
2. 构建WinUI的TabViewItem
给TabView添加新的标签页,用一个Grid作为标签页的内容容器(方便后续获取句柄和同步大小):
var newTab = new TabViewItem(); newTab.Header = "WinForms应用标签"; var contentContainer = new Grid(); newTab.Content = contentContainer; // 添加到TabView并选中 yourTabViewControl.TabItems.Add(newTab); yourTabViewControl.SelectedItem = newTab;
3. 绑定窗口父子关系(关键步骤)
通过Win32的SetParent API,把WinForms窗体的句柄设置为WinUI容器的句柄,这样WinForms窗体就会嵌入到TabViewItem里:
// 获取WinUI容器的HWND IntPtr containerHwnd = WindowNative.GetWindowHandle(contentContainer); // 获取WinForms窗体的HWND IntPtr formHwnd = winFormsAppForm.Handle; // 调用Win32 API设置父窗口 PInvoke.User32.SetParent(formHwnd, containerHwnd);
4. 同步布局大小
为了避免WinForms窗体和WinUI容器大小不一致,要监听容器的尺寸变化,同步更新WinForms窗体的大小:
contentContainer.SizeChanged += (s, e) => { if (winFormsAppForm != null && !winFormsAppForm.IsDisposed) { winFormsAppForm.Size = new System.Drawing.Size( (int)contentContainer.ActualWidth, (int)contentContainer.ActualHeight); } };
5. 显示WinForms窗体
最后别忘了显示WinForms窗体,否则它不会出现在TabView里:
winFormsAppForm.Show();
重要注意事项
- 线程模型:桌面WinUI 3默认是STA线程,和WinForms的线程模型兼容,不需要额外处理线程问题,但要避免跨线程操作UI。
- 生命周期管理:当关闭TabViewItem时,一定要主动关闭WinForms窗体并释放资源,比如在TabView的
TabCloseRequested事件里处理:private void YourTabView_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args) { var tabItem = args.Tab; var container = tabItem.Content as Grid; // 这里可以通过容器关联的WinForms窗体实例(可以提前存在Tag里)来关闭 var winFormsForm = container.Tag as Form; winFormsForm?.Close(); winFormsForm?.Dispose(); sender.TabItems.Remove(tabItem); } - 模态窗口处理:如果你的WinForms应用有模态弹窗,默认会以WinForms窗体为父窗口,可能会跳出WinUI容器,这时候需要在弹窗显示前,把弹窗的父窗口也设置为WinUI容器的句柄。
- 性能问题:如果嵌入多个WinForms窗体,要注意内存和性能消耗,及时释放不用的窗体资源。
备注:内容来源于stack exchange,提问作者Manuel

