2

I use QTableView and custom model, and I want scroll to specific item after model update.

I create two buttons "Update model" and "scroll to":

 btn->setText("Update model");
  QObject::connect(btn, &QPushButton::clicked, [&tbl_model, view] {
    tbl_model.update();
    auto idx = tbl_model.index(49, 0);
    qDebug() << "idx: " << idx;
    view->scrollTo(idx, QAbstractItemView::PositionAtCenter);
  });

  btn->setText("scroll to");
  QObject::connect(btn, &QPushButton::clicked, [view, &tbl_model] {
    auto idx = tbl_model.index(49, 0);
    qDebug() << "idx: " << idx;
    view->scrollTo(idx, QAbstractItemView::PositionAtCenter);
  });

update method code:

  void update() {
    beginResetModel();
    auto new_size = data_.size() == 100 ? 50 : 100;
    data_.clear();
    for (int i = 0; i < new_size; ++i) {
      data_.append(i + 1);
    }
    endResetModel();
  }

If I press "Update model" and my model size expanding from 50 to 100, then I see item with row==49 at the bottom of window, then if I press "scroll to" button, I will see it the center.

So how should I use scrollTo after model full update? Of course I could add processEvents or use QTimer::singleShot, but it looks like hack, may be there is some event or signal that view ready for scrolling?

Full code

user1244932
  • 7,352
  • 5
  • 46
  • 103

1 Answers1

2

For some reason, the view needs to enter the event loop after resetting the model (to handle some event(s)) before calling QTableView::scrollTo() in order for the latter to work properly. I remember encountering similar situations many times while using Qt.

I don't find it really bad to use something like a QTimer to schedule the scrollTo later. If you prefer a more readable solution, check out this answer. Basically, you can define your own QueuedInvoke function as follows:

//the functor gets invoked in the thread where the contextObject lives
//or in the current thread if no contextObject is provided
template <typename Func>
void QueuedInvoke(Func&& f, QObject* contextObject = QAbstractEventDispatcher::instance()){
    QObject signalSource;
    QObject::connect(&signalSource, &QObject::destroyed, 
                     contextObject, std::forward<Func>(f), Qt::QueuedConnection);
}

After that, you can just replace the line where you call scrollTo with something like this:

QueuedInvoke([view, idx]{
    view->scrollTo(idx, QAbstractItemView::PositionAtCenter);
}, view);
Mike
  • 8,055
  • 1
  • 30
  • 44
  • Thanks for answer, but I find strange thing, if I call `scrollTo` two times in row in "Update model" button handler all works as expected, `scrollTo` uses processEvent internally, or ..? – user1244932 Jun 22 '18 at 15:53
  • @user1244932, This is really strange (I get the same behavior here too). Your explanation is quite sensible. I had a quick look at the source code for `QAbstractItemView::scrollTo` but I couldn't find the culprit. Maybe you have better eyes ;) Anyways, I wouldn't consider calling `scrollTo` twice a solution at all... – Mike Jun 22 '18 at 16:02