QML是一种声明性语言,它允许根据用户界面的可视组件以及它们之间的交互和关联来描述用户界面。
Qt QML模块为使用QML语言开发应用程序和库提供了一个框架。它定义并实现了语言和引擎基础结构,并提供了一个API,使应用程序开发人员能够将QML语言扩展为自定义类型,并将QML代码与JavaScript和C++集成在一起。QT QML模块既提供QML API又提供C++ API。
Qt QML模块提供了语言,框架。还有很多可视化的组件、模型视图支持,动画框架,以及很多可视化用户接口。
可以认为:Qt Quick模块是QML应用程序的标准库。
Qt Quick Compiler是Qt Quick应用程序的一个开发附加组件,它允许您将QML源代码编译成最终的二进制文件。使用此加载项时,应用程序的启动时间显著缩短,不再需要将.qml文件与应用程序一起部署。
自Qt 5.11之后,Qt Quick Compiler的功能已经集成到Qt Quick模块中。这个单独的附加组件在Qt早期的长期支持版本中仍然可用。
QQmlEngine类提供了一个用于实例化QML组件的环境。
Qt Quick Controls 在Qt 5.1中引入;使用QPainter之流绘制。
Qt Quick Controls 2在Qt 5.7中引入;基于Scene Graph并使用OpenGL绘制。
可以。不过程序中添加的qml文件,如果不添加到qrc中,是无法加载成功的,因为qml的加载要么是qrc中的资源,要么是一个本地的绝对路径下的文件。
Qbs是一种构建自动化工具,旨在方便地管理跨多个平台的软件项目的构建过程。
qmlscene是用来测试qml文件的工具,即使程序不完整也可以。使用的命令:
qmlscene myqmlfile.qml
可以通过-I custom-qml-path的方式添加自定义qml类型。
参考 Prototyping with qmlscene
在Qt 4.x中对应的工具是qmlviewer。
实际上,qml中不支持qss。最多可以使用 Qml Styling 的方式。不过,qml控件定义不同的样式也很方便,而且支持state所以不支持也没有大碍;Quick控件是支持rich text格式文本的。
Qt Quick UI Forms可以使用可视化工具编辑,QML文件不行,但是可以使用qmlscene来辅助实现预览。Qt Creator中的Tools -> External -> Qt Quick -> Qt Quick 2 Preview (qmlscene)就是这个工具对当前文件的预览调用。
不过,也可以使用.ui.qml的可视化编辑工具打开qml文件来编辑,方法是打开一个.ui.qml文件,然后在可视化编辑工具的导航中,选择“工程”,找到qml文件,双击打开即可。
使用Qt Quick UI Forms的话,可以使用PhotoShop/GIMP导出为QML文件格式,免去开发写界面的麻烦,参考:Exporting Designs from Graphics Software
Qt Quick UI Forms支持的是QML语言的一个子集,它目前:
在特性上:
在类型上:
那么除了使用上面的地方,可以在仅有UI没有逻辑的地方使用Qt Quick UI Forms。(感觉这样的地方不是很多,所以这块儿没有完全弄清楚)
对应场景:QWidget中需要使用QML文件做视图。
Integrating QML Code with Existing Qt UI Code - 介绍说,Qt Quick 可以通过QDeclarativeView加载qml,但是目前Qt 5.9中已经没有这个类了。所以这个选项排除。
Deploying QML Applications 介绍了两个方法在C++中加载QML:
一种是使用QQuickView,这个类最终继承自QWindow,所以创建成功之后可以嵌入QWindow结构中,比如说作为主窗口放在main函数中:
QQuickView view;
view.setSource(QUrl::fromLocalFile("application.qml"));
view.show();
第二种是使用QQmlEngine,使用的场景是:
QQmlEngine engine;
QQmlContext *objectContext = new QQmlContext(engine.rootContext());
QQmlComponent component(&engine, "application.qml");
QObject *object = component.create(objectContext);
除了上面那篇文章提到的情况。在一般QWidget中,可以使用使用QQuickWidget,这个类最终继承自QWidget,所以创建成功之后可以嵌入QWidget结构中:
QQuickWidget *view = new QQuickWidget();
view->setSource(QUrl(QStringLiteral("test_view.qml")));
// then, add into widget architecture
QBoxLayout *layout = new QHBoxLayout();
ui->centralWidget->setLayout(layout);
layout->addWidget(view);
要注意,上面的setSource中参数是运行时的地址,如果是设置了相对地址,需要把资源设置为相对于.app的地址,或者是把qml资源添加到qrc中,使用qrc:/的方式加载。
创建好js文件之后,同qml文件一样,也必须在qrc中添加了,才能在qml文件中import成功。
ref: Interacting with QML Objects from C++
要读写qml中对象的属性,首先需要qml对象有一个名字,方便查找到,比如下面例子中的"rect":
import QtQuick 2.0
Item {
width: 100; height: 100
Rectangle {
anchors.fill: parent
objectName: "rect"
}
}
通过前面介绍的加载qml文件的方式加载之后,通过QQuickWidget/QQuickView的rootObject()得到qml中的根节点对象;然后通过查找得到某个命名的qml对象(子节点):
QQuickView view;
view.setSource(QUrl::fromLocalFile("MyItem.qml"));
view.show();
QObject *object = view.rootObject();
// 1. 直接操作根节点
object->setProperty("width", 500);
QQmlProperty(object, "width").write(500);
// 2. 查找到非根节点并操作
QObject *rect = object->findChild<QObject*>("rect");
if (rect) {
rect->setProperty("color", "red");
}
可以通过QQmlProperty, 或者 QObject::setProperty() and QObject::property()的方法操作qml对象的属性:
// MyItem.qml
import QtQuick 2.0
Item {
property int someNumber: 100
}
QQmlEngine engine;
QQmlComponent component(&engine, "MyItem.qml");
QObject *object = component.create();
qDebug() << "Property value:" << QQmlProperty::read(object, "someNumber").toInt();
QQmlProperty::write(object, "someNumber", 5000);
qDebug() << "Property value:" << object->property("someNumber").toInt();
object->setProperty("someNumber", 100);
// MyItem.qml
import QtQuick 2.0
Item {
function myQmlFunction(msg) {
console.log("Got message:", msg)
return "some return value"
}
}
// main.cpp
QQmlEngine engine;
QQmlComponent component(&engine, "MyItem.qml");
QObject *object = component.create();
QVariant returnedValue;
QVariant msg = "Hello from C++";
QMetaObject::invokeMethod(object, "myQmlFunction",
Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, msg));
qDebug() << "QML function returned:" << returnedValue.toString();
delete object;
// MyItem.qml
import QtQuick 2.0
Item {
id: item
width: 100; height: 100
signal qmlSignal(string msg)
MouseArea {
anchors.fill: parent
onClicked: item.qmlSignal("Hello from QML")
}
}
// main.cpp
class MyClass : public QObject
{
Q_OBJECT
public slots:
void cppSlot(const QString &msg) {
qDebug() << "Called the C++ slot with message:" << msg;
}
};
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQuickView view(QUrl::fromLocalFile("MyItem.qml"));
QObject *item = view.rootObject();
MyClass myClass;
QObject::connect(item, SIGNAL(qmlSignal(QString)),
&myClass, SLOT(cppSlot(QString)));
view.show();
return app.exec();
}
参考下面的例子,信号与方法的连接
Rectangle {
id: relay
signal messageReceived(string person, string notice)
Component.onCompleted: {
relay.messageReceived.connect(sendToPost)
relay.messageReceived.connect(sendToTelegraph)
relay.messageReceived.connect(sendToEmail)
relay.messageReceived("Tom", "Happy Birthday")
}
function sendToPost(person, notice) {
console.log("Sending to post: " + person + ", " + notice)
}
function sendToTelegraph(person, notice) {
console.log("Sending to telegraph: " + person + ", " + notice)
}
function sendToEmail(person, notice) {
console.log("Sending to email: " + person + ", " + notice)
}
function removeTelegraphSignal() {
relay.messageReceived.disconnect(sendToTelegraph)
}
}
信号与信号的连接
Rectangle {
id: forwarder
width: 100; height: 100
signal send()
onSend: console.log("Send clicked")
MouseArea {
id: mousearea
anchors.fill: parent
onClicked: console.log("MouseArea clicked")
}
Component.onCompleted: {
mousearea.clicked.connect(send)
}
}
一个.qml文件,要求文件名首字母大写,保存位置位于使用该qml中定义控件的qml文件的同目录,而且不需要使用import的方式导入。这种方式用起来简便,但是不能为其他目录的qml文件提供支持,要想达到这个目标需要使用下面提到的Identified Module。
ref: Identified Modules
不过想要其他模块使用到还需要做一些工作。
这类自定义的QML模块需要安装在QML的导入目录才能被QML引擎找到,比并且需要使用import语句导入。
导入目录概念和设置参考:QML Import Path
首先要明确一点,如果要定义Identified Module,那么这个module就不属于你创建的工程,而是属于QML引擎使用的全局的模块,所以对应的代码也不需要定义在你创建的工程中,放了也没关系。
假定,将要被编写的module存放目录在ROOT_PATH下,module的名字叫CustomQmlModule,那么目录结构如下:
ROOT_PATH
|-- CustomQmlModule
|-- ...
那么需要在文件夹CustomQmlModule下面创建一个qmldir文件,以及其他功能文件(比如,.qml,C/C++代码,js代码、qml中用到的图片资源等)。假定其他文件有:MyQmlCtrl.qml、MyQmlCtrlList.qml、其他文件比如图片资源,也就是说文件目录如下:
CustomQmlModule
|-- Recommend
|-- RecommendCtrl.qml
|-- RecommendList.qml
|-- 其他文件
|-- qmldir
其中qmldir的内容是:
module Recommend
RecommendCtrl 1.0 RecommendCtrl.qml
RecommendList 1.0 RecommendList.qml
typeinfo pulgin.qmltypes
写好这些文件的功能之后,运行下面的命令创建qmltype文件:
qmlplugindump CustomQmlModule.Recommend 1.0 \
ROOT_PATH > ROOT_PATH/CustomQmlModule/Recommend/plugin.qmltypes
这样就可以生成一个QML模块 - CustomQmlModule.Recommend,该模块中有两个控件RecommendCtrl和RecommendList。
做完上面的操作之后,想要QML引擎找到这个QML模块,还需要将这个模块所在路径添加到QML的导入路径,方法有几种:
如果是想要在运行时加载qml文件时,设置引用的自定义/第三方QML模块,可以通过调用QQmlEngine::addImportPath()或者设置环境变量QML2_IMPORT_PATH来实现。
一旦添加到QML导入目录,那么这个模块就不需要添加到具体某个项目中了。这里个人推荐使用环境变量QML2_IMPORT_PATH的方式,可以将所有的自定义模块定义在一个特定的目录,或者通过一个git项目管理,然后各个开发通过git的方式下载到某个目录ROOT_PATH,并把这个ROOT_PATH添加到QML2_IMPORT_PATH。这样只要设置好环境变量之后,更新模块仅需更新该目录中的文件、或者git pull即可。
将远端的QML模块URL地址设置到QML的导入路径,这样也可以用上面相同的方式使用。
不过,通过网络加载的QML模块只能有qml和js文件,不能有C/C++插件。
使用环境变量 QML_IMPORT_TRACE,具体方法是在Qt Creator的工程设置tab中的Build Envirenment中添加QML_IMPORT_TRACE,并且不需要设置值。
QML有版本更新的用法。这种用法提供了一个好处,可以通过版本更新添加新的功能,而老的功能可以继续保留在老的代码中。一个模块中可以有多种文件:.qml,.qmltypes,C/C++代码,js代码等。都可以保存在qmldir文件中。
如果要更新版本,可以将上面例子中的Recommend文件夹名修改为Recommend2.0,表示CustomQmlModule中只有这个Recommend模块需要更新。也可以将CustomQmlModule文件夹名修改为CustomQmlModule2.0表示整个模块都要升级了。当然记得保留原来的目录,因为老的代码中还是用这些老的模块。
这中场景对应:QML中使用C++对象类型,下面的方法介绍如何将Qt C++对象的属性、方法等暴露给QML类型系统(QML Type System)。
ref: Exposing Attributes of C++ Types to QML
基于QML engine与Qt meta-object系统的整合,QML可以(比较)简便地使用C++代码,只要继承自QObject即可。所以继承自QObject的类中可以供QML直接访问使用的成员有:
class Message : public QObject
{
Q_OBJECT
Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
public:
void setAuthor(const QString &a) {
if (a != m_author) {
m_author = a;
emit authorChanged();
}
}
QString author() const {
return m_author;
}
signals:
void authorChanged();
private:
QString m_author;
};
// main.cpp
class ApplicationData : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE QDateTime getCurrentDateTime() const {
return QDateTime::currentDateTime();
}
};
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQuickView view;
ApplicationData data;
view.rootContext()->setContextProperty("applicationData", &data);
view.setSource(QUrl::fromLocalFile("MyItem.qml"));
view.show();
return app.exec();
}
// MyItem.qml
import QtQuick 2.0
Text {
text: applicationData.getCurrentDateTime()
// 使用Connections检测Qt C++对象的属性变化
Connections {
target: applicationData
onDataChanged: console.log("The application data changed!")
}
}
命名建议: 属性变化的Notify建议命名格式:Changed,QML engine无论如何都是使用onChanged的格式来为属性变化的信号处理函数命名
如果要使用列表类型的属性,不能使用QList,因为QList不是继承自QObject的类型;而是需要使用QQmlListProperty,同时,QQmlListProperty的模版类型需要注册以供QML类型系统识别。
在使用QQmlContext的过程中发现:目前仅能把C++数据暴露给qml中的根上下文,如果是非根节点的qml对象,无法使用C++数据。如果这个属实的话,如果一个qml文件中的 非根节点 qml类型对象使用到C++数据,那么就需要单独把这块儿qml代码拎出来,重新写到一个新的根节点中包含该C++数据的qml文件中。
后来发现可以通过为非根节点指定objectName,并在C++代码中通过QObject的findChild的方法找到子节点对象,进而设置给它C++数据。
在 Important Concepts In Qt Quick - Positioning 中介绍到,一般情况下,使用Positioners就可以高效地完成布局,同时它们也是自己的容器;而Layouts除了控制位置,还可以控制大小,适用于界面大小变化的场景。
对应场景:QML中需要使用C++对象作为数据源。当然,最终这个qml文件也可以供 Widget-based / QuickView-based / QML 的程序使用。
ref: Models and Views in Qt Quick
如上图所示:
Model:数据源
View:展示数据的容器
Delegate:决定数据如何在View中展示
Qt Quick中提供的View有ListView,GridView,PathView。
在View中可以绑定Model和Delegate,一般情况下使用匿名Component来做Delegate
常规代码示例 :
Rectangle {
width: 200; height: 200
ListModel {
id: fruitModel
property string language: "en"
ListElement {
name: "Apple"
cost: 2.45
}
ListElement {
name: "Orange"
cost: 3.25
}
ListElement {
name: "Banana"
cost: 1.95
}
}
Component {
id: fruitDelegate
Row {
id: fruit
Text { text: " Fruit: " + name; color: fruit.ListView.view.fruit_color }
Text { text: " Cost: $" + cost }
Text { text: " Language: " + fruit.ListView.view.model.language }
}
}
ListView {
property color fruit_color: "green"
model: fruitModel
delegate: fruitDelegate
anchors.fill: parent
}
}
QML代码:
ListView {
width: 100; height: 100
model: myModel
delegate: Rectangle {
height: 25
width: 100
Text { text: modelData }
}
}
C++代码:
QStringList dataList;
dataList.append("Item 1");
dataList.append("Item 2");
dataList.append("Item 3");
dataList.append("Item 4");
QQuickView view;
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", QVariant::fromValue(dataList));
不过,上面这种方法通过rootContext设置属性,无法通过QQuickWidget加载数据。QHash RecommendListModel::roleNames() const
{
QHash roles;
// role names will be use in qml files.
roles[Role_CategoryName] = "categoryName";
roles[Role_ContentId] = "contentID";
roles[Role_ContentName] = "contentName";
roles[Role_CoverUrl] = "coverUrl";
roles[Role_CreatorName] = "creatorName";
roles[Role_IsTry] = "isTry";
roles[Role_LinkUrl] = "linkUrl";
roles[Role_ShowCount] = "showCount";
roles[Role_CountType] = "countType";
roles[Role_IsCoupon] = "isCoupon";
roles[Role_OrigianlPrice] = "originalPrice";
roles[Role_CurrentPrice] = "currentPrice";
roles[Role_ActivityName] = "activityName";
roles[Role_HasDaKa] = "hasDaKa";
roles[Role_Tag] = "tag";
roles[Role_ActivityType] = "activityType";
return roles;
}
ref: Writing QML Extensions with C++,Creating C++ Plugins for QML
如果需要为QML类型提供一种独立lib文件格式的插件,可以按照下面的步骤:
contactlist:演示自定义C++类做model,view是一个自定义Qt Quick UI Form类。
[D.0] 主要入口
[D.1] 示例代码
[D.2] 另一种工程构建工具:Qbs:Qbs Manual
[D.3] Qt Quick Controls Overview 有两个版本:
关于使用哪一个版本,参考:
[D.4] 杂项:
[D.5] 自定义QML对象
[D.6] Qt Quick file (.qml) vs Qt Quick UI Forms file (.ui.qml)
[D.7] QML和C++混编 :
[D.9] 一些概念
[D.10] js相关:
[D.11] 非官方资料: