【写在前面】
有时候,当我们接手一个老项目( 也可能不老-.- )时,会碰上这些情况:
1、老项目使用 QWidgets,想要部分或全部迁移到 Qml。
2、老项目使用 Qml,想要部分或全部迁移到 QWidgets。
然而,很多时候全部迁移几乎不现实,此时,折中的方案应运而生:QWidgets 和 Qml 相互嵌套使用。
本篇主要内容:
1、Qml 嵌入 QWidgets 中的方法及一些坑;
2、QWidgets 嵌入 Qml 中的方法及一些坑;
【正文开始】
Qml 嵌入 QWidgets 中相当容易,细数有两种方式:
1、使用 static QWidget *QWidget::createWindowContainer(QWindow *, QWidget *parent, Qt::WindowFlags flags)。
这种方式以 QWindow 为参数来创建一个 QWidget 容器,而 QWindow 又是 Qml 的容器。
QWidget *widget = new QWidget;
QQuickWindow *quickWindow = new QQuickWindow;
QWidget *center = QWidget::createWindowContainer(quickWindow, widget);
此时便已经将 Qml 嵌入QWidget 中,后面所有的 Qml 项在此 quickWindow 中即可。
另外,QQuickView 继承自 QQuickWindow ,可以根据喜好来使用。
要将 quickWindow 脱离出来也相当容易,使用 setParent(nullptr) 即可。
然而,文档中也提到,这种方式有很多缺点:
可能会出现性能问题。
堆叠顺序,可能无法正确处理覆盖问题。
焦点问题,可能无法正确传递焦点。
在 QWidget 使用过多的窗口容器将损坏整个程序的性能。
综上,这并不是一个很好的嵌入方案。
2、推荐方案的关键类为 QQuickWidget。
要使用 QQuickWidget,需 QT += quickwidgets。
使用方法也相当简单:
QQuickWidget *view = new QQuickWidget;
view->setSource(QUrl::fromLocalFile("QmlComponent.qml"));
view->show();
然而,在使用时发现这种方法有一些坑。
首先来看看代码 QmlComponent.qml:
import QtQuick 2.12
Rectangle {
width: 100
height: 100
color: "#cc00ff00"
property int count: 0
Text {
anchors.centerIn: parent
text: count
color: "red"
font.pointSize: 14
}
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
print('click');
++parent.count;
//mouse.accepted = false;
}
onPressed: {
parent.x += 1;
parent.y += 1;
//mouse.accepted = false;
}
onReleased: {
parent.x -= 1;
parent.y -= 1;
mouse.accepted = false;
}
}
}
main.cpp:
#include <QApplication>
#include <QLabel>
#include <QWidget>
#include <QQuickWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
protected:
virtual void mousePressEvent(QMouseEvent *event);
virtual void mouseReleaseEvent(QMouseEvent *event);
};
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
resize(800, 600);
QLabel *label = new QLabel("text", this);
auto s = (size() - label->size()) / 2;
label->move(s.width(), s.height());
label->show();
QQuickWidget *qmlWidget = new QQuickWidget(QUrl("qrc:/QmlComponent.qml"), this);
qmlWidget->setAttribute(Qt::WA_AlwaysStackOnTop);
qmlWidget->setClearColor(Qt::transparent);
qmlWidget->setResizeMode(QQuickWidget::SizeViewToRootObject);
s = (size() - qmlWidget->size()) / 2;
qmlWidget->move(s.width(), s.height());
qmlWidget->show();
update();
}
Widget::~Widget()
{
}
void Widget::mousePressEvent(QMouseEvent *event)
{
qDebug() << "Widget mosue press event";
event->ignore();
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
qDebug() << "Widget mosue release event";
event->ignore();
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Widget w;
w.show();
app.exec();
}
#include "main.moc"
具体有两个大坑:
- 背景透明:
将其他小部件放在下面并使 QQuickWidget 透明不会导致预期的结果:下面的小部件将不可见。
原因是 QQuickWidget 是在所有其他常规的非 OpenGL 小部件之前绘制的。
解决方案是:将其置于顶层并设置清除颜色:
qmlWidget->setAttribute(Qt::WA_AlwaysStackOnTop);
qmlWidget->setClearColor(Qt::transparent);
- 事件传播:
如果 Qml 中使用了 MouseArea,则一些事件不能正常传播:
例如 clicked 和 released。
另一方面,传递了 pressed 后,自身的 ( MouseArea ) 将无法接受到 released 事件。
这个坑目前没有找到好的解决方案 ( Ծ‸ Ծ )。
QWidgets 嵌入 Qml 中则更加简单:
1、使用 QWidget 的构造函数,传入父 Widget:QWidget::QWidget(QWidget *parent)。
2、或者使用 QWidget::setParent() 设置父 Widget,本质与上面一样。
由于 QQuickWidget 继承自 QWidget,因此可以直接成为其他 QWidget 的父 Widget。
QQuickWidget *qmlWidget = new QQuickWidget(QUrl("qrc:/QmlComponent.qml"));
qmlWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
qmlWidget->resize(800, 600);
qmlWidget->show();
QLabel *label = new QLabel("text", qmlWidget);
label->show();
注意:此时添加的 QWidget 将会处于 QQuickWidget 上方。
【结语】
呼~最后,虽然 Qml 和 QWidgets 相互嵌套有不少坑,但是在一些场合,确实是相当有用的。