在这里插入图片描述
本文解决了将UE程序嵌入Qt窗口时遇到的两个关键问题:输入事件无法传递和鼠标位置偏移。通过Windows API修改UE窗口样式并设置父窗口实现嵌入,同时重载Qt窗口的resizeEvent实现同步缩放。针对鼠标偏移问题,在UE中通过循环获取所有父窗口坐标并累加,最终设置正确的屏幕位置。解决方案提供了完整的C++代码实现,并指出需注意可执行程序路径匹配、窗口标题设置等细节问题。该方法有效实现了UE程序与Qt界面的无缝集成。

项目场景

需要将UE程序窗口作为Qt软件的一部分,将UE窗口去除系统标题栏和边框等作为一个子窗口嵌入到Qt布局中


问题描述

问题1:UE嵌入Qt窗口时,会出现鼠标和键盘无法输入到UE
问题2:UE获取的鼠标与界面UI的位置产生偏移

原因分析

问题1分析:QWindow::fromWinId得到的QWindow是对UE窗口的代理,但是事件会被阻塞传递不到UE窗口内,同时UE窗口也就无法被激活
问题2分析:UE引擎源码中在Windows系统对UE屏幕位置是通过GetWindowRect获得,默认认为UE窗口的父窗口是桌面窗口,未考虑作为子窗口的情况


解决方案

Qt正确嵌入UE程序步骤:

  1. QProgress启动ue的exe程序
  2. 通过Windows的FindWindowW函数以窗口标题查找窗口句柄
  3. GetWindowLong(handle, GWL_STYLE);函数获取样式,修改后重新设置样式
  4. SetParent设置外部窗口的父窗口widget
  5. SetWindowPos设置窗口大小和进行重绘
  6. 父窗口widget重载resizeEvent,通过MoveWindow跟随大小变化
void MyWidget::start(const QString &exePath, const QString &strTiltle)
{
  if(!exePath.isEmpty())
  {
    // 启动外部程序
    _process = new QProcess(this);
    _process->start(exePath);
    // 检查应用是否成功启动
    if(_process->waitForStarted())
    {
      // 获取外部程序的窗口句柄 可以增加获取最大时间或次数超过退出的代码
      while(!_exHWhd)
      {
        _exHWhd = FindWindowW(NULL, reinterpret_cast<LPCWSTR>(strTitle.utf16()));
        // 短暂休眠,避免CPU占用过高
        QThread::msleep(50);
      }
    }
  }
  if (_exHWhd)
  {
     // 修改窗口样式以去掉标题栏和边框 采用此方法才可正常获取激活响应键盘
        LONG style = GetWindowLong(_exHWhd, GWL_STYLE);
        style &= ~(WS_CAPTION | WS_THICKFRAME | WS_OVERLAPPED); // 移除标题栏、可调整大小的边框和重叠窗口样式
        SetWindowLong(_exHWhd, GWL_STYLE, style | WS_POPUP); // 设置为无边框的子窗口
        SetParent(_exHWhd, (HWND)winId());
        SetWindowPos(_exHWhd, HWND_TOPMOST, 0, 0, geometry().width(),
                     geometry().height(), SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOCOPYBITS);
  }
  else
  {
      QMessageBox::information(this, "Tip", QString("未成功打开窗口:%1").arg(strTitle), QMessageBox::Yes);
      qDebug() << "Failed to find external application window.";
  }
}
// 重载resizeEvent
void MyWidget::resizeEvent(QResizeEvent *event) {
  QWidget::resizeEvent(event);
  if(_exHWhd)
  {
      MoveWindow(_exHWhd,0, 0, event->size().width(),event->size().height(),false);
  }
}

UE正确更新屏幕坐标方案

  1. UE需要通过获取窗口句柄后循环GetParent直到nullptr或者桌面窗口句柄,累加lefttop
  2. UE通过SetCachedScreenPosition设置屏幕位置
  3. UE将更新屏幕位置代码封装成函数以定时器设置时间间隔调用更新
// UE修复嵌入窗口坐标偏移问题
// 获取主窗口
TSharedPtr<SWindow> MainWindow = FSlateApplication::Get().GetActiveTopLevelWindow();
if (MainWindow.IsValid()) {
  // 获取平台窗口
    TSharedPtr<FGenericWindow> GenericWindow = MainWindow->GetNativeWindow();
    if (GenericWindow.IsValid()) {
        // 转换为 Windows 平台特定的窗口类型
        FWindowsWindow* WindowsWindow = static_cast<FWindowsWindow*>(GenericWindow.Get());
        // 获取 HWND 句柄
        HWND WindowHandle = WindowsWindow->GetHWnd();
        // 最终屏幕坐标
        FVector2D FinalPosition(0, 0);
        // 获取桌面窗口句柄(作为终止条件)
        HWND DesktopHandle = GetDesktopWindow();
        // 获取窗口位置矩形
        RECT WindowRect;
        // 循环所有父窗口
        while(WindowHandle != nullptr && DesktopHandle!= DesktopHandle)
        {
            if (GetWindowRect(WindowHandle, &WindowRect))
            {
                // 窗口左上角坐标(屏幕坐标)
                FinalPosition.X +=  static_cast<float>(WindowRect.left);
                FinalPosition.Y +=  static_cast<float>(WindowRect.top);
            }
            WindowHandle = GetParent(WindowHandle);
        }
        // 获取DPI缩放因子
        float DPIScale = FSlateApplication::Get().GetDPIScaleFactorAtPoint(FinalPosition.X, FinalPosition.Y);
        // 转换为 Slate 坐标
        FinalPosition.X /= DPIScale;
        FinalPosition.Y /= DPIScale;
        // 设置屏幕位置
        SetCachedScreenPosition(FinalPosition);
    }
}

注意事项

  1. 需要准确匹配可执行程序文件位置窗口标题
  2. 获取的ue程序窗口标题尽量设置成不含特殊字符如空格等,简短不动态变化
  3. ue游戏模式依赖鼠标移动完成,则窗口化时进入游戏模式需限制鼠标隐藏,需通过某个键位响应后显示鼠标退出
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