Qt Protobuf QML Types | Qt Protobuf | Qt 6.12.0
Description: A collection of examples for Qt Protobuf

- Qt 6.12
- Qt Protobuf
- Qt Protobuf QML Types
- Qt 6.12.0 Reference Documentation

# Qt Protobuf QML Types

With the generator plugin, you can register protobuf messages in the QML. To register the type, use the `QML` and `QML_URI` generation keys. See API details in qt_add_protobuf command and API usage example QML extended protobuf.

Registered protobuf messages are available in the QML, like built-in Q_GADGET types. The registration is done via the QML module.

## Using protobuf messages in QML

Use the generator plugin to generate libraries of protobuf messages that you can access from Qt Quick applications. The Qt Protobuf CMake API has respective options that control the QML module creation.

For example, you have the `userdb.proto` protobuf schema that contains the `User` message:

     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;
     }

To expose the `User` message in QML, use the protobuf schema and the qt_add_protobuf command with the `QML` argument:

     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)

The qt_add_protobuf function will generate a library called `userdb_gen`, containing the protobuf messages from `userdb.proto` with QML support. To use the messages in QML, import the generated QML module using the URI specified in the `QML_URI` argument of the `qt_add_protobuf` call:

     import userdb.pb

All protobuf messages are registered as QML Value Types. To use them in QML, define the property attribute for some QML item:

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

To change the `type`, `name`, or `email` fields of the `newUser` property, use QML signal callbacks. For example:

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

The `User.Type` enum values are also accessible from QML. The below example shows how to create a ComboBox item using the enum values:

     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
         }
     }

## Integrating QML and C++

C++ classes registered in QML may use the messages created in QML in both properties and invocable methods.

The singleton QML object `UserDBEngine` exposes the `lastAddedUser` property and the invocable method `addUser` to 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);
         ...
     }

The `lastAddedUser` property has the `userdb::User` type generated from the `userdb.proto` schema from the previous section. The invocable `addUser` method accepts constant reference to an object of `userdb::User` type. Both property and method can be used from 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 message duplicates

You should avoid declaration of protobuf message duplicates in your `*.proto` files or do it wisely. If your application uses several identical protobuf message names declared inside different protobuf packages, they might contradict each other in auto-generated code. In the example below, two different proto packages, `qtprotobufnamespace` and `qtprotobufnamespace1.nested`, use the same proto message `NestedFieldMessage`. The file `nested.proto`:

     syntax = "proto3";

     package qtprotobufnamespace;
     import "externalpackage.proto";

     message NestedFieldMessage {
         sint32 testFieldInt = 1;
     }

The file `nestedspace1.proto`:

     syntax = "proto3";

     package qtprotobufnamespace1.nested;

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

In case there is no possibility to avoid name duplicates among packages, then put duplicated messages in different QML modules and use a <Qualifier> for each QML module import, see Module (Namespace) Imports. Below the example how to add protobuf packages into different QML modules:

     # 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"
     )

The <Qualifier> usage example:

     import qtprotobufnamespace as NestedFieldMessages
     import qtprotobufnamespace1.nested as FieldMessages_Nested1

     ...

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

Note: The usage of duplicates will trigger a warning at compilation time.

## QML types duplicates

If your application uses protobuf messages with names that are already reserved in QML as QML types, then the correct behavior cannot be guaranteed, and `Element is not creatable` error would be triggered. To prevent an overlapping of QML Types, use a <Qualifier> for QML module import, see Module (Namespace) Imports. For example, the following protobuf messages would be clashing with the Text and Item QML types when imported into the global namespace:

     syntax = "proto3";
     package test.example;

     message Text {
         string text = 1;
     }

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

Use the qt_add_protobuf macro with the `QML` option to enable QML types generation from the above protobuf messages. See the example below:

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

Clashes with the QML types will be triggered if protobuf messages are imported into a global namespace after the QtQuick import. See the example below:

      import QtQuick
      import test.example

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

To avoid clashes with the QML types, use a `<Qualifier>` to import the generated QML module into a local namespace. See the example below:

     // 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 keywords handling

Pay attention to the keywords that are reserved in QML or JavaScript context, but not reserved in *.proto context. Fields with names that are reserved by QML will be silently extended by `_proto` suffix by the generator plugin. For example, `id`, `property`, and `import` are reserved keywords. They will be replaced by `id_proto`, `property_proto`, `import_proto`:

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

Generated code output:

     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 ...)

Also, enum values cannot begin with a lower case letter. The generator plugin will capitalize the first letter in code output. See the `*.proto` example below:

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

Generated code output:

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

Also, enum fields cannot begin with an underscore symbol. Such fields will be generated as is, but will be undefined in the QML, unless the QML engine will allow registering them in the future. See the `*.proto` example below:

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

Generated output:

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

For more information about the QML properties syntax, check Defining Property Attributes.