Qt Protobuf QML 类型

使用生成器插件,您可以在 QML 中注册 protobuf 消息。要注册类型,请使用QMLQML_URI 生成键。请参阅qt_add_protobuf命令和QML 扩展 protobuf API 使用示例中的 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 模式和带有QML 参数的qt_add_protobuf命令:

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 并支持 QML 的 protobuf 消息。要在 QML 中使用这些信息,请使用qt_add_protobuf 调用的QML_URI 参数中指定的 URI 导入生成的 QML 模块:

import userdb.pb

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

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

要改变newUser 属性的type,name, 或email 字段,请使用 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 对象UserDBEngine 向 QML 公开了lastAddedUser 属性和可调用方法addUser

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 中保留的 protobuf 消息作为 QML 类型,则无法保证正确的行为,并会触发Element is not creatable 错误。为防止 QML 类型重叠,请在 QML 模块导入时使用 <Qualifier>,请参阅模块(命名空间)导入。例如,下面的 protobuf 消息导入全局命名空间后,会与TextItem QML 类型冲突:

syntax = "proto3";
package test.example;

message Text {
    string text = 1;
}

message Item {
    sint32 width = 1;
    sint32 height = 2;
}

使用带有QML 选项的qt_add_protobuf宏,从上述 protobuf 消息中生成 QML 类型。请参阅下面的示例:

qt_add_protobuf(example
    PROTO_FILES
        test.proto
    QML
    QML_URI
        test.example
)

如果在QtQuick 导入后将 protobuf 信息导入全局命名空间,则会触发与 QML 类型的冲突。请看下面的示例:

 import QtQuick
 import test.example

 Item {
    id: root
    ...
    property ProtobufMessages.item itemData
}

为避免与 QML 类型冲突,请使用<Qualifier> 将生成的 QML 模块导入本地命名空间。请参阅下面的示例:

// No qualifier - global namespace
import QtQuick
// ProtobufMessages - a qualifier of local namespace.
import test.example as ProtobufMessages

Item {
    id: root
    ...
    property ProtobufMessages.item itemData
}

QML 关键字处理

注意在 QML 或 JavaScript 上下文中保留,但在 *.proto 上下文中未保留的关键字。带有 QML 保留的名称的字段将由生成器插件通过_proto 后缀静默扩展。例如,idpropertyimport 是保留关键字。它们将被替换为id_proto,property_proto,import_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 属性语法的更多信息,请查看Defining Property Attributes(定义属性属性)。

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.