QML与C++混合编程

简述:

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> 几个重要的类:

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

系统:Qt 5 + linux

1、QML访问C++

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

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

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

1.1 C++类的实现

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

1> 几个重要的宏:

序号 描述 备注
1 Q_INVOKABLE 可使QML中访问C++public或protected成员函数 函数返回类型的前面
2 Q_ENUMS Q_ENUMS 宏将枚举类型注冊到元对象系统中,便可被QML访问 QML中使用枚举类型的方式是通过C++类型名使用“.”操作符直接访问枚举成员
3 Q_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] )
序号 项目 描述 备注
1 name 属性名,类型可以QVariant支持的任何类型,也可以是自定义类型 必选
2 READ 读取属性值,返回值必须为属性类型或者属性类型的引用或者指针 必选
3 WRITE 设置属性值,返回值必须为void型,带参数,参数类型必须是属性本身的类型或类型的指针或引用 可选
4 RESET 重设属性为默认状态,返回值必须为void型,且不带参数 可选
5 NOTIFY 提供了一个信号,这个信号在值发生改变时会自动被触发 可选
6 REVISION   可选
7 DESIGNABLE 表明该属性能在GUI builder(一般为Qt Designer)可见 可选
8 SCRIPTABLE 表明这个属性是否可以被一个脚本引擎操作(默认是true) 可选
9 STORED 表明这是一直存在的 可选
10 USER 定义是否可以被用户所编辑 可选
11 CONSTANT 定义属性是不可修改的,所以不能跟WRITE或者NOTIFY同时出现 可选
12 FINAL 表明该属性不会被派生类中重写 可选

 

3> 信号和槽

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

 

4> C++ 类的实现

#ifndef LOGIN_H
#define LOGIN_H
#include 
#include 
 
#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() 注冊一个具有附加属性的附加类型

templateint 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 
#include 
#include 
#include "login.h"
 
int main(int argc, char *argv[])
{
  qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
  QGuiApplication app(argc, argv);
  //注册C++类型Login
  qmlRegisterType("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
2 C++设置QML上下文属性
3 在QML中使用

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

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

#include 
#include 
#include 
#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 
#include 
#include 
#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("button1");

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

     QList widgets = parentWidget.findChildren("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 
#include 
#include 
#include "login.h"
 
int main(int argc, char *argv[])
{
  qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
  QGuiApplication app(argc, argv);
  //注册C++类型Login
  qmlRegisterType("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("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中方法

 

你可能感兴趣的:(Qt,C/C++,Linux)