本页内容

Qt Protobuf QML 类型

借助生成器插件,您可以在 QML 中注册 Protobuf 消息。要注册类型,请使用生成键QML 和QML_URI 。请参阅qt_add_protobuf()命令中的 API 详细信息以及 API 使用示例“QML 扩展的 Protobuf”。

已注册的 Protobuf 消息可在 QML 中使用,如同内置的Q_GADGET 类型一样。注册操作通过 QML 模块完成。

在 QML 中使用 Protobuf 消息

使用生成器插件生成 Protobuf 消息库,您可以在Qt Quick 应用程序中访问这些库。Qt Protobuf CMake API提供了相应的选项来控制 QML 模块的创建。

例如,您有一个userdb.proto Protobuf 模式,其中包含User 消息:

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 项定义property 属性:

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消息,它们可能会在自动生成的代码中相互冲突。在下面的示例中,两个不同的Protobuf包qtprotobufnamespace 和qtprotobufnamespace1.nested 使用了相同的Protobuf消息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 类型重复

如果您的应用程序使用的 protobuf 消息名称在 QML 中已被预留为 QML 类型,则无法保证正确行为,并将触发Element is not creatable 错误。为防止 QML 类型重叠,请在 QML 模块导入时使用 <Qualifier>,详见“模块(命名空间)导入”。 例如,以下 Protobuf 消息在导入全局命名空间时,将与 QML 类型Text 和Item 发生冲突:

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 后缀。例如,id 、property 和import 是保留关键词。它们将被替换为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 中会被定义为未定义(undefined),除非 QML 引擎将来允许对其进行注册。请参阅下面的*.proto 示例:

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

生成的输出:

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

有关 QML 属性语法的更多信息,请参阅《定义属性》。

© 2026 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.