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类型
boolbool
integral (char, short, int, long, ...)int
floating point (float, double)real
std::string (假设UTF-8编码)string
T* where T derives from Qul::Objectmatching component type
enum in exported classmatching 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 之后会发出 pressedreleased 信号。

现在,在 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 许可下提供。
了解更多。