Search code examples
pythonc++qtwinapipyside2

Embed Qt inside native window (Windows)


I'd like to embed a Qt Application inside Windows (not the other way around, as many other questions have already been answered). To clarify I have a win32 application which I launch a qt python process; this qt python process must be embedded within the win32 application. How can this be done? In the API for QWindow::fromWinId, it clearly states:

"Creates a local representation of a window created by another process or by using native libraries below Qt...."

"...This can be used, on platforms which support it, to embed a QWindow inside a native window, or to embed a native window inside a QWindow."

And secondly QWidget::createWindowContainer appears to only work for embedding native windows inside Qt (not the way I want it).

I am not sure how I would approach creating a QWidget inside QWindow. From this question, it seems the way would be to create a QQuickView with a QWindow::fromWinId; however, I can't seem to find how to bind a QWidget into a QQuickView.

Currently I am actually setting the parent with ::SetParent but there are weird messaging protocols to deal with there so I'd like to try to refactor this with Qt's approach.

Some basic code written so far (PySide2, but C++ or any other language with Qt bindings is fine):

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_())

Solution

  • First, you need to create a QWindow from HWND so that Qt can handle it:

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

    Then you create your Qt Widget interface:

    // C++
    QLabel *label = new QLabel("Hello from Qt");
    label->show();
    // Python
    label = QLabel("Hello from Qt");
    label.show();
    

    Then you parent the top window of you Qt Widget interface to the native window:

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

    However, you cannot use Qt to listen to changes in the HWND window. Quoting Qt documentation:

    Note: The resulting QWindow should not be used to manipulate the underlying native window (besides re-parenting), or to observe state changes of the native window. Any support for these kind of operations is incidental, highly platform dependent and untested.

    In practice, the signals QWindow::widthChanged() and QWindow::heightChanged() are not emitted.

    If you want to listen to events from HWND you have to do it the native Win32 way by using SetWindowsHookEx() or SetWinEventHook().

    If you are interested in the resize event you can do in C or 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);
    

    with the following 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;
        }
    }