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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:33:19