QML与C++混合编程

52 篇文章 5 订阅
12 篇文章 6 订阅

简述:

QML与C++混合编程就是使用QML高效便捷地构建UI,而C++则用来实现业务逻辑和复杂算法

1> Qt集成了QML引擎和Qt元对象系统,使得QML很容易从C++中得到扩展,在一定的条件下,QML就可以访问QObject派生类的成员,例如信号槽函数枚举类型属性成员函数等。

QML访问C++有两个方法:

序号方法备注
1在Qt元对象系统中注册C++类,在QML中实例化、访问可以使C++类在QML中作为一个数据类型
2在C++中实例化并设置为QML上下文属性,在QML中直接使用 

2> 在C++中也可以访问QML中的属性、函数和信号。

在C++中加载QML文件可以用QQmlComponentQQuickView,然后就可以在C++中访问QML对象。QQuickView提供了一个显示用户界面的窗口,而QQmlComponent没有。

3> 几个重要的类:

序号作用备注
1QDeclarativeEngine提供了QML的运行环境 
2QDeclarativeContext允许程序使用QML组件显示数据 
3QDeclarativeComponent封装了QML Documents 
4QDeclarativeView用于在应用程序开发过程中进行快速原型开发方便的把QML组件嵌入到QGraphicsView中

系统:Qt 5 + linux

1、QML访问C++

一个C++类要想被QML访问,必须满足两个条件:

序号条件
1QObject类或QObject类的子类派生继承
2使用Q_OBJECT

这和使用信号与槽的前提条件是一样的。QObject类是所有Qt对象的基类,作为Qt对象模型的核心,提供了信号与槽机制等很多重要特性。这两个条件是为了让一个类能够进入 Qt 强大的元对象系统(meta-object system)中,而使用元对象系统,一个类的某些方法或属性才可能通过字符串形式的名字来调用。

1.1 C++类的实现

实现一个可被QML访问的类,可以访问类中的信号槽函数枚举类型属性成员函数等。

1> 几个重要的宏:

序号描述备注
1Q_INVOKABLE可使QML中访问C++public或protected成员函数函数返回类型的前面
2Q_ENUMSQ_ENUMS 宏将枚举类型注冊到元对象系统中,便可被QML访问QML中使用枚举类型的方式是通过C++类型名使用“.”操作符直接访问枚举成员
3Q_PROPERTY用来定义可通过元对象系统訪问的属性能够在 QML 中訪问改动,也能够在属性变化时发射特定的信号

2> Q_PROPERTY宏原型:

Q_PROPERTY( type name
    READ getFunction 
    [WRITE setFunction] 
    [RESET resetFunction]
    [NOTIFY notifySignal]
    [REVISION int]
    [DESIGNABLE bool]
    [SCRIPTABLE bool]
    [STORED bool]
    [USER bool]
    [CONSTANT]
    [FINAL] )
序号项目描述备注
1name属性名,类型可以QVariant支持的任何类型,也可以是自定义类型必选
2READ读取属性值,返回值必须为属性类型或者属性类型的引用或者指针必选
3WRITE设置属性值,返回值必须为void型,带参数,参数类型必须是属性本身的类型或类型的指针或引用可选
4RESET重设属性为默认状态,返回值必须为void型,且不带参数可选
5NOTIFY提供了一个信号,这个信号在值发生改变时会自动被触发可选
6REVISION 可选
7DESIGNABLE表明该属性能在GUI builder(一般为Qt Designer)可见可选
8SCRIPTABLE表明这个属性是否可以被一个脚本引擎操作(默认是true)可选
9STORED表明这是一直存在的可选
10USER定义是否可以被用户所编辑可选
11CONSTANT定义属性是不可修改的,所以不能跟WRITE或者NOTIFY同时出现可选
12FINAL表明该属性不会被派生类中重写可选

 

3> 信号和槽

QML访问的槽必须声明为publicprotected。信号在C++中使用时要用到emit关键字,但在QML中就是个普通的函数,用法同函数一样,信号处理器形式为on,Signal首字母大写。信号不支持重载,多个信号的名字相同而参数不同时,能够被识别的只是最后一个信号,与信号的参数无关。

 

4> C++ 类的实现

#ifndef LOGIN_H
#define LOGIN_H
#include <QObject>
#include <QDebug>
 
#define GAME           "GAME"

