How to get list of visible QModelIndex in QAbstractItemView
Asked Answered
I

5

9

Is there any way to get a list of currently visible items in QAbstractItemView? And, if it possible, to receive any notifications about changing of this list.

Upd: I'm asking exactly about QAbstractItemView or QTreeView with non-plain structure, not QTableView.

Upd2: I'm implementing tree view model with checkboxes. I want next behavior (same for checking/uncheking):

  • If one of checkbox is checked - then all childs must be checked
  • If all child checkboxes are checked - then parent check box should be checked too. And same for parent of parent, and so on...

Check state are monitored/modified by external data source, so I need a mechanism to update all changed children/parents. dataChanged signal is not enough for me because it is very expansive to build a list of all changed QModelIndex for updating. And it is not necessary at all, because all fresh data will be picked from QAbstractItemModel::data.

I found next dirty hack to update all items: emit dataChanged( QModelIndex(), QModelIndex() ); but it's undocumented for invalid indexes.

So, I need a way to force all visible items redraw they content with fresh data.

Integrator answered 4/4, 2013 at 17:7 Comment(0)
I
0

I think that there are no cases where list of visible items is required. In case of correct model implementation, all items are updated automatically.

Hard part of implementation — force children and parents to update. I wrote the following code:

bool TreeModel::setData( const QModelIndex &index, const QVariant &value, int role )
    case Qt::CheckStateRole:
    {
        TreeItemList updateRangeList;  // Filled with items, in which all childred must be updated
        TreeItemList updateSingleList; // Filled with items, which must be updated
        item->setCheckState( value.toBool(), updateRangeList, updateSingleList ); // All magic there
        
        foreach ( TreeAbstractItem *i, updateRangeList )
        {
            const int nRows = i->rowCount();
            QModelIndex topLeft = indexForItem( i->m_childs[0] );
            QModelIndex bottomRight = indexForItem( i->m_childs[nRows - 1] );
            emit dataChanged( topLeft, bottomRight );
        }
        foreach ( TreeAbstractItem *i, updateSingleList )
        {
            QModelIndex updateIndex = indexForItem( i );
            emit dataChanged( updateIndex, updateIndex );
        }
    }
Integrator answered 8/4, 2013 at 9:1 Comment(0)
A
13

You can get the topleft and bottom right cell by calling:

tableview->indexAt(tableview->rect().topLeft())
tableview->indexAt(tableview->rect().bottomRight())

To get notification of change, reimplement the virtual function of qabstractscrollarea

scrollContentsBy

This function is called when the view port is scrolled. call QTableView::scrollContentsBy and then do whatever you need to.

Areta answered 4/4, 2013 at 17:44 Comment(4)
I'm not asking about simple QTableView, it's clear for me... I'm asking about custom tree-based view with my own items delegate. Scroll notofication are not enough because they will not cover expand and collapse events.Integrator
You can still use the indexAt to get the indices in the viewport. And connect scroll expand and collapse signals to a slot that will recalculate the indices. I think if you don't wanna do something dirty, like intercept the paint method, then connecting to all existing signals that change the viewport is the way to go.Areta
indexAt is not acceptable, because I use QTreeView and some of items doesn't fit all row. See update of original question.Integrator
rect().topLeft() is by definition always (0,0). For QTreeView, the visualRect for an index does for some reason exclude the expand/collaps triangle icon, so visualRect().left() >= 20 in my case. Therefore indexAt should not work. For some reason it still works... but I would be sceptical maybe this is unintended behavior (or an undocumented special case).Lexicology
M
4

For QTreeView, the list of visible items can be traversed like this:

QTreeView& tv (yourTreeView);

// Get model index for first visible item
QModelIndex modelIndex = tv.indexAt(tv.rect().topLeft());

while (modelIndex.isValid())
{
    // do something with the item indexed by modelIndex
    ...
    // This navigates to the next visible item
    modelIndex = tv.indexBelow(modelIndex);
}
Mutism answered 13/3, 2014 at 19:58 Comment(3)
There may be no index at top-left. It depends on style paddings / delegates implementation and other stuff.Integrator
// This navigates to the next visible item modelIndex = tv.indexBelow(modelIndex); Will loop till end of the tree. May be here only we can put one more check if its in tv.rect() regeion , we can break the loop or check ItemAt(index). & check if y- axis value is grated than tv.tect.bottomLeft().y() thank break the loop.Preuss
To be more exact indexBelow, seems to traverse all expanded items not just those visible in the viewport. Also, instead of comparing with tv.rect(), I think tv.viewport.rect() should be used because it accounts for the tree/table header. As for the indexAt( topLeft() ) issue mentioned by @DmitrySazonov I opened an issue / question here.Lexicology
W
1

Method 1

i, j = table.indexAt(table.rect().topLeft()).row(), table.indexAt(table.rect().bottomLeft()).row() - 1

Method 2

i, j = table.rowAt(0), table.rowAt(table.height()) - 1
Westering answered 13/1, 2022 at 12:42 Comment(1)
It doesn't work for tree view.Integrator
I
0

I think that there are no cases where list of visible items is required. In case of correct model implementation, all items are updated automatically.

Hard part of implementation — force children and parents to update. I wrote the following code:

bool TreeModel::setData( const QModelIndex &index, const QVariant &value, int role )
    case Qt::CheckStateRole:
    {
        TreeItemList updateRangeList;  // Filled with items, in which all childred must be updated
        TreeItemList updateSingleList; // Filled with items, which must be updated
        item->setCheckState( value.toBool(), updateRangeList, updateSingleList ); // All magic there
        
        foreach ( TreeAbstractItem *i, updateRangeList )
        {
            const int nRows = i->rowCount();
            QModelIndex topLeft = indexForItem( i->m_childs[0] );
            QModelIndex bottomRight = indexForItem( i->m_childs[nRows - 1] );
            emit dataChanged( topLeft, bottomRight );
        }
        foreach ( TreeAbstractItem *i, updateSingleList )
        {
            QModelIndex updateIndex = indexForItem( i );
            emit dataChanged( updateIndex, updateIndex );
        }
    }
Integrator answered 8/4, 2013 at 9:1 Comment(0)
L
0

i always update whole QAbstractTableModel with:

emit dataChanged(index(0, 0), index(rowCount(), columnCount()-1)); // update whole view
Lir answered 1/8, 2017 at 9:58 Comment(1)
emit dataChanged( QModelIndex{}, QModelIndex{} ); looks better and more efficient.Integrator

© 2022 - 2024 — McMap. All rights reserved.