QML与C++混合编程

文章参考:
https://blog.csdn.net/foruok/article/details/32698603
http://blog.51cto.com/9291927/1975383

QT项目开发过程中,画面上显示的布局以及对应的数据可能会动态变化,如果只用QML来构件简单的布局是无法满足需求的,所以会用通过C++代码来实现业务逻辑,QML负责构件UI。这里其实有MVC模式的思想。
QML与C++混合编程简介
QML与C++混合编程就是使用QML高效便捷地构建UI,而C++则用来实现业务逻辑和复杂算法。

Qt Quick与QML
Qt Quick是Qt User Interface Creation Kit的缩写。
QT框架简介中可以了解到,QML (Qt Meta-Object Language的缩写)其实是对 JavaScript 的扩展,是Qt Quick最重要的组成部分。
Qt Quick包含一个组件集合,大部分用于图形界面。同时也包含一个用于管理组件并与组件交互的C++ API——QtDeclarative模块,用于QML与C++之间的桥梁。
C++与QML的交互通过向QML注册C++对象实现,其中C++实现中,非可视化的类均为QObject的子类,可是化的类型均为QDeclarativeItem的子类,而QDeclarativeItem等同于QML的Item类。

QML 中使用 C++ 类和对象
Qt 提供了两种在 QML 环境中使用 C++ 对象的方式:

  1. 在 C++ 中实现一个类,注册到 QML 环境中, QML 环境中使用该类型创建对象;
  2. 在 C++ 中构造一个对象,将这个对象设置为 QML 的上下文属性,在 QML 环境中直接使用改属性.
    不管哪种方式,对要导出的 C++ 类都有要求,不是一个类的所有方法、变量都可以被 QML 使用,因此我们先来看看怎样让一个方法或属性可以被 QML 使用。

C++类的实现
C++类要想被QML访问,首先必须满足两个条件:

  1. 派生自QObject类或QObject类的子类;
  2. 二是使用Q_OBJECT宏。
    这两个条件跟QT信号和槽的实现一样,是为了让一个类能够进入 Qt 强大的元对象系统(meta-object system)中,只有使用元对象系统,一个类的某些方法或属性才可能通过字符串形式的名字来调用,才具有了在 QML 中访问的基础条件。

信号与槽实现
只要是信号或者槽,都可以在 QML 中访问。
信号在C++中使用时要用到emit关键字,但在QML中就是个普通的函数,用法同函数一样。

注册QML类型
int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName) 有两个模板函数:

template<typename T>
  int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName);

  template<typename T, int metaObjectRevision>
  int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName);

前一个原型一般用来注册一个新类型,而后一个可以为特定的版本注册类型。通常使用的是第一个,后面这个有兴趣可以自己详细了解。
参数含义:
typename :实现的 C++ 类的类名。
uri :让你指定一个唯一的包名,类似 Java 中的那种,一是用来避免名字冲突,而是可以把多个相关类聚合到一个包中方便引用。比如我们常写这个语句 “import QtQuick 1.0” ,其中的 “QtQuick” 就是包名 uri
versionMajor:指上述包名后面1.0中的“1”
versionMinor:指上述包名后面1.0中的“0”
qmlName : QML 中可以使用的类名。
例如:

qmlRegisterType<TestHuman >("qt.test.TestHuman ", 1, 0, "TestHuman "); //只要保证在加载qml之前即可

当将C++类注册成功之后则可以在 QML 中创建 C++ 导入类型的对象了,与 QML 内建类型的使用完全一样。如下是创建一个 TestHuman 实例的代码:

import QtQuick 2.0
import qt.test.TestFlowers 1.0
Rectangle {
    width: 200;
    height: 200;
    
    TestFlowers {
        id: testflowers ;
        anchors.top: parent.top;
    }
	Button{
	id: testBtn1;
        text: qsTr("Hello World");
        anchors.top: testflowers .bottom;
        onClicked: {
                testHuman .onBtnClick();
            }
    }
}

上述qml文件可以看出C++对象可以跟其它部件使用完全一样,qml文件中通常要使用C++对象的成员属性、方法,为了能让C++的相关数据能被访问,则需要做特殊处理。

Q_PROPERTY
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 :如果你没有为属性指定 MEMBER 标记,则 READ 标记必不可少;声明一个读取属性的函数,该函数一般没有参数,返回定义的属性。
  • WRITE:可选配置。声明一个设定属性的函数。它指定的函数,只能有一个与属性类型匹配的参数,必须返回 void 。
  • WRITE:可选配置。声明一个设定属性的函数。它指定的函数,只能有一个与属性类型匹配的参数,必须返回 void 。
    其它不常用的就不介绍了,感兴趣的可以查看SDK。

