【问题标题】:Qt signal forwarding; inheriting QAbstractProxyModelQt 信号转发;继承 QAbstractProxyModel
【发布时间】:2019-08-08 22:34:13
【问题描述】:

文档中似乎提到了这一点,而且我在 StackOverflow 和其他地方看到了一堆模棱两可的示例代码,所以...

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

【问题讨论】:

    标签: c++ qt qt5


    【解决方案1】:

    您是对的,文档对此非常没有帮助。查看 QAbstractProxyModel 的源代码并将其与 Qt 5.12 中的 QSortFilterProxyModel 进行比较,得出 QAbstractProxyModel 根本不处理 dataChanged 信号的转发!你必须自己做!最好选择更复杂的模型,例如 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;
    };
    

    您必须这样做index mapping,因为代理索引与源模型索引不同!

    请注意,此示例只是非常初级的,可能不适用于任意映射。

    请注意,对于完整的代理模型,您必须映射和转发所有信号。 QSortFilterProxyModel 在 setSourceModel 中重新连接这些信号:

    • 数据已更改
    • headerDataChanged
    • rowsAboutToBeInserted
    • 插入行
    • columnsAboutToBeInserted
    • 列插入
    • rowsAboutToBeRemoved
    • rowsRemoved
    • columnsAboutToBeRemoved
    • columnsRemoved
    • rowsAboutToBeMoved
    • 行移动
    • columnsAboutToBeMoved
    • 列移动
    • layoutAboutToBeChanged
    • layoutChanged
    • modelAboutToBeReset
    • 模型重置

    【讨论】:

      【解决方案2】:

      如果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;
      }
      

      ...

      【讨论】:

        【解决方案3】:

        如果类类似于class A : public QAbstractProxyModelclass B : public QAbstractItemModel,则信号和槽也应该被继承。 (除非你想要它的特殊行为。

        如果“QAbstractClasses”是AB 的简单成员,您必须“转发”它们

        【讨论】:

        • 信号被继承的事实并不意味着它们会正常工作。例如,如果您将QAbstractItemModel 子类化,则必须在您的setData 方法实现中发出dataChanged 信号。
        【解决方案4】:

        来自文档:

        要继承 QAbstractProxyModel,您需要实现 mapFromSource() 和 mapToSource()。只有当您需要不同于默认行为的行为时,才需要重新实现 mapSelectionFromSource() 和 mapSelectionToSource() 函数。

        没有关于信号的词。所以它在提到的方法的文档中。这意味着您不需要关心信号,它们会自动发出。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-10-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-11-10
          相关资源
          最近更新 更多