Qt Protobuf QML 类型

使用生成器插件,您可以在 QML 中注册 protobuf 消息。要注册类型,请使用 QMLQML_URI 生成密钥。在 qt_add_protobuf 命令和 API 用例 QML 扩展 protobuf 中查看 API 详细信息。

注册的 protobuf 消息在 QML 中可用,就像内置的 Q_GADGET 类型一样。注册是通过 QML 模块完成的。

在 QML 中使用 protobuf 消息

使用生成器插件生成 protobuf 消息库,您可以从 Qt Quick 应用程序中访问它。Qt Protobuf CMake API 有相应的选项来控制 QML 模块创建。

例如,您有包含 User 消息的 userdb.proto protobuf 架构

syntax = "proto3";

package userdb;

message User {
    enum Type {
        Admin = 0;
        Manager = 1;
        Account = 2;
        Director = 3;
    }
    Type type = 1;
    string name = 2;
    string email = 3;
}

要在外部 QML 中排除 User 消息,请使用 protobuf 架构和 qt_add_protobuf 命令,带有 QML 参数

qt_add_executable(appuserdb
    ...
)

qt_add_qml_module(appuserdb
    URI userdb
    VERSION 1.0
    QML_FILES
        ...
    SOURCES
        ...
)

qt_add_protobuf(userdb_gen
    QML
    QML_URI "userdb.pb"
    PROTO_FILES
        userdb.proto
)

target_link_libraries(appuserdb PRIVATE userdb_gen)

qt_add_protobuf 函数将生成一个名为 userdb_gen 的库,其中包含从 userdb.proto 中生成的 protobuf 消息(具有 QML 支持)。要在 QML 中使用这些消息,使用 qt_add_protobuf 调用的 QML_URI 参数中指定的 URI 引入生成的 QML 模块

import userdb.pb

所有 protobuf 消息都注册为 QML 值类型。要在 QML 中使用它们,为某个 QML 项目定义 属性属性

Window {
    id: userAddForm
    property user newUser
    ...
}

要更改 newUser 属性的 typenameemail 字段,请使用 QML 信号回调。例如

TextField {
    id: userNameField
    onTextChanged: {
        userAddForm.newUser.name = userNameField.text
    }
}
...
TextField {
    id: userEmailField
    onTextChanged: {
        userAddForm.newUser.email = userEmailField.text
    }
}

User.Type 枚举值也可以从 QML 访问。以下示例演示如何使用枚举值创建 ComboBox 项

ComboBox {
    id: userTypeField
    textRole: "key"
    model: ListModel {
        id: userTypeModel
        ListElement { key: "Admin"; value: User.Admin }
        ListElement { key: "Second"; value: User.Manager }
        ListElement { key: "Account"; value: User.Account }
        ListElement { key: "Director"; value: User.Director }
    }
    onActivated: function(index) {
        userAddForm.newUser.type = userTypeModel.get(index).value
    }
}

集成 QML 和 C++

在 QML 中注册的 C++ 类可以在属性和可调用的方法中使用在 QML 中创建的消息。

单例 QML 对象 UserDBEnginelastAddedUser 属性和可调用的 addUser 方法公开给 QML

class UserDBEngine : public QObject
{
    Q_OBJECT
    QML_ELEMENT
    QML_SINGLETON

    Q_PROPERTY(userdb::User lastAddedUser READ lastAddedUser WRITE setLastAddedUser NOTIFY lastAddedUserChanged FINAL)
public:
    ...
    Q_INVOKABLE void addUser(const userdb::User &newUser);
    ...
}

lastAddedUser 属性具有从先前部分中生成的 userdb::User 类型(来自 userdb.proto 架构)。可调用的 addUser 方法接受对 userdb::User 类型对象的常数引用。属性和方法都可以从 QML 使用。

Button {
    text: "Add"
    onClicked: {
        // Use the property created in the previous section
        UserDBEngine.addUser(userAddForm.newUser)
    }
}
...
Text {
    // The text will be updated automatically when lastAddedUser is changed
    text: "Last added user: " + UserDBEngine.lastAddedUser.name
}

