2

Please run the following code (I am using Qt 5.9):

QTableWidget* tableWidget = new QTableWidget(2, 2, nullptr);
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
connect(tableWidget->selectionModel(), &QItemSelectionModel::selectionChanged,
   [&](const QItemSelection& selected, const QItemSelection& deselected) 
   { qDebug() << "selected =" << selected << endl << "deselected =" << deselected; });
tableWidget->show();
QTimer::singleShot(10000, [=](){ tableWidget->removeRow(0); });

Within 10 seconds, select the first of two rows. You will see debug ouput. It will show you that row 0 was selected by your click. Then, after 10s, row 0 is removed automatically. Debug output now shows that row 1 is selected and row 0 is deselected.

The latter doesn't make any sense to me. When removing row 0 I would expect the "new" row 0 being selected afterwards. Also the visually selected row still is row 0 and row 1 simply doesn't exist anymore.

This also happens with a custom model and generic view and makes my application crash by pointing to a row that does not exist.

Is this desired behavior? Where is my misunderstanding?

Silicomancer
  • 8,604
  • 10
  • 63
  • 130

1 Answers1

0

It makes perfectly sense to change the selected row before deleting it. Doing the opposite may lead to reading dangling data, for example, if the UI is refreshed when the model has changed but the view holds outdated indices.

Think about removing row 0 twice: the second time is very obvious that the selection must be changed (deselected in this case) before removing the last row in the table to avoid having an invalid index as the selected row.

You can use the following modified example to see when the model is actually updated.

auto tableWidget = new QTableWidget(2, 2, nullptr);
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);

connect(tableWidget->selectionModel(), &QItemSelectionModel::selectionChanged,
[&](const QItemSelection& selected, const QItemSelection& deselected) 
{ qDebug() << "selected =" << selected << endl << "deselected =" << deselected; });

connect(tableWidget->model(), &QAbstractItemModel::rowsRemoved, [&](const QModelIndex &, int first, int last)
{ qDebug() << "first row removed =" << first << endl << "last row removed =" << last; });

tableWidget->show();

QTimer::singleShot(10000, [=](){ tableWidget->removeRow(0); });
QTimer::singleShot(15000, [=](){ tableWidget->removeRow(0); }); // remove twice

Workaround

The main problem is that, as you pointed out in the comments, you cannot rely on the signal information: those QModelIndex may or not be valid if a removal was performed. You can keep track of all changes but that would be exhausting.

Instead, you can try deferring the selection signal, so when it is handled the model has been updated and you can trust the information from the selection model. The trick is to use a timer: the function handling the timeout event will be executed in the next iteration of the events loop (even if the timeout time is 0), while the model and the widget are updated in the current iteration:

connect(tableWidget->selectionModel(), &QItemSelectionModel::selectionChanged,
[&](const QItemSelection&, const QItemSelection&) {
  QTimer::singleShot(0, [&]() {
    qDebug() << "selected =" << tableWidget->selectionModel()->selectedIndexes() << endl;
  });
});
cbuchart
  • 10,847
  • 9
  • 53
  • 93
  • What has *current* item todo with it *selected* item? Anyway, if the signal is emitted before removal this makes the situation even worse. Purpose of the signal is to get informed about each selection change, do you agree? In my example I would have expected either ONE signal (selected row 0 after removal) or NO signal (still row 0 after removal) or TWO signals (row 1 before then 0 after). The current way does not make sense to me. Currently the one and only signal that arrives, tells me row 1 (before removal) which is not the final state... – Silicomancer Jun 23 '17 at 20:54
  • ... How can I ever get informed about the actual, final selection (row 0)? I can track selection manually, by retrieving the selection whenever I change the model and discard the signal result (which is what the Qt examples do). But the signal should not make that necessary. Currently the signal says "hey, maybe the selection changed to row x or maybe x is wrong. go and find out by yourself what happened since you can't know if I am telling the truth". – Silicomancer Jun 23 '17 at 20:54
  • I've updated the answer with a solution I use in similar cases. Hope it can be useful for you. – cbuchart Jun 25 '17 at 23:01