Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Storing persistent information about items in view

Tags:

c++

view

model

qt

qt5

I've my own view based directly on QAbstractItemView. Generally speaking I need to store some information about particular model's item.

So in my view I have a map from QModelIndex to struct describing partical item. Then I use those data mostly on view's paintEvent.

The problem is, that QModelIndex is not persistent, it may get outdated. So when rows are inserted or removed from model, some QModelIndex may become invalid and I should not relay on them.

How then can I build relation between item in model and some decoration data I use in view?

QPersistentModelIndex seems to be proper tool for such things, however I'm aware of its performance (my model and view may be huge).

Another problem with QPersistentModelIndex is that it probably should not be used as map's key (as it is in my case) as it may (and will) change and make map inconsistent.

I've took a look at Qt's implementation of QTreeView and QListView to see how they deal rows removal/insertion, but it seems they simply drop all the data.

So at this point I cannot see any easy way to solve my problem.

like image 422
Michał Walenciak Avatar asked Dec 21 '14 08:12

Michał Walenciak


People also ask

How do you store data persistently?

Persistent storage is any data storage device that retains data after power to that device is shut off. It is also sometimes referred to as non-volatile storage. Magnetic media, such as hard disk drives and tape are common types of persistent storage, as are the various forms of Optical media such as DVD.

What is persistent data in database?

Persistence is "the continuance of an effect after its cause is removed". In the context of storing data in a computer system, this means that the data survives after the process with which it was created has ended. In other words, for a data store to be considered persistent, it must write to non-volatile storage.

What is a persistent storage in Java?

Persistence, in computer science, is a noun describing data that outlives the process that created it. Java persistence could be defined as storing anything to any level of persistence using the Java programming language, but obviously this would be too broad a definition to cover in a single book.

What is important about persistent data?

Process persistence is achieved by storing core system processes in non-volatile, persistent storage. Persistent data is important because of its cross-platform access, non-volatility, reliability, stability, static, and time-independent features.


2 Answers

Instead of trying to map some data to some model items, make each item store its data. This involves using a delegate for painting and not relying on QAbstractItemView paint event.

Let's have a simple stateful class, to represent our item data, and let's make it a new citizen in Qt meta-object system, using the Q_DECLARE_METATYPE macro.

#include <QMetaType>
class ItemData
{
public:
    ItemData() = default;
    ItemData(int d) : _data(d){}
    int data() const { return _data; }
    void paint(QPainter *painter, QRect rect);
private:
    int _data;
};
Q_DECLARE_METATYPE(ItemData)

Now the delegate, a very simple one, indeed:

#include <QStyledItemDelegate>
class ItemDelegate : public QStyledItemDelegate
{
public:
    // ...
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    // ...
};

The delegate paint method, is where things happens:

void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if(index.data().canConvert<ItemData>())
    {
        ItemData itemdata = qvariant_cast<ItemData>(index.data());
        itemdata.paint(painter, option.rect);
    }
    else
    {
        QStyledItemDelegate::paint(painter, option, index);
    }
}

Here we can use the model index for what it is meant: retrieve the item data. Our item can paint itself, though. Let it paint some circles, according to its internal state:

void ItemData::paint(QPainter *painter, QRect rect)
{
    QRect r(rect.left() + 2, rect.top() + 2, rect.height() - 4, rect.height() - 4);
    for(int i=0; i<_data; ++i)
    {
        painter->drawEllipse(r);
        r.moveLeft(r.left() + rect.height() + 2);
    }
}

In a more flexible design, data is decoupled from rendering, so the ItemData class have no paint method and painting is performed by the delegate itself:

void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if(index.data().canConvert<ItemData>())
    {
        ItemData itemdata = qvariant_cast<ItemData>(index.data());
        //itemdata.paint(painter, option.rect);
        QRect rect = option.rect;
        QRect r(rect.left() + 2, rect.top() + 2, rect.height() - 4, rect.height() - 4);
        for(int i=0; i<itemdata.data(); ++i)
        {
            painter->drawEllipse(r);
            r.moveLeft(r.left() + rect.height() + 2);
        }
    }
    else
    {
        QStyledItemDelegate::paint(painter, option, index);
    }
}

This way, we can implement and then choose different delegates for different views, yet keeping a single consistent model and share it among them.

Using delegate and data in a item-based widget like QListWidget is straightforward. In a form constructor:

ui->listWidget->setItemDelegate(new ItemDelegate());
for(int i=0; i<10; ++i)
{
    QListWidgetItem * item = new QListWidgetItem();
    item->setData(0, QVariant::fromValue(ItemData(i + 1)));
    ui->listWidget->addItem(item);
}

Not so different in a model-based one, though: instead of setting data to items, data is set to the model, again using QVariant::fromValue.

like image 23
p-a-o-l-o Avatar answered Nov 15 '22 07:11

p-a-o-l-o


You can safely use a QPersistentModelIndex as a map or hash key. Even if the underlying QModelIndex changes, the "persistent" part ensure that all the QPersistentModelIndex are kept up to date while preserving their identities, i.e operator == and qHash() return consistent values.

That being said, you should not store data about the index in your view. The data are supposed to be stored by the model. And this seems to be how its done in Qt classes: views are making a lot of calls to QAbstractItemModel::data().

The only data that I would deem worthy to be stored in the view is "cache data", i.e values that are:

  • not directly provided by the model
  • require heavy computation to be calculated from the model's data
  • specific to the view

If any of these 3 conditions is not met, my personal preference would be to store the data in the model.


Edit

Contrary to my original answer, you cannot safely use QPersistentModelIndex as a QMap key.

The reason is that QMap uses operator < for inserting and finding data.

The issue here is that bool QPersistentModelIndex::operator<(const QPersistentModelIndex &other) const is just a proxy to bool QModelIndex::operator<(const QModelIndex &other) const which compares indexes by row and column. This means that the sort order of QPersistentModelIndex is not persistent and will change when indexes are moved in the model.

However, QMap is not aware that the sort order has changed. And at this point searching a value inside a QMap<QPersistentModelIndex, T> is just as broken as doing binary search on an unsorted array.

Note that this issue is not limited to QMap, it affects all containers which are based on the ordering of the keys like std::map. However, containers that do not rely on ordering (QHash, std::unordered_map, ...) are not affected.

like image 144
Benjamin T Avatar answered Nov 15 '22 06:11

Benjamin T