Q_ENUMS
如果你要导出的类定义了想在 QML 中使用枚举类型,可以使用 Q_ENUMS 宏将该枚举注册到元对象系统中。

Q_INVOKABLE
在定义一个类的成员函数时使用 Q_INVOKABLE 宏来修饰,就可以让该方法被元对象系统调用。这个宏必须放在返回类型前面。在 QML 中就可以用 O b j e c t . {Object}. {method} 来访问。

代码:
C++类(.h文件):

class TestFlowers : public QObject
{
    Q_OBJECT
	Q_ENUMS(Color)
	Q_PROPERTY(Color color READ color WRITE setColor NOTIFY colorChange)
	Q_INVOKABLE void (onBtnClick)
public:
    TestFlowers (QObject *parent = 0);
    ~TestFlowers ();
	
	enum Color {
		RED,
		GREEN,
		BLUE
	};
    
};

qml:

import QtQuick 2.0
import qt.test.TestFlowers 1.0
Rectangle {
    width: 200;
    height: 200;
    
    TestFlowers {
        id: testflowers ;
        anchors.top: parent.top;
    }
	Button{
	id: testBtn1;
        text: qsTr("Hello World");
        anchors.top: testflowers .bottom;
        onClicked: {
                testflowers.onBtnClick();      //控件的点击事件,可以在类文件(.cpp)中实现业务逻辑
            }
    }
    Rectangle{
	id:background;
	width: parent.width;
	height:50;
	color:testflowers .color;        //可以直接使用C++属性值
	anchors.bottom: testBtn1.bottom;
	}
}

具体的C++类的.cpp文件就不详细写了,这里主要表达的是在qml中使用C++类的相关属性和方法。如果想在qml中使用C++类属性,则必须用特殊的宏才能使用。

注册C++ 对象为 QML 的属性
在C++应用程序加载QML对象时,可以直接嵌入一些C++数据来给QML使用,需要用到QQmlContext::setContextProperty()设置QML上下文属性,上下文属性可以是一个简单的类型,也可以是任何自定义的类对象。
以下代码在main函数中注册:

QtQuick2ApplicationViewer viewer;
viewer.rootContext()->setContextProperty("testflowers ", new TestFlowers );  //这里new创建了对象,需要自己手动回收
viewer.setMainQmlFile(QStringLiteral("qml/testflowers /main.qml"));    //"qml/testflowers /main.qml"表示qml文件路径

通过上述方法注册之后。qml的使用有部分改动:

import QtQuick 2.0
// import qt.test.TestFlowers 1.0     已经注册,不需要了
Rectangle {
    width: 200;
    height: 200;
    
// 已经注册成属性了,不要了
/*    TestFlowers {
        id: testflowers ;
        anchors.top: parent.top;
    }*/
    
	Button{
	id: testBtn1;
        text: qsTr("Hello World");
        anchors.top: testflowers .bottom;
        onClicked: {
                testflowers.onBtnClick();      //控件的点击事件,可以在类文件(.cpp)中实现业务逻辑
            }
    }
    Rectangle{
	id:background;
	width: parent.width;
	height:50;
	color:testflowers .color;        //可以直接使用C++属性值
	anchors.bottom: testBtn1.bottom;
	}
}

在 C++ 中使用 QML 对象
在C++中也可以访问QML中的属性、函数和信号。QObject 类的构造函数有一个 parent 参数,可以指定一个对象的父亲, QML 中的对象其实借助这个组成了以根 item 为父的一棵对象树。
而 QObject 定义了一个属性 objectName ,这个对象名字属性,就可以用于查找对象。现在该说到查找对象的方法了: findChild() 和 findChildren() 。它们的函数原型如下:

T QObject::findChild(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
QList<T> QObject::findChildren(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const

用例:

QPushButton *button = parentWidget->findChild<QPushButton *>("button1");
QList<QWidget *> widgets = parentWidget.findChildren<QWidget *>("widgetname");

在TestFlowers 类中查找testBtn1对象调用方法如下:
Button* button1 = this->findChild<Button*>(“testBtn1”);

PS:上述代码的实例没有写全,所以不要直接copy在实机上运行,上述相关方法都可以到QT SDK中找到,可以自己下载一个qt 工具,然后在里边搜索相关方法,里边都有详细介绍且非常方便(参考:QT框架简介里边的搜索方法)。

猜你喜欢

转载自blog.csdn.net/yj_android_develop/article/details/84582168