在Windows系统下将Qt嵌入本地窗口

4
我想在Windows中嵌入一个Qt应用程序(不是反过来,因为已经有很多其他问题得到了回答)。要澄清的是,我有一个Win32应用程序,我启动了一个qt python进程;这个qt python进程必须嵌入win32应用程序中。如何做到这一点?在QWindow::fromWinId的API中,它明确表示:

"创建由另一个进程或使用低于Qt的本地库创建的窗口的本地表示...."

"...这可以在支持它的平台上用于将QWindow嵌入本机窗口中,或将本机窗口嵌入QWindow中。"

其次,QWidget::createWindowContainer似乎仅适用于在Qt中嵌入本机窗口(不是我想要的方式)。

我不确定如何在QWindow内创建QWidget。从这个问题中,似乎方法是使用带有QWindow::fromWinIdQQuickView;但是,我找不到如何将QWidget绑定到QQuickView中。

目前,我实际上是使用::SetParent设置父级,但是那里有一些奇怪的消息协议需要处理,因此我想尝试使用Qt的方法进行重构。

迄今为止编写的一些基本代码(PySide2,但C++或任何其他具有Qt绑定的语言也可以):

app = QApplication(sys.argv)
hwnd = int(sys.argv[1], 16)

nativeParentWindow = QWindow.fromWinId(hwnd)
quickview = QQuickView(nativeParentWindow)

# this part is incorrect (tries to embed native window into qt)
# I want this application to run embedded inside hwnd
wrongWidget = QWidget.createWindowContainer(quickview)
wrongWidget.show()

sys.exit(app.exec_())

实际上,这并不是很实用,就像你调用SetParent时发现的那样。支持的方式是所有UI代码都驻留在同一个进程中。 - David Heffernan
我认为让这个顺利运行的希望不是很大。 - David Heffernan
@DavidHeffernan 虽然我不是Qt专家,但我仍然希望它有可能工作,因为fromWinId的文档明确说明这是可能的。 - Michael Choi
1
这里有数百个问题,来自于那些力图让其正常工作的人们。你遇到了每个人都会遇到的同样的问题。 - David Heffernan
1
Qt并不是非常相关的。问题在于底层系统级别。将Qt加入其中只会使困难更加严重。 - David Heffernan
显示剩余5条评论
1个回答

3

首先,您需要从HWND创建一个QWindow,以便Qt可以处理它:

最初的回答:

// C++
QWindow *nativeWindow = QWindow::fromWinId(hwnd);
// Python
nativeWindow = QWindow.fromWinId(hwnd);

然后您创建Qt小部件界面:
// C++
QLabel *label = new QLabel("Hello from Qt");
label->show();
// Python
label = QLabel("Hello from Qt");
label.show();

然后,您将Qt Widget界面的顶层窗口与本地窗口进行父子关联:

Original Answer翻译成"最初的回答"

// C++
label->windowHandle()->setParent(nativeWindow);
// Python
label.windowHandle().setParent(nativeWindow);

然而,您不能使用Qt来监听HWND窗口的更改。引用Qt文档
注意:生成的QWindow不应该用于操作底层本机窗口(除了重新父级),或者观察本机窗口状态的变化。对这些操作的任何支持都是偶然的、高度依赖平台的和未经测试的。
实际上,信号QWindow :: widthChanged()和QWindow :: heightChanged()不会被发射。
如果您想要监听来自HWND的事件,您必须使用本地Win32方式,即使用SetWindowsHookEx()或SetWinEventHook()。
如果您对调整大小事件感兴趣,可以在C或C++中执行以下操作:
targetHwnd = hwnd; // defined on global scope
DWORD processId;
DWORD threadId= GetWindowThreadProcessId(hwnd, &processId);
HWINEVENTHOOK hook = SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, 0, &Wineventproc, processId, threadId, WINEVENT_OUTOFCONTEXT);

使用以下Wineventproc函数:
void WINAPI Wineventproc(
  HWINEVENTHOOK hWinEventHook,
  DWORD event,
  HWND hwnd,
  LONG idObject,
  LONG idChild,
  DWORD idEventThread,
  DWORD dwmsEventTime
)
{
    if(targetHwnd == hwnd) { // Filter events that are not for our window
        qDebug() << "event" << event;
        RECT rect;
        GetWindowRect(hwnd, &rect);
        qDebug() <<  "Height:" << rect.bottom - rect.top;
        qDebug() <<  "Width:" << rect.right - rect.left;
    }
}

除了调整大小(已经处理过了)之外,我如何知道窗口事件钩子是否会处理其他可能被忽略的消息?此外,我希望将这些本机窗口消息委托给Qt,然后由Qt进行调整大小,而不是从本机应用程序层(win32)进行调整。 - Michael Choi
1
@MichaelChoi 刚刚阅读了关于 SetWinEventHook() 的文档,你可以看到你可以监视的事件列表:https://learn.microsoft.com/fr-fr/windows/win32/winauto/event-constants 此外,使用 SetWindowsHookEx() 可以挂钩进窗口过程函数,因此您应该能够获得您感兴趣的任何事件或消息。 - Benjamin T
@MichaelChoi 你不能将这些消息“委托”给Qt,我甚至不确定这句话是否有意义。发生的情况是,您必须使用Win32 API从HWND获取事件。一旦您拥有这些事件,您必须使用Qt API通过调用QWidget::resize()QWidget::setGeometry()来调整顶部Qt小部件的大小。正如我在我的答案中所说,您不能使用Qt从HWND获取事件。而且您必须使用Qt来调整小部件的大小。 - Benjamin T
@benjamin_T 我明白了,很抱歉我没有先查看文档。我发现大多数事件都可以挂钩,但操纵仍然是在C++ win32端完成的。是否有一种标准的Qt方法将 DWORD event 转换为 QEvent(例如像 QEvent::createEventFromNativeWin32(event) 这样的东西)?我没有看到这方面的API调用,我怀疑这是不可能的;但是,Qt必须在其应用程序中某个地方转换这些事件,因此我希望在此处利用相同的代码。 - Michael Choi
非常感谢,还值得一提的是,在调用setParent()之前,先调用label->show()很重要,否则setParent会崩溃。 - David
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接