Using a Qt 5 Widget within a QML application?
-
wrote on 20 Jan 2016, 19:09 last edited by
I am writing a Qt 5 application with QML and would like to know if it is possible to integrate a Qt 5 Widget into the same UI of that application.
- Is it possible?
- If yes, how is it achieved?
- Are there any restrictions, caveats or problems associated with doing this?
Thanks,
Felix
-
Hi and welcome.
I have not huge experience with QML but i know you can expand it
http://doc.qt.io/qt-5/qtqml-tutorials-extending-qml-example.html
so i assume its also possible with normal widgets in some way.Since you can show QML inside widget application, it is possible to mix.
http://doc.qt.io/qt-4.8/qml-integration.htmlSo yes, I think with some research its possible.
-
Hi and welcome.
I have not huge experience with QML but i know you can expand it
http://doc.qt.io/qt-5/qtqml-tutorials-extending-qml-example.html
so i assume its also possible with normal widgets in some way.Since you can show QML inside widget application, it is possible to mix.
http://doc.qt.io/qt-4.8/qml-integration.htmlSo yes, I think with some research its possible.
sorry thats not officially supported by Qt.
The problem is that QML uses SceneGraph for painting, where QWidgets use a raster engine for painting. So they unfortunately dont play very well together.
But not impossible, but but far not efficient. You would need to render the widget as a texture on every paint event. Additionally you would need to forward the events to the widget. -
I am writing a Qt 5 application with QML and would like to know if it is possible to integrate a Qt 5 Widget into the same UI of that application.
- Is it possible?
- If yes, how is it achieved?
- Are there any restrictions, caveats or problems associated with doing this?
Thanks,
Felix
@Lucan1d
you made me curious, and so i played around a bit.
This example renders the widget and forwards mouse- and key-events to the widget (i used a QTextEdit in this case).
But it also points out the problems very well:- you can't use widgets with popup windows (e.g. combobox)
- cursor blinking and text-selection isn't painted (may be fixed somehow - didn't investigate further)
- correct focus handling might be pretty tricky to implement
- correct state handling is essential for visual appearance
But i think it might be a good starting point. It still needs a lot of work to behave as someone would expect it to behave.
qmlRegisterType<MyQmlWidget>("com.test.qml", 1, 0, "MyQmlWidget"); QQuickView *qmlView = new QQuickView; qmlView->setSource(QUrl::fromLocalFile("test.qml")); qmlView->show();
test.qml
import QtQuick 2.0 import com.test.qml 1.0 MyQmlWidget { focus: true }
h
class MyQmlWidget : public QQuickPaintedItem { Q_OBJECT public: MyQmlWidget( QWidget* widget = new QTextEdit ); virtual bool eventFilter(QObject *, QEvent *); virtual void paint(QPainter * painter); virtual bool event(QEvent * e); protected: void setWidget( QWidget* widget ); QWidget* m_Widget; };
cpp
MyQmlWidget::MyQmlWidget( QWidget* widget ) : QQuickPaintedItem() { this->setWidget( widget ); this->setAcceptedMouseButtons(Qt::AllButtons); } bool MyQmlWidget::eventFilter(QObject * watched, QEvent * event) { if( watched == m_Widget ) { switch( event->type() ) { case QEvent::Paint: case QEvent::UpdateRequest: this->update(); break; } } return QQuickPaintedItem::eventFilter(watched, event); } void MyQmlWidget::paint(QPainter *painter) { if( !m_Widget ) return; m_Widget->render( painter ); } bool MyQmlWidget::event(QEvent *event) { switch( event->type() ) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::KeyPress: case QEvent::KeyRelease: { qDebug() << "SEND EVENT:" << event; QApplication::sendEvent( m_Widget, event ); return true; } break; case QEvent::FocusIn: this->forceActiveFocus(); break; } return QQuickPaintedItem::event(event); } void MyQmlWidget::setWidget(QWidget *widget) { m_Widget = widget; m_Widget->installEventFilter( this ); const QSizeF sh = m_Widget->sizeHint(); this->setImplicitSize( sh.width(), sh.height() ); }
-
@Lucan1d
you made me curious, and so i played around a bit.
This example renders the widget and forwards mouse- and key-events to the widget (i used a QTextEdit in this case).
But it also points out the problems very well:- you can't use widgets with popup windows (e.g. combobox)
- cursor blinking and text-selection isn't painted (may be fixed somehow - didn't investigate further)
- correct focus handling might be pretty tricky to implement
- correct state handling is essential for visual appearance
But i think it might be a good starting point. It still needs a lot of work to behave as someone would expect it to behave.
qmlRegisterType<MyQmlWidget>("com.test.qml", 1, 0, "MyQmlWidget"); QQuickView *qmlView = new QQuickView; qmlView->setSource(QUrl::fromLocalFile("test.qml")); qmlView->show();
test.qml
import QtQuick 2.0 import com.test.qml 1.0 MyQmlWidget { focus: true }
h
class MyQmlWidget : public QQuickPaintedItem { Q_OBJECT public: MyQmlWidget( QWidget* widget = new QTextEdit ); virtual bool eventFilter(QObject *, QEvent *); virtual void paint(QPainter * painter); virtual bool event(QEvent * e); protected: void setWidget( QWidget* widget ); QWidget* m_Widget; };
cpp
MyQmlWidget::MyQmlWidget( QWidget* widget ) : QQuickPaintedItem() { this->setWidget( widget ); this->setAcceptedMouseButtons(Qt::AllButtons); } bool MyQmlWidget::eventFilter(QObject * watched, QEvent * event) { if( watched == m_Widget ) { switch( event->type() ) { case QEvent::Paint: case QEvent::UpdateRequest: this->update(); break; } } return QQuickPaintedItem::eventFilter(watched, event); } void MyQmlWidget::paint(QPainter *painter) { if( !m_Widget ) return; m_Widget->render( painter ); } bool MyQmlWidget::event(QEvent *event) { switch( event->type() ) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::KeyPress: case QEvent::KeyRelease: { qDebug() << "SEND EVENT:" << event; QApplication::sendEvent( m_Widget, event ); return true; } break; case QEvent::FocusIn: this->forceActiveFocus(); break; } return QQuickPaintedItem::event(event); } void MyQmlWidget::setWidget(QWidget *widget) { m_Widget = widget; m_Widget->installEventFilter( this ); const QSizeF sh = m_Widget->sizeHint(); this->setImplicitSize( sh.width(), sh.height() ); }
wrote on 10 Feb 2021, 18:19 last edited byHere is a method of capturing mouse events and wheel events based on the previous answer. Unfortunately, this method requires us to access protected members of the QWidget. I don't know how legal it is, but it works:
Constructor:
QmlPlainTextEdit::QmlPlainTextEdit(QQuickItem *parent) : QQuickPaintedItem(parent) { // Set item flags setFlag(ItemHasContents, true); setFlag(ItemAcceptsInputMethod, true); setFlag(ItemIsFocusScope, true); setAcceptedMouseButtons(Qt::AllButtons); // Initialize the text edit widget m_textEdit = new QPlainTextEdit(); m_textEdit->installEventFilter(this); m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); // Set the QML item's implicit size auto hint = m_textEdit->sizeHint(); setImplicitSize(hint.width(), hint.height()); // Resize QPlainTextEdit to fit QML item connect(this, &QQuickPaintedItem::widthChanged, this, &QmlPlainTextEdit::updateWidgetSize); connect(this, &QQuickPaintedItem::heightChanged, this, &QmlPlainTextEdit::updateWidgetSize); }
Events:
bool QmlPlainTextEdit::event(QEvent *event) { switch (event->type()) { case QEvent::FocusIn: forceActiveFocus(); return QQuickPaintedItem::event(event); break; case QEvent::Wheel: processWheelEvents(static_cast<QWheelEvent*>(event)); return true; break; case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::MouseMove: processMouseEvents(static_cast<QMouseEvent*>(event)); return true; break; default: break; } return QApplication::sendEvent(m_textEdit, event); }
Event filter:
bool QmlPlainTextEdit::eventFilter(QObject *watched, QEvent *event) { Q_ASSERT(m_textEdit); if (watched == m_textEdit) { switch (event->type()) { case QEvent::Paint: case QEvent::UpdateRequest: update(); break; default: break; } } return QQuickPaintedItem::eventFilter(watched, event); }
Paint function:
void QmlPlainTextEdit::paint(QPainter *painter) { if (m_textEdit && painter) m_textEdit->render(painter); }
Process mouse/wheel events:
Doing some tests, I found out that
QPlainTextEdit::event()
returnedfalse
for mouse and wheel events. The quick and dirty solution was to call the appropriate event handlers directly. However, these functions are protected, so we need to do some unorthodox things in order to call these functions:void QmlPlainTextEdit::processMouseEvents(QMouseEvent* event) { class Hack : public QPlainTextEdit{ public: using QPlainTextEdit::mousePressEvent; using QPlainTextEdit::mouseMoveEvent; using QPlainTextEdit::mouseReleaseEvent; using QPlainTextEdit::mouseDoubleClickEvent; }; auto hack = static_cast<Hack*>(m_textEdit); switch(event->type()) { case QEvent::MouseButtonPress: hack->mousePressEvent(event); break; case QEvent::MouseMove: hack->mouseMoveEvent(event); break; case QEvent::MouseButtonRelease: hack->mouseReleaseEvent(event); break; case QEvent::MouseButtonDblClick: hack->mouseDoubleClickEvent(event); break; default: break; } } void QmlPlainTextEdit::processWheelEvents(QWheelEvent* event) { class Hack : public QPlainTextEdit{ public: using QPlainTextEdit::wheelEvent; }; static_cast<Hack*>(m_textEdit)->wheelEvent(event); }
Resize widget to fit QML item:
void QmlPlainTextEdit::updateWidgetSize() { m_textEdit->setGeometry(0, 0, static_cast<int>(width()), static_cast<int>(height())); update(); }
To use this class in QML, add in
main.cpp
:qmlRegisterType<QmlPlainTextEdit>("QtWidgets", 1, 0, "QmlPlainTextEdit");
And finally, the QML item would be used like:
QmlPlainTextEdit { id: textEdit focus: true Layout.fillWidth: true Layout.fillHeight: true onFocusChanged: { textEdit.forceActiveFocus() } }
If anybody has a better way to process mouse/wheel events for this scenario, please post it here. I was stuck with this issue for three days and this is the only solution that I found.