How to display a simple QMap in a QTableView in Qt?
Asked Answered
K

2

7

I have a QMap called map. I initialize this map with couple of row of data from my database. Now I send this map to another class which contain GUI classes. In my GUI I have a TableView item. I need to show this map in any order in this TableView.

I have seen couple of examples, but all of them are for one vector which has only one field. And they used another class to form the view. I wondered if anyone has done this before and can help me with that.

Kaseykasha answered 6/5, 2014 at 1:22 Comment(0)
N
13

Wrap the QMap in a subclass of QAbstractTableModel and set it to the view. Following, a basic functional example:

File "mapmodel.h"

#ifndef MAPMODEL_H
#define MAPMODEL_H

#include <QAbstractTableModel>
#include <QMap>

class MapModel : public QAbstractTableModel
{
    Q_OBJECT
public:

    enum MapRoles {
        KeyRole = Qt::UserRole + 1,
        ValueRole
    };

    explicit MapModel(QObject *parent = 0);
    int rowCount(const QModelIndex& parent = QModelIndex()) const;
    int columnCount(const QModelIndex& parent = QModelIndex()) const;
    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
    inline void setMap(QMap<int, QString>* map) { _map = map; }

private:
    QMap<int, QString>* _map;
};

#endif // MAPMODEL_H

File "mapmodel.cpp"

#include "mapmodel.h"

MapModel::MapModel(QObject *parent) :
    QAbstractTableModel(parent)
{
    _map = NULL;
}

int MapModel::rowCount(const QModelIndex& parent) const
{
    if (_map)
        return _map->count();
    return 0;
}

int MapModel::columnCount(const QModelIndex & parent) const
{
    return 2;
}

QVariant MapModel::data(const QModelIndex& index, int role) const
{
    if (!_map)
        return QVariant();
    if (index.row() < 0 ||
        index.row() >= _map->count() ||
        role != Qt::DisplayRole) {
        return QVariant();
    }
    if (index.column() == 0)
        return _map->keys().at(index.row());
    if (index.column() == 1)
        return _map->values().at(index.row());
    return QVariant();
}

Example of use:

// ...
QMap<int, QString> map;
map.insert(1, "value 1");
map.insert(2, "value 2");
map.insert(3, "value 3");

MapModel mapmodel;
mapmodel.setMap(&map);

YourTableView.setModel(&mapmodel);
// ...

It will show a table view populated as follows:

enter image description here

Numbing answered 6/5, 2014 at 3:14 Comment(6)
You'll want to do beginModelReset() and endModelReset() in setMapMidwest
I would not advise doing this, ever. The use of .keys() and .values() makes this really, really inefficient. Both these methods create a new container and then copy all data into this new container. Worse, this new container is a QList, which is by itself not an efficient container for all but a handfull of types. You then do this operation for every call to data(), which is called often. So please, don't. Efficient item models need a random-access container to back them. std::vector is your first choice.Theressathereto
@André, Bernard was looking for a way of displaying a QMap in a QTableView, that's what the answer is about. I agree with you in the time penalty introduced by the use of .keys() and .values() but that's all you have in QMap. QList is not inefficient at all for random access, QList is not analogous to std::list, it's indeed constant time index-based access as std::vector. Note the use of at() instead of [] to guarantee the better performance.Numbing
@Numbing No, it is not all you have. You could just copy out the data into a continious container. You could also use an iterator into the QMap and advance that to the right row (and perhaps optimizing that to not always start from begin() but start from either the last used, begin() or end() depending on which is closest or something like that). Not ideal, but much better than using .keys() and .values(). And QList is for most types a continious container of pointers_to heap allocated values. That is not efficient at all. It is rightfully being phased out for Qt6.Theressathereto
LOL @André If someone asks how to fold a paper plane you tell them how to fold a paper plane. Going to the extreme of telling them that it would be better to make it out of other materials such as steel etc so that you can fit people inside is a bit overkill.So I still believe that mhcuervo provided a pretty concise answer to the question that was asked.Remember this is a community for sharing and helping not for making other people do things the way you want them done.Sometimes simple is better i.e. you dont need any design patterns to make a hello world application.Be a dev share dont forceDetradetract
What about using QMap key to corresponds to row index ? This way, you don't have to 'loop' in a way or another in your underlying data. Plus that the offset computation rely of QMap property to keep item sorted. With a QHash, it won't work anymore. But I am also wondering if there is a way to keep 'index row' information in model when inserting the item, with custom role?Cis
B
4

As @André said the solution relying on QMap keys() and values() methods is not efficient at all and shouldn't be used. Instead you should use iterators!

so using the same .h

#ifndef MAPMODEL_H
#define MAPMODEL_H

#include <QAbstractTableModel>
#include <QMap>

class MapModel : public QAbstractTableModel
{
    Q_OBJECT
public:

    enum MapRoles {
        KeyRole = Qt::UserRole + 1,
        ValueRole
    };

    explicit MapModel(QObject *parent = 0);
    int rowCount(const QModelIndex& parent = QModelIndex()) const;
    int columnCount(const QModelIndex& parent = QModelIndex()) const;
    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
    void setMap(QMap<int, QString>* map);

private:
    QMap<int, QString>* _map;
};

#endif // MAPMODEL_H

the cpp would be something like this:

#include "mapmodel.h"

MapModel::MapModel(QObject *parent) :
    QAbstractTableModel(parent), _map(nullptr)
{}

void MapModel::setMap(QMap<int, QString>* map)
{
    beginModelReset();
    _map = map;
    endModelReset();
}

int MapModel::rowCount(const QModelIndex& parent) const
{
    if (_map)
        return _map->count();
    return 0;
}

int MapModel::columnCount(const QModelIndex & parent) const
{
    return 2;
}

QVariant MapModel::data(const QModelIndex& index, int role) const
{
    if (!_map || !index.isValid() || index.row() >= _map->count() || role != Qt::DisplayRole)
        return QVariant();

    auto it = _map.cbegin();
    it += index.row();

    if (index.column() == 0)
        return it.key();
    if (index.column() == 1)
        return it.value();

    return QVariant();
}

The main change is in MapModel::data where you use an iterator and as @Claudiu commented, you must use beginModelReset and endModelReset in MapModel::setMap. (to allow you to be able to change the map on your model and signal your Views)

If you want some examples, I've done it for QAbstractListModel on FamilyModel2. Here is the header, here the cpp. It is using a wrapper for the Map that you can find here. Check the constructor FamilyModel2::FamilyModel2 where external signals are connected in order to manage insertion of entries and also emit dataChanged when your data is updated ;)

Benelux answered 21/5, 2021 at 11:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.