Protobuf 消息重复

您应在您的 *.proto 文件中避免声明protobuf消息重复,或者智慧地处理它们。如果您的应用程序在使用不同protobuf包中声明了多个相同的protobuf消息名称,它们可能在自动生成的代码中出现冲突。以下示例中,两个不同的proto包 qtprotobufnamespaceqtprotobufnamespace1.nested 使用了相同的proto消息 NestedFieldMessage。文件 nested.proto 中包含了这种情况。

syntax = "proto3";

package qtprotobufnamespace;
import "externalpackage.proto";

message NestedFieldMessage {
    sint32 testFieldInt = 1;
}

文件 nestedspace1.proto

syntax = "proto3";

package qtprotobufnamespace1.nested;

message NestedFieldMessage {
    message NestedMessage {
        sint32 field = 1;
    }
    NestedMessage nested = 1;
}

如果无法避免包之间的名称重复,那么请将重复的消息放在不同的QML模块中,并为每个QML模块的导入使用一个 <Qualifier>,请参阅 模块(命名空间)导入。以下是如何将protobuf包添加到不同QML模块中的示例。

# qtprotobufnamespace QML module
qt_add_protobuf(nestedtypes_qtprotobuf_qml
    PROTO_FILES
        nested.proto
    QML
    QML_URI
        qtprotobufnamespace
    OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/qt_protobuf_gen1"
)

...

# qtprotobufnamespace1.nested QML module
qt_add_protobuf(nestedspace_qml
    PROTO_FILES
        nestedspace1.proto
    QML
    QML_URI
        qtprotobufnamespace1.nested
    OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/qt_protobuf_gen2"
)

<Qualifier> 的使用示例

import qtprotobufnamespace as NestedFieldMessages
import qtprotobufnamespace1.nested as FieldMessages_Nested1

...

property NestedFieldMessages.nestedFieldMessage fieldMsg1;
property FieldMessages_Nested1.nestedFieldMessage fieldMsg2;

注意:在编译时间使用重复将触发一个警告。

QML关键字处理

请注意,在QML或JavaScript上下文中保留但不在 *.proto 上下文中保留的关键字。具有QML保留名称的字段将被 _proto 后缀静默扩展,由 生成插件 完成。例如,idpropertyimport 是保留关键字。它们将被替换为 id_protoproperty_protoimport_proto

message MessageUpperCaseReserved {
    sint32 Import = 1;
    sint32 Property = 2;
    sint32 Id = 3;
}

生成代码输出

Q_PROPERTY(QtProtobuf::sint32 import_proto READ import_proto ...)
Q_PROPERTY(QtProtobuf::sint32 property_proto READ property_proto ...)
Q_PROPERTY(QtProtobuf::sint32 id_proto READ id_proto ...)

此外,枚举值不能以下划线字母开头。生成插件将代码输出中的第一个字母转换为大写。以下是一个 *.proto 示例

enum LowerCaseEnum {
    enumValue0 = 0;
    enumValue1 = 1;
    enumValue2 = 2;
}

生成代码输出

enum LowerCaseEnum {
    EnumValue0 = 0,
    EnumValue1 = 1,
    EnumValue2 = 2,
};
Q_ENUM(LowerCaseEnum)

此外,枚举字段不能以下划线符号开头。此类字段将被原样生成,但在QML中未经定义,除非QML引擎将来允许注册它们。以下是一个 *.proto 示例

enum UnderScoreEnum {
    _enumUnderscoreValue0 = 0;
    _EnumUnderscoreValue1 = 1;
}

生成输出

enum UnderScoreEnum {
    _enumUnderscoreValue0 = 0,
    _EnumUnderscoreValue1 = 1,
};
Q_ENUM(UnderScoreEnum)

有关QML属性语法的更多信息,请查看 定义属性属性

© 2024 Qt公司有限公司。包含在此处的文档贡献的版权属于其所有者。此处提供的文档是根据自由软件基金会发布的 GNU自由文档许可版本1.3 的条款许可的。Qt及其相关徽标是芬兰的Qt公司及其全球子公司和分公司的商标。所有其他商标均为其所有者的财产。