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

C++ 存储有关视图中项目的持久信息,c++,qt,view,model,qt5,C++,Qt,View,Model,Qt5,我直接基于qabstractemview有自己的观点。 一般来说,我需要存储一些关于特定型号项目的信息 所以在我看来,我有一个从QModelIndex到描述特定项的结构的映射。 然后我主要在视图的paintEvent上使用这些数据 问题是,QModelIndex不是持久性的,它可能会过时。 所以,当从模型中插入或删除行时,一些QModelIndex可能会变得无效,我不应该依赖它们 那个么,如何在模型中的项目和视图中使用的一些装饰数据之间建立关系呢 QPersistentModelIndex似乎是

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

所以在我看来,我有一个从
QModelIndex
到描述特定项的结构的映射。 然后我主要在视图的
paintEvent
上使用这些数据

问题是,QModelIndex不是持久性的,它可能会过时。 所以,当从模型中插入或删除行时,一些
QModelIndex
可能会变得无效,我不应该依赖它们

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

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

QPersistentModelIndex
的另一个问题是,它可能不应该被用作map的键(在我的例子中就是这样),因为它可能(并且将)改变并使map不一致

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


因此,在这一点上,我看不到任何简单的方法来解决我的问题。

您可以安全地使用
QPersistentModelIndex
作为映射或哈希键。 即使基础QModelIndex发生更改,“持久化”部分也确保所有
QPersistentModelIndex
在保持其身份的同时保持最新,即
操作符==
qHash()
返回一致的值

也就是说,您不应该在视图中存储有关索引的数据。数据应该由模型存储。 在Qt类中似乎就是这样:视图对
qabstractemmodel::data()
进行了大量调用

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

  • 不是由模型直接提供的
  • 需要根据模型数据进行大量计算
  • 特定于视图
如果这3个条件中的任何一个不满足,我个人的偏好是将数据存储在模型中


编辑 与我最初的回答相反,您不能安全地将
QPersistentModelIndex
用作
QMap


原因是
QMap
使用
操作符而不是尝试将某些数据映射到某些模型项,而是使每个项存储其数据。这涉及使用代理进行绘制,而不依赖于
qabstractemview
paint事件

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

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

在基于项的小部件(如
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);
}
ui->listWidget->setItemDelegate(newItemDelegate());
for(inti=0;isetData(0,QVariant::fromValue(ItemData(i+1)));
ui->listWidget->addItem(项目);
}

不过,与基于模型的视图没有太大区别:不是将数据设置为项,而是将数据设置为模型,再次使用
QVariant::fromValue

我几乎可以肯定您不想实现自己的视图。您为什么这么认为?@lpapp:您能解释一下吗?如果您确实想创建自己的视图,也许不应该使用QAbstractItemModel,但只需创建更符合您需要的内容即可。QAbstractItemModel旨在同时对所有Qt视图可用,即使出于此目的,它似乎也非常复杂。我匹配第三种情况。我仍然担心此类解决方案的性能。不过,我会尝试一下并执行profiling。我认为qt模型拥有唯一的项目id会很好。这将非常有效。我的第一个猜测是
QModelIndex::internalId()
是解决方案,但测试证明不是(至少对于一些标准的qt模型不是这样)@PatrickParker
QPersistentModelIndex
在模型更新时更新。如果之前插入了行,则
QPersistentModelIndex::row()
返回的值将增加1。如果之前删除了行,则该值将减少1。如果“单元格”被
QPersistentModelIndex
引用的
QPersistentModelIndex::isValid()
将返回false。@PatrickParker
QPersistentModelIndex
使用
QPersistentModelIndexData
实现。引用相同“单元格”的
QPersistentModelIndex
的两个实例将共享
QPersistentModelIndexData
的同一实例。2
QPersistentModelIndexData
当且仅当它们在内部使用相同的
QPersistentModelIndexData*
时才相等。一个
QPersistentModelIndexData
的qHash只是其内部
QPersistentModelIndexData*
的qHash刚刚编辑了我的答案。我花了一些时间挖掘Qt代码并运行测试,
QPersistentModelIndex
不能用作
QMap
键。它们的顺序是基于行/列的,当模型更改时,行/列将更改,无声地打破
QMap
内部顺序。@BenjaminT感谢您的确认。如果我们增加了不调用或实现
moveRows
的规定(这意味着行号仅因删除或插入而改变)也许这样会比较安全?我想我们需要进一步保证在
rowsabouttoberemove
期间从QMap中删除索引,因为删除的行可能会将我们的
QPersistentModelIndex
键跳转到逻辑行号
-1
——这也违反了假定的排序顺序。不过,您的解决方案很有趣要求我“覆盖”显示
#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);
    }
}
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);
    }
}
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);
}