class Login: public QObject
{
    Q_OBJECT
    //枚举类型注册
    Q_ENUMS(Color)
    //属性声明
    Q_PROPERTY(Color color READ getColor WRITE setColor NOTIFY colorChanged)
    Q_PROPERTY(QString deviceId READ getDeviceId WRITE setDeviceId)
    Q_PROPERTY(QString password READ getPassword WRITE setPassword)
public:
    Login(QObject *parent) : QObject(parent){ qDebug() << "Welcome.";}
    //枚举
    enum Color
    {
      RED,
      BLUE,
      BLACK
    };
    //成员函数
    Q_INVOKABLE void show(){  qDebug() << "show() is called."; }
    Q_INVOKABLE void login(){ qDebug() << "login() is called."; }
    Color getColor() const{return m_color;}
    void setColor(const Color& color){m_color = color;emit colorChanged();}
    QString getDeviceId() {return deviceId;}
    void setDeviceId(QString id) {deviceId = id;}
    QString getPassword() {return password;}
    void setPassword(QString pw) {password = pw;}
    
public slots:
    void doSomething(Color color)
    {
        qDebug() << "dosomething() is called " << color;
    }
signals:
    void begin();
    void colorChanged();
private:
    Color m_color;//属性 
    QString deviceId;       
    QString password;
};
 
#endif // LOGIN_H

1.2 注册C++类

注册一个C++类,并被QML访问,步骤如下表

序号步骤
1实现一个C++类,请参考 1.1
2将该类注册为QML类型
3在QML导入类型
4在 QML 创建由 C++ 导出的类型的实例并使用

1> QObject派生类可以注册到Qt元对象系统,使得类在QML中同其它内建类型一样,可以作为一个数据类型来使用。QML引擎允许注册可实例化的类型,也可以是不可实例化的类型,常见的注册函数有:

注册函数描述
qmlRegisterInterface() 
qmlRegisterRevision() 
qmlRegisterSingletonType()注冊一个单例类型
qmlRegisterType()注冊一个非单例的类型
qmlRegisterTypeNotAvailable()注冊一个类型用来占位
qmlRegisterUncreatableType()注冊一个具有附加属性的附加类型

template<typename T>int qmlRegisterType(const char *uri,int versionMajor, int versionMinor, const char *qmlName);
模板函数注册C++类到Qt元对象系统中,参数说明如下:

参数说明
uri需要导入到QML中的库/包名
versionMajor主版本号
versionMinor次版本号
qmlName在QML中可以使用的类名

例如, 语句 "import QtQuick.Controls 1.1"中, "QtQuick.Controls" 就是包名 uri ,而 1.1 则是版本号,是 versionMajor 和 versionMinor 的组合。

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "login.h"
 
int main(int argc, char *argv[])
{
  qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
  QGuiApplication app(argc, argv);
  //注册C++类型Login
  qmlRegisterType<Login>("Login.module",1,0,"Login");
 
  QQmlApplicationEngine engine;
  engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
 
  return app.exec();
}

main.cpp中将Login类注册为在QML中可以使用的Login类型,主版本为1,次版本为0,库的名字是Login.module。main.qml中导入了C++库,使用Login构造了一个对象,id为login,可以借助id来访问C++。

注册动作必须在QML上下文创建前,否则无效。

2> 在QML中导入C++注册的类型

import Login.module 1.0

3> 在QML文件中导入C++类并使用

import QtQuick 2.9
import QtQuick.Window 2.2
//导入注册的C++类
import Login.module 1.0
 
Window {
    id:root
    visible: true
    width: 1920
    height: 1080
    title: qsTr("Login QML")
    property string tips: ""
    property int count: 0
    property var array: [
    "qrc:/Res/sound1.wav",
    "qrc:/Res/sound2.wav",
    "qrc:/Res/sound3.wav"
    ]

    signal finished()
    Component.onCompleted: {
        console.log("Hello,Hello")
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            //单击鼠标调用begin信号函数
            login.begin()
            login.show()
            //修改属性
            login.color = 2
            login.password = "123456"
            login.deviceId = "admin"
        }
    }
    Login{
        id:login   //Login类的实例
        onBegin: {
            doSomething(Login.RED)
        }
        onColorChanged: {
            console.log("color changed.")
        }
    }
}

在QML中访问C++的成员函数的形式是“.”,如login.show(),支持函数重载。

C++类中的m_color属性可以在QML中访问、修改,访问时调用了color()函数,修改时调用setColor()函数,同时还发送了一个信号来自动更新color属性值。

1.3 QML上下文属性设置

