QML QVariantMap as ComboBox model

147 Views Asked by At

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?

2

There are 2 best solutions below

0
Stephen Quan On BEST ANSWER

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"
    }
}
0
Jürgen Lutz On

As an addition to the great answere of @StephenQuan here the answere to your question, how to log the selection:

You can use the property change signals onCurrentIndexChanged, onCurrentTextChanged and onCurrentValueChanged

Here a small example:

ComboBox {
    textRole: "text"
    valueRole: "value"

    model: [
        { value: 1, text: "Key 1" },
        { value: 2, text: "Key 2" },
        { value: 3, text: "Key 3" }
    ]

    onCurrentIndexChanged: {
        console.log("index: " + currentIndex);
    }

    onCurrentTextChanged: {
        console.log("text:" + currentText);
    }

    onCurrentValueChanged: {
        console.log("value:" + currentValue);
    }

}