文档中好像没有提到这个问题,我在StackOverflow和其他地方看到了一堆含糊不清的示例代码,所以...
如果有一个类A
实现了QAbstractProxyModel
接口,另一个类B
实现了QAbstractItemModel
接口,并且我调用A
实例的setSourceModel(b)
方法,其中b
是B
类的实例,那么是否会自动转发更新信号,如modelReset
、rowsInserted
等?还是必须手动连接所有这些信号?
/**
* Proxy model which only returns one data row of the underlying QAbstractItemModel
* except for the first column. Can be used to separate a model for QTreeView into
* the tree column and the data columns. This proxy returns the data columns.
* Can't use QSortFilterProxyModel because it does not allow for only showing one
* row if its parent is filtered out.
*/
class SingleRowProxy :
public QAbstractProxyModel
{
Q_OBJECT;
using BaseType = QAbstractProxyModel;
static constexpr auto FIRST_DATA_COLUMN = 1;
public:
SingleRowProxy( QAbstractItemModel* sourceModel,
int row,
const QModelIndex& parentIndex,
QObject* parentObject = nullptr ) :
BaseType( parentObject ),
m_sourceRow( row ),
m_sourceParent( parentIndex )
{
Q_ASSERT( sourceModel != nullptr );
setSourceModel( sourceModel );
}
void setSourceModel( QAbstractItemModel *newSourceModel ) override
{
if ( newSourceModel == sourceModel() ) {
return;
}
beginResetModel();
disconnect( newSourceModel, nullptr, this, nullptr );
BaseType::setSourceModel( newSourceModel );
connect( newSourceModel, &QAbstractItemModel::dataChanged,
this, &SingleRowProxy::sourceDataChanged );
connect( newSourceModel, &QAbstractItemModel::modelAboutToBeReset,
this, [this] () { beginResetModel(); } );
connect( newSourceModel, &QAbstractItemModel::modelReset,
this, [this] () { endResetModel(); } );
}
QModelIndex
mapFromSource( const QModelIndex& sourceIndex ) const override
{
if ( !sourceIndex.isValid() || ( sourceIndex.column() < FIRST_DATA_COLUMN ) ) {
return {};
}
return index( 0, sourceIndex.column() - FIRST_DATA_COLUMN, QModelIndex() );
}
QModelIndex
mapToSource( const QModelIndex& proxyIndex ) const override
{
if ( !proxyIndex.isValid() ) {
return {};
}
return sourceModel()->index( m_sourceRow,
proxyIndex.column() + FIRST_DATA_COLUMN,
m_sourceParent );
}
QVariant
data( const QModelIndex& index,
int role ) const override
{
return sourceModel()->data( mapToSource( index ), role );
}
int
rowCount( [[maybe_unused]] const QModelIndex& parent = QModelIndex() ) const override
{
return sourceModel()->hasIndex( m_sourceRow, FIRST_DATA_COLUMN, m_sourceParent ) ? 1 : 0;
}
int
columnCount( [[maybe_unused]] const QModelIndex& parent = QModelIndex() ) const override
{
return sourceModel()->columnCount( sourceModel()->index( m_sourceRow, 0, m_sourceParent ) );
}
QModelIndex
index( int row,
int column,
const QModelIndex& parent ) const override
{
if ( !hasIndex( row, column, parent ) ) {
return {};
}
return createIndex( row, column );
}
QModelIndex
parent( [[maybe_unused]] const QModelIndex& child ) const override
{
return {};
}
private slots:
/**
* QSortFilterProxyModel does it for us but QAbstractProxyModel does not!
* So we have to map the source indices and reemit the dataChanged signal. */
void
sourceDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles )
{
if ( !topLeft.isValid() ||
!bottomRight.isValid() ||
( topLeft.parent() != bottomRight.parent() ) )
{
return;
}
const auto& parent = topLeft.parent();
int minRow = std::numeric_limits<int>::max();
int maxRow = std::numeric_limits<int>::lowest();
int minCol = std::numeric_limits<int>::max();
int maxCol = std::numeric_limits<int>::lowest();
bool foundValidIndex = false;
for ( int sourceRow = topLeft.row(); sourceRow <= bottomRight.row(); ++sourceRow ) {
for ( int sourceColumn = topLeft.column(); sourceColumn <= bottomRight.column(); ++sourceColumn ) {
const auto index = mapFromSource( sourceModel()->index( sourceRow, sourceColumn, topLeft.parent() ) );
if ( !index.isValid() ) {
continue;
}
minRow = std::min( minRow, index.row() );
maxRow = std::max( maxRow, index.row() );
minCol = std::min( minCol, index.column() );
maxCol = std::max( maxCol, index.column() );
foundValidIndex = true;
}
}
if ( foundValidIndex ) {
emit dataChanged( index( minRow, minCol, parent ),
index( maxRow, maxCol, parent ),
roles );
}
}
private:
const int m_sourceRow;
const QModelIndex m_sourceParent;
};
你需要执行索引映射,因为代理索引与源模型索引不同!
请注意,此示例非常基础,并且可能无法适用于任意映射。
请注意,对于完整的代理模型,您必须映射和转发所有信号。QSortFilterProxyModel在setSourceModel中重新连接这些信号:
从文档中可以看到:
要实现QAbstractProxyModel子类,您需要实现mapFromSource()和mapToSource()方法。只有在需要与默认行为不同的行为时,才需要重新实现mapSelectionFromSource()和mapSelectionToSource()函数。
关于信号方面没有提及。在上述方法的文档中也没有相关说明。这意味着您不需要担心信号,它们将自动发出。
class A : public QAbstractProxyModel
,class B : public QAbstractItemModel
,一些信号(例如 dataChanged
)可以正常转发和更新。但是有些信号(例如 rowsInserted
)需要手动连接。...
#define COL_ID 0
void A::setSourceModel(QAbstractItemModel *newSourceModel) {
beginResetModel();
if (this->sourceModel()) { // disconnect sourceModel signals
...
}
...
QAbstractProxyModel::setSourceModel(newSourceModel);
if (this->sourceModel()) { // connect sourceModel signals
...
connect(this->sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(sourceRowsInserted(QModelIndex, int, int)));
...
}
return;
}
...
void A::sourceRowsInserted(const QModelIndex &parent, int first, int last) {
QModelIndex parentIndex = this->mapFromSource(parent);
QModelIndex sourceTopIndex = this->sourceModel()->index(first, COL_ID, parent);
QModelIndex sourceBottomIndex = this->sourceModel()->index(last, COL_ID, parent);
QModelIndex topIndex = this->mapFromSource(sourceTopIndex);
QModelIndex bottomIndex = this->mapFromSource(sourceBottomIndex);
beginInsertRows(parentIndex, topIndex.row(), bottomIndex.row());
endInsertRows();
return;
}
...
class A : public QAbstractProxyModel
和 class B : public QAbstractItemModel
,那么信号和槽也应该被继承。(除非你想要特殊的行为)。A
和 B
的简单成员,则必须进行“转发”。
QAbstractItemModel
类,你必须在你的setData
方法实现中发出dataChanged
信号。 - Pavel Strakhov