Search code examples
comboboxqmlqt6

How to make a Q_ENUM from C++ work as options for a ComboBox in QML


I am using Qt6.5.1 and I have created ComboBox in QML. Following is what I have done.

I have following C++ class.

class MyComboBoxOptions : public QObject {
    Q_OBJECT
    Q_PROPERTY(QStringList options READ options)
public:     

    QStringList options() const {
        return mComboOptionStrings;
    }

private:
    const QStringList mComboOptionStrings {
        "Option 1",
        "Option 2",
        "Option 3",
    };
};

In main.cpp I have done the following to register the above class MyComboBoxOptions in QML.

MyComboBoxOptions myComboBoxOptions;
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myComboBoxOptions", &myComboBoxOptions);

Then I have the ComboBox like below in QML.

ComboBox {
    id: comboBox
    height: 20
    width: 50

    model: myComboBoxOptions.options
    currentIndex: 0 // default
}

Question:
How can replace the QStringList returned by MyComboBoxOptions::options with a Q_ENUM? Simply returning a Q_ENUM there does not work.


Solution

  • [REWRITE]

    I have a general-purpose EnumInfo class that can build a model for an enum from any object, including Qt's objects. In the following example, I am accessing PetUtils's Pet enum and Qt's Image's FillMode enum:

    // PetUtils.h
    #ifndef PETUTILS_H
    #define PETUTILS_H
    #include <QObject>
    #include <QQmlEngine>
    class PetUtils : public QObject
    {
        Q_OBJECT
        QML_ELEMENT
    public:
        explicit PetUtils(QObject *parent = nullptr) : QObject(parent) { }
        enum Pet
        {
            Cat,
            Dog,
            Parrot
        };
        Q_ENUMS(Pet)
    };
    #endif // PETUTILS_H
    
    // main.qml
    import QtQuick
    import QtQuick.Controls
    import QtQuick.Window
    import QtQuick.Layouts
    import QtEnumApp
    
    Window {
        width: 640
        height: 480
        visible: true
        title: qsTr("Hello World")
        ColumnLayout {
            Label { text: "Choose a Pet type" }
            // "Cat", "Dog", "Parrot"
            ComboBox {
                model: petEnum.keyValues
                textRole: "key"
                valueRole: "value"
            }
            Label { text: "Choose an Image FillMode" }
            // "Stretch", "PreserveAspectFit", "PreserveAspectCrop", "Tile", "TileVertically", "TileHorizontally", "Pad"
            ComboBox {
                model: fillModeEnum.keyValues
                textRole: "key"
                valueRole: "value"
            }
            Image {
                id: image
            }
        }
        PetUtils { id: petUtils }
        EnumInfo { id: petEnum; obj: petUtils; enumName: "Pet" }
        EnumInfo { id: fillModeEnum; obj: image; enumName: "FillMode" }
    }
    

    The way this works is that the EnumInfo uses the metaObject to look up any enum from any object you give it and it can extract the enum keys (i.e. strings) and enum values (i.e. integer values).

    1. ptrQObject->metaObject() // QMetaObject for any QObject
    2. metaObject()->indexOfEnumerator(name) // finds an enum index
    3. metaObject()->enumerator(index) // QMetaEnum from QMetaObject
    4. walk the key/values from QMetaEnum
    // EnumInfo.h
    #ifndef ENUMINFO_H
    #define ENUMINFO_H
    #include <QObject>
    #include <QQmlEngine>
    class EnumInfo : public QObject
    {
        Q_OBJECT
        QML_ELEMENT
        Q_PROPERTY(QVariant obj MEMBER m_var NOTIFY enumChanged)
        Q_PROPERTY(QString enumName MEMBER m_enumName NOTIFY enumChanged)
        Q_PROPERTY(QVariant keyValues READ keyValues NOTIFY enumChanged)
        Q_PROPERTY(QStringList keys READ keys NOTIFY enumChanged)
        Q_PROPERTY(QVariant values READ values NOTIFY enumChanged)
    public:
        explicit EnumInfo(QObject *parent = nullptr) : QObject(parent) { }
        Q_INVOKABLE int keyToValue(const QString& key)
        {
            QMetaEnum metaEnum = getMetaEnum();
            if (!metaEnum.isValid()) return -1;
            return metaEnum.keyToValue(key.toUtf8());
        }
        Q_INVOKABLE QString valueToKey(int value)
        {
            QMetaEnum metaEnum = getMetaEnum();
            if (!metaEnum.isValid()) return QString();
            return QString::fromUtf8(metaEnum.valueToKey(value));
        }
    protected:
        QVariant keyValues() const
        {
            QMetaEnum metaEnum = getMetaEnum();
            if (!metaEnum.isValid()) return QVariant();
            QVariantList list;
            for (int i = 0; i < metaEnum.keyCount(); i++)
            {
                QVariantMap map;
                map["key"] = QString::fromUtf8(metaEnum.key(i));
                map["value"] = metaEnum.value(i);
                list.append(map);
            }
            return list;
        }
        QStringList keys() const
        {
            QMetaEnum metaEnum = getMetaEnum();
            if (!metaEnum.isValid()) return QStringList();
            QStringList list;
            for (int i = 0; i < metaEnum.keyCount(); i++)
                list.append(QString::fromUtf8(metaEnum.key(i)));
            return list;
        }
        QVariant values() const
        {
            QMetaEnum metaEnum = getMetaEnum();
            if (!metaEnum.isValid()) return QVariant();
            QVariantList list;
            for (int i = 0; i < metaEnum.keyCount(); i++)
                list.append(metaEnum.value(i));
            return list;
        }
        QMetaEnum getMetaEnum() const
        {
            if (!m_var.isValid() || m_var.isNull()) return QMetaEnum();
            QObject* obj = qvariant_cast<QObject*>(m_var);
            if (!obj) return QMetaEnum();
            int index = obj->metaObject()->indexOfEnumerator(m_enumName.toUtf8());
            if (index == -1) return QMetaEnum();
            return obj->metaObject()->enumerator(index);
        }
    signals:
        void enumChanged();
    private:
        QVariant m_var;
        QString m_enumName;
    };
    #endif // ENUMINFO_H
    
    # CMakeLists.txt
    qt_add_qml_module(appQtEnumApp
        URI QtEnumApp
        VERSION 1.0
        QML_FILES Main.qml
        SOURCES EnumInfo.h PetUtils.h
    )