QSortFilterProxyModel 返回虚假的行
我正在使用QSortFilterProxyModel来过滤QAbstractListModel中的结果。不过,我想在返回的结果中加一个在原始模型中不存在的第一个条目,也就是说,这个条目是人为添加的。
这是我目前的代码:
class ActivedAccountModel(QSortFilterProxyModel):
def __init__(self, model, parent=None):
super(ActiveAccountModel, self).__init__(parent)
self.setSourceModel(model)
self.setDynamicSortFilter(True)
def data(self, index, role=Qt.DisplayRole):
account_info = super(ActiveAccountModel, self).data(index, Qt.UserRole).toPyObject()
if role == Qt.DisplayRole:
return account_info.name
elif role == Qt.UserRole:
return account_info
return None
def filterAcceptsRow(self, source_row, source_parent):
source_model = self.sourceModel()
source_index = source_model.index(source_row, 0, source_parent)
account_info = source_model.data(source_index, Qt.UserRole)
return isinstance(account_info.account, Account) and account_info.account.enabled
这段代码会返回一个这样的列表:
Account 1
Account 2
...
我想在返回的元素列表的开头加一个额外的元素:
Extra Element
Account 1
Account 2
...
我尝试重新实现rowCount方法,以便返回实际的行数加1,但我需要将所有的项目“移动”一下,以便把这个人为的元素放在索引0的位置,这让我有点困惑。
有没有什么线索?我到现在为止没有找到相关的代码示例……谢谢!
3 个回答
我最近也遇到了类似的问题,特别是在处理父子关系和源模型映射时,遇到了很多麻烦。
我的版本需要处理一些虚拟列,左边有几个与操作相关的列,还有可能有一个复选框列。
希望这些信息也能帮助到其他人 :)
不过,有个小建议,我在子类化一个叫QSortFilterProxyModel的东西时,似乎失去了排序的能力。我猜是因为我重写了index和data这两个方法。如果我改为先子类化QIdentityProxyModel,然后再添加QSortFilterProxyModel,我就会失去勾选和取消勾选复选框列的能力……尽管标志已经设置为Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsEditable……这真是让人头疼 :)
QModelIndex GenericProxy::mapToSource(const QModelIndex & proxy) const {
if(not proxy.isValid())
return QModelIndex();
if((action || checkbox)) {
int column = proxy.column() - addedCount();
if(column < 0) // this index is local.
return QModelIndex();
QModelIndex idx = sourceModel()->index(proxy.row(), column, mapToSource(proxy.parent()));
return idx ;
}
QModelIndex idx = sourceModel()->index(proxy.row(), proxy.column(), mapToSource(proxy.parent()));
return idx;
}
QModelIndex GenericProxy::mapFromSource(const QModelIndex & source) const {
if(not source.isValid())
return QModelIndex();
if((action || checkbox)) {
// simply add appropriate informations ..
int column = source.column() + addedCount();
QModelIndex idx = index(source.row(), column, mapFromSource(source.parent()));
return idx;
}
QModelIndex idx = index(source.row(), source.column(), mapFromSource(source.parent()));
return idx;
}
GenericItem * GenericProxy::convert(const QModelIndex & idx) const {
if(idx.isValid())
return _convert(index(idx.row(), firstRealColumn(), idx.parent()));
else
return _convert(idx);
}
// _convert doesn't take care of index not really at the rightplace_ness :)
GenericItem * GenericProxy::_convert(const QModelIndex & index) const {
if(not index.isValid())
return dynamic_cast<GenericModel *>(sourceModel())->convert(QModelIndex());
return static_cast<GenericItem*>(index.internalPointer());
}
QModelIndex GenericProxy::parent(const QModelIndex & item) const {
if(not item.isValid())
return QModelIndex();
GenericItem * child = _convert(item);
if(!child)
return QModelIndex();
GenericItem * parent = child->parentItem();
if(parent == _convert(QModelIndex()))
return QModelIndex();
int column = addedCount();
return sourceModel()->parent(mapToSource(createIndex(item.row(), column, parent )));
}
QModelIndex GenericProxy::index(int row, int column, const QModelIndex & parent) const {
if( not hasIndex(row,column,parent))
return QModelIndex();
GenericItem * pitem = convert(parent);
GenericItem * pchild = pitem->child(row);
if(pchild)
return createIndex(row, column, pchild);
else
return QModelIndex();
}
因为我在实现这个功能的时候遇到了一些困难,而且在网上找不到其他的示例代码,所以我决定分享这个示例实现。
希望这也能帮助到其他人...
/**
** Written by Sven Anders (ANDURAS AG). Public domain code.
**/
#include <QDebug>
#include <QBrush>
#include <QFont>
#include <QSortFilterProxyModel>
/** Definition **/
class ProxyModelNoneEntry : public QSortFilterProxyModel
{
Q_OBJECT
public:
ProxyModelNoneEntry(QString _entry_text = tr("(None)"), QObject *parent=0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
/* lessThan() is not necessary for this model to work, but can be
implemented in a derived class if a custom sorting method is required. */
// bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &child) const;
private:
QString entry_text;
};
/** Implementation **/
ProxyModelNoneEntry::ProxyModelNoneEntry(QString _entry_text, QObject *parent) : QSortFilterProxyModel(parent)
{
entry_text = _entry_text;
}
int ProxyModelNoneEntry::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return QSortFilterProxyModel::rowCount()+1;
}
QModelIndex ProxyModelNoneEntry::mapFromSource(const QModelIndex &sourceIndex) const
{
if (!sourceIndex.isValid()) return QModelIndex();
else if (sourceIndex.parent().isValid()) return QModelIndex();
return createIndex(sourceIndex.row()+1, sourceIndex.column());
}
QModelIndex ProxyModelNoneEntry::mapToSource(const QModelIndex &proxyIndex) const
{
if (!proxyIndex.isValid()) return QModelIndex();
else if (proxyIndex.row() == 0) return QModelIndex();
return sourceModel()->index(proxyIndex.row()-1, proxyIndex.column());
}
QVariant ProxyModelNoneEntry::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) return QVariant();
if (index.row() == 0)
{
if (role == Qt::DisplayRole)
return entry_text;
else if (role == Qt::DecorationRole)
return QVariant();
else if (role == Qt::FontRole)
{ QFont font; font.setItalic(true); return font; }
else
return QVariant();
}
return QSortFilterProxyModel::data(createIndex(index.row(),index.column()), role);
}
Qt::ItemFlags ProxyModelNoneEntry::flags(const QModelIndex &index) const
{
if (!index.isValid()) return Qt::NoItemFlags;
if (index.row() == 0) return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
return QSortFilterProxyModel::flags(createIndex(index.row(),index.column()));
}
QModelIndex ProxyModelNoneEntry::index(int row, int column, const QModelIndex &parent) const
{
if (row > rowCount()) return QModelIndex();
return createIndex(row, column);
}
QModelIndex ProxyModelNoneEntry::parent(const QModelIndex &child) const
{
Q_UNUSED(child)
return QModelIndex();
}
祝好,
Sven
我在工作中做过这个,所以不能给你太多代码。不过我可以告诉你大概的思路。
如果你想做得更好,可以创建一个 QAbstractProxyModel 的子类。这个类是为了处理一般的数据操作,而不是专门用来排序或过滤的。你需要重写 rowCount 方法,还要重写 columnCount 方法(不过这个方法只需要返回源模型的信息就可以了)。你还需要重写 data 方法,返回你自己定义的第一行数据,或者再调用一次源模型。
你还需要重写 mapFromSource 和 mapToSource 这两个方法,以便在代理模型的索引和源模型的索引之间切换。
为了实现一个稳健的功能,你需要创建一些槽函数,并连接到源模型的数据变化、模型重置,以及行/列即将插入/删除的信号。然后你应该发出自己的信号,适当地调整它们,以考虑到你多出的那一行。
在我们的类中,我们让第一行的文本可以设置,这样我们就可以在不同的情况下使用同一个代理模型。你也可以考虑这样做,因为这几乎不需要额外的工作。
编辑
根据评论的请求,简单介绍一下 mapToSource 和 mapFromSource。这是你需要考虑的大致内容。
// Remember that this maps from the proxy's index to the source's index,
// which is invalid for the extra row the proxy adds.
mapToSource( proxy_index ):
if proxy_index isn't valid:
return invalid QModelIndex
else if proxy_index is for the first row:
return invalid QModelIndex
else
return source model index for (proxy_index.row - 1, proxy_index.column)
mapFromSource( source_index ):
if source_index isn't valid:
return invalid QModelIndex
else if source_index has a parent:
// This would occur if you are adding an extra top-level
// row onto a tree model.
// You would need to decide how to handle that condition
return invalid QModelIndex
else
return proxy model index for (source_index.row + 1, source_index.column)