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 fromQObject
containig theQ_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.
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.hIn main.cpp, I registered the namespace
WarningLevel
and the data typeWarningLevel::Enum
. The second registration is needed to make the type of the Q_PROPERTY warningLevel known to QML.Setting the colour in the upper half doesn’t work:
When I print
mainModel.warningLevel
, I get “Warning”.Setting the colour for the four buttons in the lower half works.
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`