背景:
目的是学习qml,因为看到很多qml的酷炫效果,想试一试。
看过网上一些代码,qt提供的工具类好几个,看着就晕。只想提炼一下,做个记录。
我先整理了一套自己的想法:所谓交互,还是qt的信号槽。既然是前后端分离设计,就尽量遵循松散耦合的初衷。后端c++用于写逻辑,就像写库一样,考虑好用途和接口,只要调试通过,就不用管了。只需要把qml当做使用者,去调用c++即可。
为了简单,实例化放在c++中,qml中只管调用即可。
因此,做了一个demo试验一下。
demo:
先用qt新建一个空的quick项目。在c++中添加一个具有信号槽的类,然后在qml中尝试调用它。
先做一个类MyClass.h:
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QObject>
#include <QDebug>
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass();
signals:
void sigFromCpp(QVariant s);
public slots:
inline void onCpp(QString s)
{
qDebug() << "The cpp slot is called:" << __FUNCTION__ << s;
qDebug() << "The cpp signal is sent.";
emit sigFromCpp(s);
}
};
#endif // MYCLASS_H
Myclass.cpp:
#include "myclass.h"
MyClass::MyClass()
{
}
已经尽量简单。
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "myclass.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
//--------------------------------------------
/**
* 这一段是自己添加的。
* 实测:放在engine加载前面没有报错。
* 放在engine加载后面,一样能出结果,但是会有报错。
* 所以,应该放在前面。
*/
QQmlContext *oContext = engine.rootContext();
oContext->setContextProperty("g_iWidth", 500);
oContext->setContextProperty("g_iHeight", 500);
MyClass obj;
oContext->setContextProperty("g_obj", &obj);
//--------------------------------------------
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
只有中间那一段是自己加的。其中QQmlContext::setContextProperty()函数,就当是在c++中,为qml定义全局变量。我打算用c++的命名习惯来做,所以全局变量一律g_开头,变量名加上类型标识。
所以g_iWidth和g_iHeight表示宽和高,g_obj表示c++对象。
main.qml:
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.12
Window {
id: mainwindow
visible: true
width: 640
height: 480
title: qsTr("Hello World")
signal qmlSig(string s)
function qmlSlot(s) {
console.log("The qml slot is called:", s)
//这里使用c++指定的全局变量重新设置窗体大小
mainwindow.setWidth(g_iWidth)
mainwindow.setHeight(g_iHeight)
}
Button {
id: btnChangeSize
text: qsTr("Change size")
onClicked: {
//qml如果要“发信号”给c++,两种方式:
//g_obj.onCpp("The button is clicked.")//直接调用了c++的槽函数
qmlSig("me")//发信号
}
}
onQmlSig: {
console.log("The qml signal is sent.")
g_obj.onCpp("The button is clicked.")
}
Connections {
target: g_obj
/**
* 亲测:函数的“形参”可以不写,函数体中使用的变量名,和c++信号中的一样。
* function onSigFromCpp() {
* qmlSlot(s)//是和c++里的信号对应的:void sigFromCpp(QVariant s);
* }
*
* 当然,如果指定了形参,形参的名字可以用,直接用信号里那个也行。
*/
function onSigFromCpp(ss) {
qmlSlot(ss)//亲测用s也行
}
/**
* 尝试以下写法,也可以的。
*
* onSigFromCpp: {
* qmlSlot(s + "234")
* }
*
* 但是,亲测几点注意:
* 1.不能在后面直接跟参数,像这样不行:onSigFromCpp(s): {...}
* 2.上面参数直接用“s”,是和c++里的信号对应的:void sigFromCpp(QVariant s);
* 3.只能写在connections里面,因为qml是按代码块对应的。就像:
* Button块里面可以直接onClicked,因为clicked信号是button发出的。
* Window块里面可以直接onQmlSig,因为qmlSig信号是在Window块定义的。
*/
}
}
以上代码中,我认为关键的地方都做了注释。本文最后会有总结。
效果:
运行之后显示带一个按钮的默认窗体,点击按钮之后:
qml响应按钮clicked信号,发出自定义信号qmlSig通知c++;
c++槽函数响应,再发出c++信号给qml;
qml槽函数响应。
之所以要兜一圈,仅仅为了测试信号槽的控制方式。
界面如下:
点击按钮之后,窗体改变大小:
同时调试信息输出如下:
qml: The qml signal is sent.
The cpp slot is called: onCpp "The button is clicked."
The cpp signal is sent.
qml: The qml slot is called: The button is clicked.
达到预期。
要点1:
main.cpp中,如果对engine有设置,需要放在load之前。本次demo就是执行setContextProperty那里,应该放在engine.load之前,如果放在后面,亲测运行效果也能出来,但是调试输出是有错误的:
qrc:/main.qml:34:5: QML Connections: Detected function "onSigFromCpp" in Connections element. This is probably intended to be a signal handler but no signal of the target matches the name.
qrc:/main.qml:35: ReferenceError: g_obj is not defined
qrc:/main.qml:35: ReferenceError: g_obj is not defined
qrc:/main.qml:35: ReferenceError: g_obj is not defined
见名知意不用解释,也许能运行是跟qt内部机制有关,感兴趣可以看源码,但只是使用的话,记住最后load即可。
要点2:
关于MyClass,定义的信号形参类型,现在是QVariant,亲测QString也行。具体以后用到时,一切以运行结果为准。
要点3:
关于main.qml,毕竟它是用来描述ui的,我就姑且认为和QWidget类似,一块一块的区域,就像widget对象树那样。所以如果要定义信号或槽,一定要写在对应的“块”当中。
所谓发qml信号,只需要定义一个信号,然后像函数一样调用即可,没有emit。当然c++里不写emit也行(严谨易读,还是写上)。
qml槽,写在对应块里面时,可以像属性一样直接加冒号,就如:onClicked:{...}既然是“属性”,当然要写在对应的“块”当中。
如果qml槽用于响应含参数的c++信号,两种写法:
要么像函数一样带function关键字,可以带形参,也可以不带形参,不带形参时,函数体中直接使用c++信号中的形参名。
要么像属性一样不带形参,带冒号,因为没有形参,只能使用c++信号中的形参名。
我也不知道为什么,只是今天实测的结果是这样。如果有哪位知道原因或者有更好的建议,请赐教。
本文完。