存储有关视图中项目的持久信息

时间:2014-12-21 08:46:07

标签: c++ qt view model qt5

我自己的观点直接基于QAbstractItemView。 一般来说,我需要存储一些有关特定型号项目的信息。

所以在我看来,我有一张从QModelIndex到结构描述partical项目的地图。 然后我主要使用这些数据来查看paintEvent

问题是,QModelIndex不是持久性的,它可能会过时。 因此,当从模型中插入或删除行时,某些QModelIndex可能会变为无效,我不应该继续使用它们。

那么如何在模型中的项目和我在视图中使用的一些装饰数据之间建立关系?

QPersistentModelIndex似乎是适合此类事情的工具,但我知道它的性能(我的模型和视图可能很大)。

QPersistentModelIndex的另一个问题是它可能不应该用作map的键(就像我的情况一样),因为它可能(并且会)改变并使地图不一致。

我已经看了Qt的QTreeView和QListView的实现,看看它们如何处理行删除/插入,但似乎它们只是删除了所有数据。

所以在这一点上,我看不出任何简单的方法来解决我的问题。

2 个答案:

答案 0 :(得分:6)

您可以安全地使用QPersistentModelIndex作为地图或哈希键。 即使底层的QModelIndex发生了变化," persistent"部分确保所有QPersistentModelIndex都保持最新,同时保留其身份,即operator ==qHash()返回一致的值。

话虽如此,您不应该在视图中存储有关索引的数据。数据应该由模型存储。 这似乎是它在Qt类中的完成方式:视图对QAbstractItemModel::data()进行了大量调用。

我认为值得存储在视图中的唯一数据是"缓存数据",即值:

  • 不是由模特直接提供的
  • 需要从模型的数据计算大量计算
  • 特定于视图

如果不满足这3个条件中的任何一个,我个人的偏好是将数据存储在模型中。

答案 1 :(得分:2)

不是尝试将某些数据映射到某些模型项,而是让每个项都存储其数据。这涉及使用委托进行绘画而不依赖于QAbstractItemView绘画事件。

让我们有一个简单的有状态类来表示我们的项目数据,并让我们使用Q_DECLARE_METATYPE宏在Qt元对象系统中使其成为新的公民。

#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)

现在代表,一个非常简单的代表,确实:

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

委托绘制方法,就是发生的事情:

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);
    }
}

在这里,我们可以使用模型索引来表示它的含义:检索项目数据。不过,我们的项目可以自己画画。根据其内部状态,让它绘制一些圆圈:

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);
    }
}

在更灵活的设计中,数据与渲染分离,因此ItemData类没有paint方法,绘画由委托本身执行:

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);
    }
}

这样,我们可以为不同的视图实现然后选择不同的委托,同时保持一个一致的模型并在它们之间共享。

QListWidget等基于项目的窗口小部件中使用委托和数据非常简单。 在表单构造函数中:

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);
}

在基于模型的项目中没有那么不同:不是将数据设置为项目,而是使用QVariant::fromValue将数据设置为模型。