qtqmlqtquickcontrols2

QML QVariantMap as ComboBox model


I have this class:

//main_model.hpp

#ifndef MAIN_MODEL_H
#define MAIN_MODEL_H

#include <QObject>
#include <QVariantMap>

class MainModel : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QVariantMap map READ map NOTIFY mapChanged)

  public:
    MainModel();

    const QVariantMap& map() const { return m_map; }

  signals:
    void mapChanged();

  private:
    QVariantMap m_map;
};

#endif // MAIN_MODEL_H

And in main_model.cpp:

#include "main_model.hpp"

MainModel::MainModel()
{
  m_map.insert("KEY1", "VALUE1");
  m_map.insert("KEY2", "VALUE2");
  m_map.insert("KEY3", "VALUE3");
  emit mapChanged();
}

I have registered the MainModel class as mainModel through the QML engine, then in main.qml

Window {
    height: 640
    width: 360
    visible: true

    ComboBox {
        height: parent.height * 0.1
        width: parent.width * 0.5
        anchors.centerIn: parent
        model: mainModel.map // This is not working
    }
}

I would like to show in the ComboBox the values of the map -> ["VALUE1","VALUE2","VALUE3"], and when the ComboBox selection changes I would like to log which key was chosen. Is this possible?


Solution

  • ComboBox really needs something a list such as QQmlListProperty, QAbstractListModel (including QML ListModel), or a QList (e.g. QVariantList). Because you're using a QVariantMap it doesn't work because it is not list-like or array-like. QVariantMap behaves like a JavaScript object, so a quick fix would be to convert the JavaScript object to an JavaScript array using one of the following: Object.values(), Object.keys() or Object.entries. For example:

        ComboBox {
             height: parent.height * 0.1
             width: parent.width * 0.5
             anchors.centerIn: parent
             // model: mainModel.map // This is not working
             model: Object.values(mainModel.map)
        }
    

    I know you're instantiating MainModel and assigning it directly to the context property, but, in general, the constructor of a QObject component should also support a QObject* parent property, e.g.

    class MainModel : public QObject
    {
      //...
      public:
        // MainModel();
        MainModel(QObject* parent = nullptr);
      //...
    

    And the implementation, of course, should also do something with that parameter, e.g.

    // MainModel::MainModel()
    MainModel::MainModel(QObject* parent) : QObject(parent)
    {
      m_map.insert("KEY1", "VALUE1");
      m_map.insert("KEY2", "VALUE2");
      m_map.insert("KEY3", "VALUE3");
      emit mapChanged();
    }
    

    Another quick fix would be to replace QVariantMap with QVariantList. In the following example, I also updated the Q_PROPERTY to use the default getter by using MEMBER instead of READ thus reducing the need to implement a custom getter.

    // MainModel.h
    #ifndef MAIN_MODEL_H
    #define MAIN_MODEL_H
    
    #include <QObject>
    #include <QVariantMap>
    
    class MainModel : public QObject
    {
        Q_OBJECT
        // Q_PROPERTY(QVariantMap map READ map NOTIFY mapChanged)
        Q_PROPERTY(QVariantList list MEMBER m_list NOTIFY listChanged)
    
      public:
        MainModel(QObject* parent = nullptr);
    
        // const QVariantMap& map() const { return m_map; }
    
      signals:
        void listChanged();
    
      private:
        // QVariantMap m_map;
        QVariantList m_list;
    };
    
    #endif // MAIN_MODEL_H
    
    // MainModel.cpp
    #include "MainModel.h"
    
    MainModel::MainModel(QObject* parent) : QObject(parent)
    {
        QVariantMap map;
        map["key"] = "KEY1";
        map["value"] = "VALUE1";
        m_list.append(map);
        map["key"] = "KEY2";
        map["value"] = "VALUE2";
        m_list.append(map);
        map["key"] = "KEY3";
        map["value"] = "VALUE3";
        m_list.append(map);
        emit listChanged();
    }
    
    // main.qml
    import QtQuick 2.15
    import QtQuick.Controls 2.15
    
    ApplicationWindow {
        width: 640
        height: 480
        visible: true
        title: qsTr("Hello World")
        ComboBox {
             height: parent.height * 0.1
             width: parent.width * 0.5
             anchors.centerIn: parent
             model: mainModel.list
             textRole: "key"
             valueRole: "value"
        }
    }