0

Problem

I need a Windows native window displayed within a QML application.

Constraints

  • The native window (NW) application is drawn with OpenGL
  • The NW is for display purposes only and does not require user input
  • No 3rd party libraries allowed
  • You have access to the NW's source code
    • Note: porting this specific source code to a QOpenGLWidget is frowned upon

Working Solution

A working solution is to leverage the QWindow::fromWinId and QWidget::createWindowContainer methods to "throw" the window into a QWidget. Then we take an item from QML and force this widget to "mirror" the item's dimensions and position.

The following code snippets demonstrate a possible implementation.
(Note: These snippets are paraphrased and stripped of error checking)

main.qml

The Rectangle with id "target" is passed into MyInterfaceObject's targetObject property.

Rectangle {
    id: app
    .
    .
    Rectangle {
        id: target

        MyInterfaceObject {
            targetObject: target
            .
            .
        }
}

MyInterfaceObject.cpp

MyInterfaceObject is a QObject that has a QVariant targetObject property. It takes a desired window handle and "throws" it onto a widget. Then, it starts a timer that will map the embedded widget to the targetObject's position.

One could potentially update based on some targetObject signals instead of on a timer, but since I originally did my testing on a Flickable, that did not work.

// Find the window by it's title
HWND windowHandle = ::FindWindow(0, L"Your Window Title");

// "Throw" window into a widget
QWindow *embeddedWidgetWindow = QWindow::fromWinId((WId)windowHandle);
// the quickWidget is created in main.cpp
QWidget *embeddedWidget = QWidget::createWindowContainer(embeddedWidgetWindow, quickWidget);

embeddedWidget->show();
embeddedWidgetWindow->show();
quickWidget->show();

// Start a timer that will enforce the "mirroring" effect
QTimer* timer = new QTimer;
timer->setInterval(1);
timer->start();

timer->connect(timer, &QTimer::timeout, [this](){
    // targetObject is the QVariant form of the target QML object from our main.qml
    auto quickItem = this->targetObject().value<QQuickItem*>();

    // Map our widgets position/dimensions to our target qml object
    auto scenePos = quickItem->mapToItem(0, quickItem->position());
    auto myX = scenePos.x() - quickItem->position().x();        
    auto myY = scenePos.x() - quickItem->position().y();

    embeddedWidget->setGeometry(myX, myY, quickItem->width(), quickItem->height());
});

main.cpp

// The widget that will parent our native window widget
QQuickWidget* quickWidget= new QQuickWidget;
quickWidget->setSource(QUrl(QStringLiteral("qrc:/main.qml")));
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);

// Give our InterfaceObject access to this quickWidget
InterfaceObject::setQuickWidget(quickWidget);

The embedded widget will successfully follow around my QML object. However, when set upon a Flickable, the widget will temporarily lag behind a little bit when scrolling or dragging.

Non-Working Solution

My second solution is similar to the above and involves the QScreen::grabWindow method and a QuickQuickPaintedItem.

MyInterfacePaintedItem.cpp

// Do this for every paint event

// Find the window by it's title
HWND windowHandle = ::FindWindow(0, L"Your Window Title");

auto quickItem = this->targetObject().value<QQuickItem*>();

// can also "throw" the native window into a widget like above and
// use that widgets winId (widget->winId())

auto pixmap = QGuiApplication::primaryScreen()->grabWindow((WId)windowHandle)

painter->drawPixmap(QRect(0, 0, quickItem->width(), quickItem->height()), 
                          pixmap,
                          pixmap.rect());

This solution works for every window I pass in EXCEPT the one I want. I am left with an empty white image instead of the desired image.

Extra

I've attempted using BitBlt in combination with QtWin::fromHBITMAP (usage), but that does not work on my specific window either. I always get a white image like the Non-Working Solution above. (Potential cause).

Environment

  • Qt: 5.10.0
  • Platform: Windows 7
  • Compiler: MSVC2015 32bit
kañe
  • 11
  • 7
  • native OpenGL doesn't draw into surfaces you can grab with Qt like that, that's why you get rectangle of background color. even with QOpenGLWidget that doesn't work unless you implant it into QGraphicsScene. You have to work with offscreen rendering and render image. Note, that QML inherently uses OpenGL on Windows for rendering content in default configuration. – Swift - Friday Pie May 24 '18 at 18:10

1 Answers1

0

By leveraging DwmRegisterThumbnail, you can display a native OpenGL window anywhere inside your QML application. Off of the top of my head, the steps to doing so are:

Adding the DWM dll to QMake

win32:LIBS += -ldwmapi.dll

Registering the native application's thumbnail with the QML application

The DWM API allows you to register an applications thumbnail with another application by passing the window handles (HWND, not WId) of the source (owner of the thumbnail) and destination windows.

Note: the destination window must be a top level window created by the process in which you are calling this function!!

DwmRegisterThumbnail((HWND)window()->winId(), hNativeHandle, hThumbnail);

Updating thumbnail size and position

Now that we have registered the thumbnail with a destination window, we can leverage the DwmUpdateThumbnailProperties function to manipulate its size and position in our window. Read the DWM API documentation on this function and update the thumbnail size and position to mirror the global size and position of where you want it in your application. I use a QQuickItem that I place in my application and I base my thumbnail properties off of that.

kañe
  • 11
  • 7