如何在WPF应用中嵌入UltraVNC及其连接后的子窗口?
如何将UltraVNC及其所有子窗口完整嵌入WPF应用
我懂你现在的痛点——用SetParent把UltraVNC主窗口嵌进WPF后,它的连接对话框、远程桌面窗口这些子窗口还是会跑到外面去。这是因为UltraVNC会创建多个独立的顶级窗口,只处理主窗口根本管不住后续生成的窗口。下面给你一套能彻底解决这个问题的方案:
核心思路
要实现完整嵌入,得做两件事:
- 把UltraVNC的主窗口绑定到WPF的宿主容器上
- 监听UltraVNC进程的窗口创建事件,把所有新生成的子窗口也强制绑定到同一个宿主容器
具体实现步骤
1. 准备WPF的宿主容器
WPF本身基于DirectX,直接操作HWND不如WinForms方便,所以我们用WindowsFormsHost作为中间层:
- 先在WPF项目里添加对
System.Windows.Forms和WindowsFormsIntegration的引用 - 在XAML里添加
WindowsFormsHost,里面放一个WinForms的Panel作为窗口容器:
<Grid> <WindowsFormsHost x:Name="vncHost"> <wf:Panel x:Name="vncContainer" BackColor="Black" /> </WindowsFormsHost> <Button Content="启动UltraVNC" Click="Button_Click" HorizontalAlignment="Bottom" Margin="10"/> </Grid>
(注意要在XAML根节点添加命名空间:xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms")
2. 导入必要的Win32 API
我们需要用到几个user32.dll的函数,用来设置父窗口、监听窗口创建事件:
using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Forms; public partial class MainWindow : Window { // 导入Win32 API [DllImport("user32.dll")] private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); [DllImport("user32.dll")] private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventProc lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); [DllImport("user32.dll")] private static extern bool UnhookWinEvent(IntPtr hWinEventHook); [DllImport("user32.dll")] private static extern bool GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("user32.dll")] private static extern IntPtr GetParent(IntPtr hWnd); [DllImport("user32.dll")] private static extern int SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); [DllImport("user32.dll")] private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int nWidth, int nHeight, bool bRepaint); // 窗口样式常量 private const int GWL_STYLE = -16; private const uint WS_OVERLAPPEDWINDOW = 0x00CF0000; private const uint WS_CHILD = 0x40000000; // 窗口创建事件常量 private const uint EVENT_OBJECT_CREATE = 0x8000; private const uint WINEVENT_OUTOFCONTEXT = 0x0000; // 定义事件回调委托 private delegate void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); private IntPtr _eventHook; private Process _vncProcess; private IntPtr _containerHwnd; public MainWindow() { InitializeComponent(); _containerHwnd = vncContainer.Handle; // 监听容器大小变化,同步调整嵌入窗口大小 vncContainer.SizeChanged += VncContainer_SizeChanged; }
3. 启动UltraVNC并绑定窗口
在按钮点击事件里,启动UltraVNC进程,找到它的主窗口,设置父容器,同时启动窗口创建监听:
private void Button_Click(object sender, RoutedEventArgs e) { // 启动UltraVNC Viewer _vncProcess = Process.Start(@"C:\Program Files\UltraVNC\vncviewer.exe"); _vncProcess.WaitForInputIdle(); // 等待进程初始化完成 // 找到UltraVNC的主窗口HWND IntPtr vncMainHwnd = IntPtr.Zero; while (vncMainHwnd == IntPtr.Zero) { foreach (ProcessThread thread in _vncProcess.Threads) { vncMainHwnd = FindWindowByThreadId(thread.Id); if (vncMainHwnd != IntPtr.Zero) break; } System.Threading.Thread.Sleep(100); } // 设置主窗口的父容器,并调整窗口样式 SetParent(vncMainHwnd, _containerHwnd); AdjustWindowStyle(vncMainHwnd); MoveWindow(vncMainHwnd, 0, 0, vncContainer.Width, vncContainer.Height, true); // 启动窗口创建事件监听,捕获UltraVNC的所有子窗口 _eventHook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_CREATE, IntPtr.Zero, WinEventCallback, (uint)_vncProcess.Id, 0, WINEVENT_OUTOFCONTEXT); } // 辅助方法:根据线程ID找窗口 private IntPtr FindWindowByThreadId(int threadId) { IntPtr hwnd = IntPtr.Zero; EnumWindows((hWnd, lParam) => { uint processId; GetWindowThreadProcessId(hWnd, out processId); if (processId == _vncProcess.Id && GetParent(hWnd) == IntPtr.Zero) { hwnd = hWnd; return false; } return true; }, IntPtr.Zero); return hwnd; } // 辅助方法:调整窗口样式(去掉标题栏、边框,设置为子窗口) private void AdjustWindowStyle(IntPtr hwnd) { IntPtr style = new IntPtr((int)(WS_CHILD)); SetWindowLongPtr(hwnd, GWL_STYLE, style); } // 窗口枚举委托 private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); [DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
4. 处理子窗口创建事件
在回调函数里,把UltraVNC新创建的子窗口也绑定到宿主容器:
private void WinEventCallback(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { // 只处理窗口对象(idObject为0表示窗口本身) if (idObject != 0) return; // 检查窗口是否属于UltraVNC进程 uint processId; GetWindowThreadProcessId(hwnd, out processId); if (processId != _vncProcess.Id) return; // 避免重复设置父窗口 if (GetParent(hwnd) != _containerHwnd) { SetParent(hwnd, _containerHwnd); AdjustWindowStyle(hwnd); MoveWindow(hwnd, 0, 0, vncContainer.Width, vncContainer.Height, true); } }
5. 清理资源
窗口关闭时,要取消事件监听,关闭UltraVNC进程:
protected override void OnClosed(EventArgs e) { base.OnClosed(e); if (_eventHook != IntPtr.Zero) { UnhookWinEvent(_eventHook); } if (_vncProcess != null && !_vncProcess.HasExited) { _vncProcess.Kill(); } } private void VncContainer_SizeChanged(object sender, EventArgs e) { // 同步调整所有嵌入窗口的大小 EnumWindows((hWnd, lParam) => { uint processId; GetWindowThreadProcessId(hWnd, out processId); if (processId == _vncProcess.Id && GetParent(hWnd) == _containerHwnd) { MoveWindow(hWnd, 0, 0, vncContainer.Width, vncContainer.Height, true); } return true; }, IntPtr.Zero); } }
关键注意事项
- 窗口样式调整:必须把UltraVNC窗口的样式改成
WS_CHILD,否则即使设置了父窗口,它还是会以顶级窗口的形式弹出。 - 等待窗口创建:UltraVNC启动后可能不会立刻创建主窗口,所以需要循环等待直到找到HWND。
- 资源清理:一定要记得取消事件监听和关闭进程,避免内存泄漏。
这样操作后,UltraVNC的所有窗口(包括连接对话框、远程桌面窗口)都会乖乖嵌在你的WPF窗口里,不会再跑出去了。
内容的提问来源于stack exchange,提问作者Pratheek
相关产品推荐
相关产品推荐

