Passing Enum Properties between C++ and QML

We have defined a Qt property warningLevel in the C++ class MainModel:

    Q_PROPERTY(WarningLevel::Enum warningLevel READ warningLevel
               WRITE setWarningLevel NOTIFY warningLevelChanged)

We want to use this property in QML. For example, we want to colour a rectangle according to the warningLevel:

    import com.embeddeduse.models 1.0
    // ...

    property MainModel mainModel : MainModel {}

    Rectangle {
        color: toColor(mainModel.warningLevel)
        // ...
    }

    function toColor(level) {
        switch (level) {
        case WarningLevel.Error:
            return "red"
        case WarningLevel.Warning:
            return "orange"
        case WarningLevel.Info:
            return "green"
        case WarningLevel.Debug:
            return "purple"
        default:
            return "magenta"
        }
    }

Note how we access the C++ property mainModel.warningLevel from QML to set the color of the rectangle and how we use symbolic enum constants like WarningLevel.Info in the function toColor().

It is similarly easy to use a list of the symbolic enum constants as the model of a Repeater and to assign the warning level by the user to the property mainModel.warningLevel in the onReleased handler of a MouseArea.

    Repeater {
        model: [WarningLevel.Error, WarningLevel.Warning, WarningLevel.Info,
            WarningLevel.Debug]
        Rectangle {
            color: toColor(modelData)
            // ...
            MouseArea {
                anchors.fill: parent
                onReleased: mainModel.warningLevel = modelData
            }
        }
    }

I’ll show you in the rest of this post how to write your C++ code so that you can use a C++ property of enum type easily in QML.

Our enum represents the four levels of warnings that we want to show in an application. The QML code toColor() maps each warning level to a colour. A red message is an error, an orange message a warning and so on. Here is the plain enum, which I declared in a separate file qmlenums.h.

    enum class Enum : quint8 { Error, Warning, Info, Debug };

We must register the enum with Qt’s meta object system. This allows us to use the Enum as a custom-type of a QVariant, to pass it between QML and Qt, to use it in synchronous signal-slot connections, and to print the symbolic enums (instead of a magic number) with qDebug(). Our first stop for registrations always is the macro Q_DECLARE_METATYPE. The documenation of this macro gives us a good hint.

Some types are registered automatically and do not need this macro:

  • Enumerations registered with Q_ENUM or Q_FLAG

The documentation of Q_ENUM gives us the necessary details.

This macro registers an enum type with the meta-object system. It must be placed after the enum declaration in a class that has the Q_OBJECT or the Q_GADGET macro.

This tells us to do two things:

  • Place the macro Q_ENUM(Enum) after the enum declaration.
  • Create a class WarningLevel derived from QObject containig the Q_OBJECT macro.

The code for the enum looks as follows:

class WarningLevel : public QObject {
    Q_OBJECT
public:
    enum class Enum : quint8 { Error, Warning, Info, Debug };
    Q_ENUM(Enum)
};

The above class is the template for other enums that we want to expose to QML. If, for example, we wanted to use an enum OperatingMode in QML, we would write another small class. We could access the enum constants in QML as OperatingMode.Field. If we added the enum OperatingMode to the existing class WarningLevel, we would have to access the enum constants in QML as WarningLevel.Field. This is most likely not what we want.

There are two more steps to make the enum known in QML. First, we must register the class WarningLevel with the QML engine. This is done with the following command in the file main.cpp.

    qmlRegisterUncreatableType<WarningLevel>("com.embeddeduse.models", 1, 0, "WarningLevel",
                                             "Cannot create WarningLevel in QML");

This command registers the C++ class WarningLevel under the QML name WarningLevel in version “1.0” of the QML module “com.embeddeduse.models”. We use the function qmlRegisterUncreatableType instead of qmlRegisterType, because we don’t need to call the constructor of WarningLevel in QML and because the documentation says so.

This is useful where the type is only intended for providing attached properties or enum values.

When we want to access the enum constants WarningLevel in a QML file, we must add the following import command at the beginning of the QML file. This is the second step.

import com.embeddeduse.models 1.0

You can find the complete source code on github.

3 thoughts on “Passing Enum Properties between C++ and QML

  1. I don’t think nesting enums in classes just for the sake of being able to export them to QML is not a good idea.

    The main problem I have with this approach is that you can’t forward declare the enum anymore, which can have a huge impact on creating header dependencies if you do this for a large number of enums.

    Luckily, Qt has got us covered: Just define a namespace instead of a class and use the `Q_ENUM_NS` macro instead of `Q_ENUM`. Then you can still forward declare the enum, and can use it from QML. Win-Win.

    • This sounds like a good idea. Hence I tried it – but got stuck 🙁 Here is what I did.

      I replaced the class WarningLevel by a namespace in qmlenums.h

      namespace WarningLevel {
      Q_NAMESPACE
      enum class Enum : quint8 { Error, Warning, Info, Debug };
      Q_ENUM_NS(Enum)
      };
      

      In main.cpp, I registered the namespace WarningLevel and the data type WarningLevel::Enum. The second registration is needed to make the type of the Q_PROPERTY warningLevel known to QML.

          qmlRegisterUncreatableMetaObject(WarningLevel::staticMetaObject,
                                           "com.embeddeduse.models", 1, 0, "WarningLevel",
                                           "Cannot create namespace WarningLevel in QML");
          qRegisterMetaType<WarningLevel::Enum>("WarningLevelEnum");
      

      Setting the colour in the upper half doesn’t work:

          color: toColor(mainModel.warningLevel)
      

      When I print mainModel.warningLevel, I get “Warning”.

      Setting the colour for the four buttons in the lower half works.

              Repeater {
                  model: [WarningLevel.Error, WarningLevel.Warning, WarningLevel.Info,
                      WarningLevel.Debug]
                  Rectangle {
                      color: toColor(modelData)
      

      When I print modelData, I get a number: 0, 1, 2 or 3.

      The class solution interprets the enum constants as numbers in both calls of toColor() (upper and lower half). Hence, it works.

      How could we fix the namespace solution?

      • The reason this doesn’t work as is is that the type of `level` in the `toColor` function is `object` when called with `mainModel.warningLevel` as an argument, while the type of the enum constants is `number`.

        I consider this a Qt bug, but at least the workaround is pretty simple: Replace `switch(level)` by `switch(Number(level))` to force the conversion from `object` to `number`

Comments are closed.