设置QML上下文属性,并被QML访问,步骤如下表

序号步骤
1实现一个C++类,请参考 1.1
2C++设置QML上下文属性
3在QML中使用

1> C++设置QML上下文属性

Login类先实例化为login对象,然后注册为QML上下文属性

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "login.h"
 
int main(int argc, char *argv[])
{
  qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
  QGuiApplication app(argc, argv);
 
  QQmlApplicationEngine engine;
  Login login;
  engine.rootContext()->setContextProperty("login", &login);
  engine.rootContext()->setContextProperty("GAME", GAME);
  engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
 
  return app.exec();
}
#include <QGuiApplication>
#include <QQuickView>
#include <QQmlContext>
#include "login.h"
 
int main(int argc, char *argv[])
{
  qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
  QGuiApplication app(argc, argv);
 
  QQuickView view;
  Login login;
  view.rootContext()->setContextProperty("login", &login);
  view.rootContext()->setContextProperty("GAME", GAME);
  view.setSource(QUrl(QStringLiteral("qrc:/main.qml")));
  view.show();
 
  return app.exec();
}

engine.rootContext()、viewer.rootContext() 返回的是 QQmlContext 对象。 QQmlContext 类代表一个 QML 上下文,它的 setContextProperty() 方法能够为该上下文设置一个全局可见的属性。

2 > QML 使用

import QtQuick 2.9
import QtQuick.Window 2.2
 
Window {
    id:root
    visible: true
    width: 1920
    height: 1080
    title: qsTr("Login QML")
    property string tips: ""
    property int count: 0
    property var array: [
    "qrc:/Res/sound1.wav",
    "qrc:/Res/sound2.wav",
    "qrc:/Res/sound3.wav"
    ]

    signal finished()
    Component.onCompleted: {
        console.log("Hello,Hello")
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            //单击鼠标调用begin信号函数
            login.begin()
            login.show()
            //修改属性
            login.color = 2
            login.password = 123456
            login.deviceId = admin
        }
    }
    Connections{
        target:login
        onBegin: {
            console.log("begin.")
            console.log(GAME)
        }
        onColorChanged: {
            console.log("color changed.")
        }
    }

}

没有使用 qmlRegisterType()来注册Login类,则qml 中不能再訪问 Login 类,所以不能通过类名来引用它定义的枚举类型,即Login中的枚举类型在QML中是访问不到的。而属性,成员函数,信号和槽均可以访问。

2、C++访问QML

在C++中也可以访问QML中的属性、函数和信号

2.1 C++访问QML的知识要点

1> 加载QML文件

在C++中加载QML文件可以用QQmlComponentQQuickView,然后就可以在C++中访问QML对象。QQuickView提供了一个显示用户界面的窗口,而QQmlComponent没有。

2> 访问QML属性

QObject::property()/setProperty()来读取、修改width属性值。

QQmlProperty::read()/write()来读取、修改height属性值。

如果某个对象的类型是QQuickItem,例如QQuickView::rootObject()的返回值,可以使用QQuickItem::width/setWidth()来访问、修改width属性值。

3>查找组件

QML组件是一个复杂的树型结构,包含兄弟组件和孩子组件,可以使用QObject::findChild()/findChildren()来查找。

方法描述
findChild返回单个对象
findChildren返回对象列表
     //查找 parentWidget 的名为 "button1" 的类型为 QPushButton 的孩子:

     QPushButton *button = parentWidget->findChild<QPushButton *>("button1");

     //查找 parentWidget 全部名为 "widgetname" 的 QWidget 类型的孩子列表

     QList<QWidget *> widgets = parentWidget.findChildren<QWidget *>("widgetname");

4> 访问QML函数

