Qt信号转发;继承QAbstractProxyModel

6

文档中好像没有提到这个问题,我在StackOverflow和其他地方看到了一堆含糊不清的示例代码,所以...

如果有一个类A实现了QAbstractProxyModel接口,另一个类B实现了QAbstractItemModel接口,并且我调用A实例的setSourceModel(b)方法,其中bB类的实例,那么是否会自动转发更新信号,如modelResetrowsInserted等?还是必须手动连接所有这些信号?

4个回答

3
您说得对,关于这个问题文档确实没有提供太多帮助。查看Qt 5.12中的QAbstractProxyModel源代码并将其与QSortFilterProxyModel进行比较,可以发现QAbstractProxyModel根本不处理数据更改信号的转发!您必须自己处理它!最好选择一个更复杂的模型,如QSortFilterProxyModel或QIdentityProxyModel,它们会为您处理此转发。但如果您真的无法绕过它,那么代码可能如下所示:
/**
 * 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中重新连接这些信号:

  • dataChanged
  • headerDataChanged
  • rowsAboutToBeInserted
  • rowsInserted
  • columnsAboutToBeInserted
  • columnsInserted
  • rowsAboutToBeRemoved
  • rowsRemoved
  • columnsAboutToBeRemoved
  • columnsRemoved
  • rowsAboutToBeMoved
  • rowsMoved
  • columnsAboutToBeMoved
  • columnsMoved
  • layoutAboutToBeChanged
  • layoutChanged
  • modelAboutToBeReset
  • modelReset

1

从文档中可以看到:

要实现QAbstractProxyModel子类,您需要实现mapFromSource()和mapToSource()方法。只有在需要与默认行为不同的行为时,才需要重新实现mapSelectionFromSource()和mapSelectionToSource()函数。

关于信号方面没有提及。在上述方法的文档中也没有相关说明。这意味着您不需要担心信号,它们将自动发出。


1
如果 class A : public QAbstractProxyModelclass 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;
}

...


0
如果类似于 class A : public QAbstractProxyModelclass B : public QAbstractItemModel,那么信号和槽也应该被继承。(除非你想要特殊的行为)。
如果 "QAbstractClasses" 是 AB 的简单成员,则必须进行“转发”。

1
信号的继承并不意味着它们会正常工作。例如,如果你继承了QAbstractItemModel类,你必须在你的setData方法实现中发出dataChanged信号。 - Pavel Strakhov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接