- 通过qml高效便捷的构建UI界面,使用C ++来实现业务逻辑和复杂算法
- Qt中提供了两种在 QML 环境中使用C ++对象的方式
- 第一种:在C ++中实现一个类,注册到qml环境中,qml环境中使用该类型创建对象
- 第二种:在C ++中构造一个对象,将这个对象设置为qml的上下文属性,在qml环境中直接使用该属性
第一种 实现可以被QML访问的 C++ 类
- 必须满足两个条件:一是派生自QObject类或QObject类的子类,二是使用Q_OBJECT宏
- 新建C++ Class
mixing.h
#ifndef MIXING_H
#define MIXING_H
#include <QObject>
#include <QColor>
class Mixing : public QObject
{
Q_OBJECT
public:
explicit Mixing(QObject *parent = nullptr);
signals:
void colorChanged(const QColor & color);
public slots: //槽必须被声明为public或protected
void start();
};
#endif // MIXING_H
mixing.cpp
#include "mixing.h"
#include <QDebug>
Mixing::Mixing(QObject *parent) : QObject(parent)
{
}
void Mixing::start()
{
qDebug() << "start";
emit colorChanged(Qt::blue);
}
- 将Mixing类注册为qml类型—注册类型qmlRegisterType
原型:
template<typename T>
int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)
模板参数typename ,即要实现的 C++ 类的类名。
第一个参数uri ,指定一个唯一的包名,一是用来避免名字冲突,二是可以把多个相关类聚合到一个包中方便引用。如我们常写这个语句 “import QtQuick.Controls 2.3” ,其中的 “QtQuick.Controls” 就是包名 uri
而2.3则是版本,是versionMajor=2和versionMinor=3的组合。
qmlName则是 QML中可以使用的类名。
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "mixing.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
//注册类型 注册动作一定要放在 QML 上下文创建之前,否则的话,注册是没有用的
//Mixing类注册成为Qml类型Mixing,主版本是1,次版本是0,包名是an.qt.Mixing
qmlRegisterType<Mixing>("an.qt.Mixing", 1, 0, "Mixing");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
- qml导入Mixing类
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import an.qt.Mixing 1.0
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("mixing")
MouseArea{
anchors.fill: parent
onClicked: {
mixing.start()
}
}
Mixing{
id: mixing
onColorChanged: {
root.color = color
}
}
}
- 结果
枚举类型
- 在注册的类中使用枚举类型,可以使用Q_ENUMS 宏将该枚举注册到元对象系统中
mixing.h
#ifndef MIXING_H
#define MIXING_H
#include <QObject>
#include <QColor>
class Mixing : public QObject
{
Q_OBJECT
Q_ENUMS(BALL_COLOR)
public:
explicit Mixing(QObject *parent = nullptr);
enum BALL_COLOR{
BALL_COLOR_YELLOW,
BALL_COLOR_BLUE,
BALL_COLOR_GREEN,
};
signals:
void colorChanged(const QColor & color);
public slots: //槽必须被声明为public或protected
void start(BALL_COLOR ballColor);
};
#endif // MIXING_H
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import an.qt.Mixing 1.0
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("mixing")
MouseArea{ //点击鼠标左键,窗口颜色变蓝;点击鼠标右键,窗口颜色变绿;双击鼠标,窗口颜色变黄
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if(mouse.button === Qt.LeftButton)
{
mixing.start(Mixing.BALL_COLOR_BLUE)
}else if(mouse.button === Qt.RightButton)
{
mixing.start(Mixing.BALL_COLOR_GREEN)
}
}
onDoubleClicked: {
mixing.start(Mixing.BALL_COLOR_GREEN)
}
}
Mixing{
id: mixing
onColorChanged: {
root.color = color
}
}
}
mixing.cpp
#include "mixing.h"
#include <QDebug>
Mixing::Mixing(QObject *parent) : QObject(parent)
{
}
void Mixing::start(BALL_COLOR ballColor)
{
QColor color;
qDebug() << "start";
switch (ballColor) {
case BALL_COLOR_BLUE:
color = Qt::blue;
break;
case BALL_COLOR_GREEN:
color = Qt::green;
break;
case BALL_COLOR_YELLOW:
color = Qt::yellow;
break;
default:
break;
}
emit colorChanged(Qt::blue);
}
C++ 类的属性和成员函数
- 在定义一个类的成员函数时使用Q_INVOKABLE宏来修饰,这个宏必须放在返回函数前面, 且在QML中访问的前提是public或protected成员函数
- 定义属性需要使用 Q_PROPERTY宏,通过定义的属性,可以在 QML 中访问、修改,也可以在属性变化时发射特定的信号。使用 Q_PROPERTY 宏,定义的类必须是QObject的后代,必须在类首使用Q_OBJECT宏
Q_PROPERTY宏的原型
Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])
-
属性的type、name是必需的,其它是可选项,最常用的有READ、WRITE、NOTIFY。属性的type可以是QVariant支持的任何类型,也可以是自定义类型,包括自定义类、列表类型、组属性等。另外,属性的READ、WRITE、RESET是可以被继承的,也可以是虚函数,这些特性并不常用。
-
READ:读取属性值,如果没有设置MEMBER的话,它是必需的。一般情况下,函数是个const函数,返回值类型必须是属性本身的类型或这个类型的const引用,没有参数。
-
WRITE:设置属性值,可选项。函数必须返回void,有且仅有一个参数,参数类型必须是属性本身的类型或这个类型的指针或引用。
-
NOTIFY:与属性关联的可选信号。这个信号必须在类中声明过,当属性值改变时,就可触发这个信号,可以没有参数,有参数的话只能是一个类型同属性本身类型的参数,用来记录属性改变后的值。
实现
mixing.h
#ifndef MIXING_H
#define MIXING_H
#include <QObject>
#include <QColor>
class Mixing : public QObject
{
Q_OBJECT
Q_ENUMS(BALL_COLOR)
//修饰了名为number的属性,number通过Number函数读得数据,通过setNumber函数写入数据,触发信号是Numberchanged函数
Q_PROPERTY(unsigned int number READ Number WRITE setNumber NOTIFY Numberchanged)
public:
explicit Mixing(QObject *parent = nullptr);
enum BALL_COLOR{
BALL_COLOR_YELLOW,
BALL_COLOR_BLUE,
BALL_COLOR_GREEN,
};
unsigned int Number() const;
void setNumber(const unsigned int & Number);
Q_INVOKABLE void stop();
signals:
void colorChanged(const QColor & color);
void Numberchanged();
public slots: //槽必须被声明为public或protected
void start(BALL_COLOR ballColor);
private:
unsigned int m_Number;
};
#endif // MIXING_H
mixing.cpp
#include "mixing.h"
#include <QDebug>
Mixing::Mixing(QObject *parent) : QObject(parent)
{
}
void Mixing::start(BALL_COLOR ballColor)
{
QColor color;
qDebug() << "start";
switch (ballColor) {
case BALL_COLOR_BLUE:
color = Qt::blue;
break;
case BALL_COLOR_GREEN:
color = Qt::green;
break;
case BALL_COLOR_YELLOW:
color = Qt::yellow;
break;
default:
break;
}
emit colorChanged(Qt::blue);
}
unsigned int Mixing::Number() const
{
return m_Number;
}
void Mixing::setNumber(const unsigned int &number)
{
if(number != m_Number)
{
m_Number = number;
emit Numberchanged();
}
}
void Mixing::stop()
{
qDebug() << "颜色改变ing...";
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import an.qt.Mixing 1.0
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("mixing")
MouseArea{
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if(mouse.button === Qt.LeftButton)
{
mixing.start(Mixing.BALL_COLOR_BLUE)
}else if(mouse.button === Qt.RightButton)
{
mixing.start(Mixing.BALL_COLOR_GREEN)
}
}
onDoubleClicked: {
mixing.start(Mixing.BALL_COLOR_GREEN)
mixing.number = 10
}
}
Mixing{
id: mixing
onColorChanged: {
root.color = color
mixing.stop(color)
}
Component.onCompleted:
{
console.log("default ball number is ", number)
}
onNumberChanged:
{
console.log("new ball number is ", number)
}
}
}
第二种 QML上下文属性设置
- QQmlContext类
定义了qml引擎内的上下文,上下文允许将数据暴露给由qml引擎实例化的qml组件
每个QQmlContext包含一组属性,允许以名称将数据显式地绑定到上下文。通过调用QQmlContext::setContextProperty()来定义和更新上下文属性
void QQmlContext::setContextProperty(const QString &name, const QVariant &value)
简单的上下文属性,对应的值为QVariant类型。
void QQmlContext::setContextProperty(const QString &name, QObject *value)
相对来说稍微复杂一些,QObject*对象类型。
设置简单的上下文属性
- 新建一个Qt quick工程
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QQmlContext>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQuickView view;
view.rootContext()->setContextProperty("Data", QString("设置Qml上下文属性"));
view.setSource(QUrl(QStringLiteral("qrc://main.qml")));
view.show();
return app.exec();
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
Rectangle { //main.cpp已经view.show(),所以不需要在Window对象下定义控件
visible: true
width: 640
height: 480
color: "lightgray"
Text {
id: text
anchors.centerIn: parent
text: Data
}
}
设置对象为上下文属性
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QQmlContext>
#include "mixing.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
Mixing mixing;
QQuickView view;
view.rootContext()->setContextProperty("mixing", &mixing);
view.setSource(QUrl(QUrl(QStringLiteral("qrc://main.qml"))));
view.show();
return app.exec();
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
Rectangle { //main.cpp已经view.show(),所以不需要Window对象下定义控件
id: root
visible: true
width: 640
height: 480
MouseArea{
anchors.fill: parent
acceptedButtons:Qt.LeftButton | Qt.RightButton;
onClicked: {
mixing.start()
mixing.number = 10
}
}
Connections{
target: mixing
onColorChanged: {
root.color = color
mixing.stop(color)
}
onNumberChanged:{
console.log("new ball number is", mixing.number) // 10
}
}
}
- 因为去掉了qmlRegisterType() 调用,所以在 main.qml中不能再访问Mixing类了,比如说不能通过类名来引用它定义的BALL_COLOR枚举类型了,即枚举类型的值不可以调用
Mixing.cpp修改start函数 以及 Mixing.h的start函数去掉形参
void Mixing::start()
{
//QColor color;
qDebug() << "start";
// switch (ballColor) {
// case BALL_COLOR_BLUE:
// color = Qt::blue;
// break;
// case BALL_COLOR_GREEN:
// color = Qt::green;
// break;
// case BALL_COLOR_YELLOW:
// color = Qt::yellow;
// break;
// default:
// break;
// }
emit colorChanged(Qt::blue);
}
C++ 访问qml的属性、函数、信号
- 以上介绍了在qml中访问 C++ 中属性和方法。反过来,在 C++ 中加载qml文件可以用QQmlComponent或QQuickView,然后就可以在 C++ 中访问Qml对象。QQuickView提供了一个显示用户界面的窗口,而QQmlComponent没有。
QQmlComponent在C++中访问qml中的属性
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import an.qt.Mixing 1.0
Window {
id: root
visible: true
width: 640
height: 480
Rectangle { //
objectName: "rect" //这个值是为了在C++中能够找到这个Rectangle
anchors.fill: parent
}
MouseArea{
anchors.fill: parent
acceptedButtons:Qt.LeftButton | Qt.RightButton;
onClicked: {
mixing.start()
mixing.number = 10
qmlSignal("这是qml文件中的qml信号")
}
}
Mixing{
id:mixing
}
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "mixing.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<Mixing>("an.qt.Mixing", 1, 0, "Mixing");
QQmlEngine engine;
QQmlComponent compontext(&engine, QUrl(QStringLiteral("qrc:///main.qml")));
QObject *object = compontext.create();
qDebug() << "width value is " << object->property("width").toInt(); //读取width属性值
object->setProperty("width", 320); //修改width属性值
qDebug() << "height value is " << QQmlProperty::read(object, "height").toInt(); //读取height属性值
QQmlProperty::write(object, "height", 240); //修改height属性值
QObject *rect = object->findChild<QObject*>("rect");
if(rect)
{ //查找名字为rect的孩子组件,如果找到的话就将其颜色设置为橘黄色
rect->setProperty("color", "orange");
}
return app.exec();
}
在C++中访问qml中的函数与信号
-
C++中,使用 QMetaObject::invokeMethod() 可以调用QML中的函数,它 是个静态方法,函数原型:
bool QMetaObject::invokeMethod(QObject * obj, const char * member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument( 0 ), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()) [static]
- 它的返回值如果为true则表明调用成功,返回false,则说明参数不正确或被调用函数名错误。重点关注它的前四个参数。
第一个参数是被调用对象的指针
第二个参数是方法名字
第三个参数是连接类型
第四个参数用来接收返回值 - 必须使用Q_ARG() 宏来声明函数参数,用Q_RETURN_ARG() 宏来声明函数返回值。
- 传递给被调用方法的参数
QGenericArgument Q_ARG(Type, const Type & value)
- 返回类型
QGenericReturnArgument Q_RETURN_ARG(Type, Type & value)
- 它的返回值如果为true则表明调用成功,返回false,则说明参数不正确或被调用函数名错误。重点关注它的前四个参数。
-
信号传递到 C++ :使用 QObject::connect() 可以连接QML中的信号,connect()共有四个重载函数,它们都是静态函数。必须使用SIGNAL()宏来声明信号,SLOT()宏声明槽函数
-
QObject::disconnect() 可以解除信号与槽函数的连接
main.qml 添加一个方法和信号
import QtQuick 2.9
import QtQuick.Window 2.2
import an.qt.Mixing 1.0
Window {
id: root
visible: true
width: 640
height: 480
signal qmlSignal (string message) //单击会输出message的值
onQmlSignal: console.log("这是一个信号:", message)
function qmlFunction(parameter)
{
console.log("这是一个方法", parameter)
return "function from qml"
}
Rectangle {
objectName: "rect" //这个值是为了在C++中能够找到这个Rectangle
anchors.fill: parent
}
MouseArea{
anchors.fill: parent
acceptedButtons:Qt.LeftButton | Qt.RightButton;
onClicked: {
mixing.start()
mixing.number = 10
qmlSignal("这是qml文件中的qml信号")
}
}
Mixing{
id:mixing
}
}
main.cpp 调用信号和方法
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "mixing.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<Mixing>("an.qt.Mixing", 1, 0, "Mixing");
QQmlEngine engine;
QQmlComponent compontext(&engine, QUrl(QStringLiteral("qrc:///main.qml")));
QObject *object = compontext.create();
qDebug() << "width value is " << object->property("width").toInt(); //读取width属性值
object->setProperty("width", 320); //修改width属性值
qDebug() << "height value is " << QQmlProperty::read(object, "height").toInt(); //读取height属性值
QQmlProperty::write(object, "height", 240); //修改height属性值
QObject *rect = object->findChild<QObject*>("rect");
if(rect)
{ //查找名字为rect的孩子组件,如果找到的话就将其颜色设置为橘黄色
rect->setProperty("color", "orange");
}
QVariant returnedValue, message = "Hello from c++";
QMetaObject::invokeMethod(object, "qmlFunction",
Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, message));
qDebug() << "returnedValue is " << returnedValue.toString();
Mixing mixing;
QObject::connect(object, SIGNAL(qmlSignal(QString)),
&mixing, SLOT(cppSlot(QString)));
return app.exec();
}
mixing.h中定义cppSlot槽函数
public slots: //槽必须被声明为public或protected
void start();
void cppSlot(const QString &message)
{
qDebug() << "这是一个qml信号:" << message;
}
- 效果