在C++中,使用QMetaObject::invokeMethod()可以调用QML中的函数,从QML传递过来的函数参数和返回值会被转换为C++中的QVariant类型,成功返回true,参数不正确或被调用函数名错误返回false.。

    static bool invokeMethod(QObject *obj, const char *member,
                             Qt::ConnectionType,
                             QGenericReturnArgument ret,
                             QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
                             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 inline bool invokeMethod(QObject *obj, const char *member,
                             QGenericReturnArgument ret,
                             QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
                             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())
    {
        return invokeMethod(obj, member, Qt::AutoConnection, ret, val0, val1, val2, val3,
                val4, val5, val6, val7, val8, val9);
    }

    static inline bool invokeMethod(QObject *obj, const char *member,
                             Qt::ConnectionType type,
                             QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
                             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())
    {
        return invokeMethod(obj, member, type, QGenericReturnArgument(), val0, val1, val2,
                                 val3, val4, val5, val6, val7, val8, val9);
    }

    static inline bool invokeMethod(QObject *obj, const char *member,
                             QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
                             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())
    {
        return invokeMethod(obj, member, Qt::AutoConnection, QGenericReturnArgument(), val0,
                val1, val2, val3, val4, val5, val6, val7, val8, val9);
    }

第一个invokeMethod()参数说明:

参数说明备注
第一个参数被调用对象的指针 
第二个参数访问的函数名 
第三个参数连接类型 
第四个参数用来接收返回值 
其他参数传递被调用方法的参数不能超过10个

必须使用Q_ARG()宏来声明函数参数,用Q_RETURN_ARG()宏来声明函数返回值,其原型如下:

     QGenericArgument           Q_ARG(Type, const Type & value)
     QGenericReturnArgument     Q_RETURN_ARG(Type, Type & value)

 

5> C++中使用QML的信号

使用QObject::connect()可以连接QML中的信号,connect()共有四个重载函数,都是静态函数。必须使用SIGNAL()宏来声明信号,SLOT()宏声明槽函数。

使用QObject::disconnect()可以解除信号与槽函数的连接。

2.2 QML中定义信号、函数、元素属性

QML中添加了一个Rectangle,设置objectName属性值为“rect”,objectName是为了在C++中能够找到Rectangle。

QML中添加了qmlSignal()信号和qmlFunction()函数,信号在QML中发送,函数在C++中调用。

import QtQuick 2.9
import QtQuick.Window 2.2
//导入注册的C++类
import Login.module 1.0
 
Window {
    id:root
    visible: true
    width: 1920
    height: 1080
    color: "white"
    title: qsTr("Login QML")
    property string tips: ""
    property int count: 0
    property var array: [
    "qrc:/Res/sound1.wav",
    "qrc:/Res/sound2.wav",
    "qrc:/Res/sound3.wav"
    ]

    signal finished()
    //定义信号
    signal qmlSigStart(string message)


    Component.onCompleted: {
        console.log("Hello,Hello")
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            //单击鼠标调用begin信号函数
            login.begin()
            login.show()
            //修改属性
            login.color = 2
            login.password = "123456"
            login.deviceId = "admin"
            //发送信号
            qmlSigStart("This is an qml signal.")
        }
    }

    Rectangle{
        //设置objectName属性值为“rect”,objectName是为了在C++中能够找到Rectangle
        objectName: "rect"
        anchors.fill: parent
        color:"red"
    }

    Login{
        id:login   //Login类的实例
        onBegin: {
            doSomething(Login.RED)
        }
        onColorChanged: {
            console.log("color changed.")
        }
    }

    //定义函数
    function qmlFunction(parameter) {
        console.log("qml function parameter is", parameter)
        return "function from qml"
    }

}

2.3  C++ 中访问QML代码示例

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "login.h"
 
int main(int argc, char *argv[])
{
  qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
  QGuiApplication app(argc, argv);
  //注册C++类型Login
  qmlRegisterType<Login>("Login.module",1,0,"Login");
 
  QQmlApplicationEngine engine;
  QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
  QObject* object = component.create();
  qDebug() << "width value is" << object->property("width").toInt();
  object->setProperty("width", 1280);//设置window的宽
  qDebug() << "width value is" << object->property("width").toInt();
  qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt();
  QQmlProperty::write(object, "height", 720);//设置window的高
  qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt();
  QObject* rect = object->findChild<QObject*>("rect");//查找名称为“rect”的元素
  if(rect)
  {
      rect->setProperty("color", "blue");//设置元素的color属性值
      qDebug() << "color is " << object->property("color").toString();
  }
  //调用QML中函数
  QVariant returnedValue;
  QVariant message = "Login from C++";
  QMetaObject::invokeMethod(object, "qmlFunction",
                            Q_RETURN_ARG(QVariant, returnedValue),
                            Q_ARG(QVariant, message));
  qDebug() << "returnedValue is" << returnedValue.toString(); // function from qml

  Login login;
  //连接QML元素中的信号到C++ Login类的槽函数slotStart
  QObject::connect(object, SIGNAL(qmlSigStart(QString)),
                   &login, SLOT(slotStart(QString)));
 
  return app.exec();
}

3、QML嵌入到QWidget中方法

QML嵌入到QWidget中方法

 

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值