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