C
将C++代码与QML集成
概述
Qt Quick Ultralite 应用使用C++代码进行业务逻辑,与硬件接口,以及与其他软件模块协同工作。应用程序的C++部分与QML表示层进行交互。因此,C++类以及其函数、属性和信号都是暴露给QML代码的。
接口头文件
应用可以包含描述要暴露给QML的组件的接口头文件。这些是正常C++头文件,在QmlProject中使用InterfaceFiles.files属性注册。
使用宏注册头文件时,对构建有两个影响
- 将运行
qmlinterfacegenerator
工具,在头文件中生成一个或多个QML接口文件 - 生成的文件将在转换项目QML代码时提供给
qmltocpp
这些步骤可以使用QML代码利用C++接口。
导出C++ API
组件
类和结构声明是暴露C++功能到QML的构建块。每个导出的C++类声明定义了一个同名QML组件。
以下示例演示了如何使用组件
#include <qul/object.h> #include <qul/property.h> #include <qul/signal.h> struct BigState : public Qul::Object { Qul::Property<int> bigness; int feed(int amount); Qul::Signal<void()> gotFood; };
Item { BigState { id: bigState bigness: 61 onGotFood: bigness = 99 } Component.onCompleted: bigState.feed(3) }
为了被qmlinterfacegenerator捕获,声明类
- 必须至少间接继承自Qul::Object
- 必须是默认可构造的
单例
如果类直接从Qul::Singleton<Itself>派生,则具有pragma Singleton标记,并且全局范围内可访问实例。
以下示例演示了如何使用单例
#include <qul/singleton.h> struct MyApi : public Qul::Singleton<MyApi> { void start(); };
Item { Component.onCompleted: MyApi.start() }
模型
类可以直接从Qul::ListModel<T>继承以导出列表模型。有关详细信息,请参阅Qul::ListModel文档。
函数
导出类声明中的所有公共非重载成员函数在QML中可用。函数的参数类型和返回类型会被映射到它们对应的QML类型。
以下示例演示了如何使用函数
struct DirLookup : public Qul::Object { Qul::Property<std::string> basePath; int lookup(const std::string &path); }; struct Simulator : public Qul::Singleton<Simulator> { void run(DirLookup *lookup); };
Item { DirLookup { id: look basePath: "/objects" } Component.onCompleted: Simulator.run(look) }
属性
在导出类声明中的公共字段,如果字段类型是Qul::Property<T>,将变为组件属性。这里T描述了属性的C++类型,并且将它映射到对应的QML类型。
注意:每个没有内置比较运算符的类型T都必须提供一个用户定义的operator==。
以此方式定义的属性表现与内置属性类似。特别是,它们可以在QML中分配属性绑定,并可作为其他绑定的数据源。
以下示例演示了如何使用属性。在C++中定义一个属性
struct MyData : public Qul::Object { Qul::Property<int> val; void update(int x) { // can get and set property values from C++ val.setValue(x); } };
然后在QML中使用它
Item { Item { // can bind QML property to exported property x: mydata_x.val color: "red" width: 50 height: 50 } MyData { id: mydata_x val: 100 } MyData { id: mydata_width // can bind exported property val: parent.width } Component.onCompleted: { mydata_x.update(200); console.log(mydata_width.val); } }
注意:调用Qul::Property::setValue不会请求Qt Quick Ultralite引擎更新。如果没有QML动画或定时器运行,则需要事件队列。以下是如何使用事件队列来触发重绘的示例。
struct MySingleton : public Qul::Singleton<MySingleton> { Qul::Property<int> val; // function that is called only from the C++ code void update(int value); }; class MyEventQueue : public Qul::EventQueue<int> { void onEvent(const int &value) override { // set property value in the event handler MySingleton::instance().val.setValue(value); } }; void MySingleton::update(int value) { static MyEventQueue myEventQueue; myEventQueue.postEvent(value); }
分组属性
属性可以按以下方式分组:
struct MyObject : public Qul::Object { struct Grouped { Qul::Property<int> val1; Qul::Property<int> val2; }; Grouped group; };
然后它们可以在QML中如此使用
Item { MyObject { group.val1: 42 group.val2: 43 } }
分组是通过在结构体或类S内部放置属性,并在导出类内部有一个类型为S的字段来实现的。类型S本身不能从Qul::Object继承。只有其类型为Qul::Property的公共字段被暴露为组内的属性。组仅用于属性,不能有信号、枚举或函数。
信号
类型为Qul::Signal<Fn>
的公共字段将在QML组件上转换为信号。
模板参数Fn
必须是一个描述信号参数类型的函数类型。通常,这些类型会被映射到相匹配的QML类型。同样,在Fn
中使用的参数名称将成为QML信号的参数名称。
以下代码演示了如何使用信号
struct MyItem : public Qul::Object { Qul::Signal<void(int sigValue)> triggered; void callTriggered() { triggered(42); } };
Item { MyItem { id: myitem onTriggered: console.log(sigValue); } Component.onCompleted: myitem.callTriggered() }
枚举
在导出类声明中的公共枚举声明将被转换为QML枚举。
以下代码演示了如何使用公共枚举
struct MyStates : public Qul::Singleton<MyStates> { enum State { On, Off, Broken }; };
Item { property MyStates.State state: MyStates.On }
C++到QML类型映射
C++类型 | QML类型 |
---|---|
bool | bool |
integral (char, short, int, long, ...) | int |
floating point (float, double) | real |
std::string (假设UTF-8编码) | string |
T* where T derives from Qul::Object | matching component type |
enum in exported class | matching enum type |
注意:转换为std::string意味着动态内存分配。
从中断处理程序向QML传输数据
以下技术结合了Qt Quick Ultralite中存在的多个API和机制。结果,从应用程序的任何部分(包括中断处理程序)都可以实现直接且高效的数据传输。
此示例介绍了使用QML单例模式暴露数据到C++的数据传输使用技术,但相同的技术在Objects和Models中也可用。
注意: interrupt_handler
示例展示了这里描述的技术。您可以在 examples
目录下找到它。
需要什么?
首先,定义您将要用作数据有效负载的事件类型。
struct HMIInputEvent { enum Type { KeyPress, KeyRelease }; int keyCode; Type type; };
接下来,实现一个 C++ 到 QML 界面(例如 Qul::Singleton)。它可以与 Qul::EventQueue 结合使用,以改变属性值或在接收到事件时发出信号。
示例
#include <qul/singleton.h> #include <qul/eventqueue.h> struct HMIInput : Qul::Singleton<HMIInput>, Qul::EventQueue<HMIInputEvent> { Qul::Signal<void(int key)> pressed; Qul::Signal<void(int key)> released; void onEvent(const HMIInputEvent &inputEvent) override { if (inputEvent.type == HMIInputEvent::KeyPress) { pressed(inputEvent.keyCode); } else if (inputEvent.type == HMIInputEvent::KeyRelease) { released(inputEvent.keyCode); } } };
这创建了一个 Singleton 对象,在接收到 HMIInputEvent
之后会发出 pressed
和 released
信号。
现在,在 QML 中使用 Singleton 对象。
import QtQuick 2.15 Rectangle { Row { spacing: 10 Text { id: operation } Text { id: keyCode } } HMIInput.onPressed: { operation.text = "pressed" keyCode.text = key } HMIInput.onReleased: { operation.text = "released" keyCode.text = key } }
到此为止,该实现已准备好通过向 EventQueue 发送事件来传输数据,例如从中断处理程序中。
示例
static void keyEventInterruptHandler() { static HMI_StateTypeDef keyBuffer; if (BSP_HMI_GetState(&keyBuffer) != HMI_OK) { return; } HMIInput::instance().postEventFromInterrupt( HMIInputEvent{keyBuffer.code, keyBuffer.pressed ? HMIInputEvent::KeyPress : HMIInputEvent::KeyRelease}); }
注意: 裸机平台的默认队列实现只能有一个写入者将数据写入事件队列。有关更多信息,请参阅 Qul::EventQueue 描述。
现在,每次调用 keyEventInterruptHandler()
时,QML 中相应的文本将改变。
在特定的 Qt 许可下提供。
了解更多。