Search code examples
c++qtqt5qtablewidget

Shared selection for several QTableWidgets


I'm working in Qt 5.6.1. There are several QTableWidget tables, in theory they represent one table - they are located closely, and the data on the rows is interconnected, but they should be presented as separate widgets. Their selection flag is SelectRows, and the ExtendedSelection mod. How can I make a general selection for them? Let's say you selected several rows of the first table and selected the same rows in others. The selection should be removed accordingly. Tables have different numbers of columns.

I tried to handle the selectionChanged signal from the selectionModel tables in this way:

void wSpisokActive::selectionChangedMain(const QItemSelection &selected, const QItemSelection &deselected)
{
    permission_to_changed_main_ = false; // flag to exit a similar slot in another table

    if (permission_to_changed_monitoring_) { // select row
        QModelIndexList selected_list = selected.indexes();
        for (QModelIndex model_index: selected_list) {
            ui->tbMonitoring->selectRow(model_index.row());
        }

        QModelIndexList deselected_list = deselected.indexes();
        int last_row = -1;
        for (QModelIndex model_index: deselected_list) { // deselect row
            if (last_row == model_index.row()) continue;
            last_row = model_index.row();
            for (int i = 0; i < ui->tbMonitoring->columnCount(); ++i) {
                ui->tbMonitoring->selectionModel()->select(ui->tbMonitoring->model()->index(last_row, i), QItemSelectionModel::Deselect);
            }
        }
    }

    permission_to_changed_main_ = true;
}

But this way selection via Ctrl is processed incorrectly. Also, when selecting several rows, only the last one is highlighted - I tried setting ui->tbMonitoring->setSelectionMode(QAbstractItemView::MultiSelection); before selecting rows, and after ui->tbMonitoring->setSelectionMode(QAbstractItemView::ExtendedSelection);. But it didn't help.


Solution

  • As explained in this help page (my emphasis):

    Information about the items selected in a view is stored in an instance of the QItemSelectionModel class. This maintains model indexes for items in a single model, and is independent of any views. Since there can be many views onto a model, it is possible to share selections between views, allowing applications to show multiple views in a consistent way.

    The methods you are interested in come in pairs:

    The resulting code looks like (assuming this is on your main thread):

    auto widgetModel    = myTableWidget->model();
    auto widgetSelModel = myTableWidget->selectionModel();
    for (auto view : { myTableView1, myTableView2, myTableView3 }) {
        view->setModel(widgetModel);
        delete view->selectionModel();
        view->setSelectionModel(widgetSelModel);
    }
    

    Edit: About QTBUG-49966, pointed out by @musicamante in comments.

    To cut the chase: YES, 1 selection model per view (rather, 1 selection model for the header view of the view) will remain in memory until the view is deleted, along with its header.
    However, if you do nothing more than what I presented above, you will only have 1 selection model "leaked" per view (which should be a very small number) which get deleted when you delete the view.

    If you are implementing a window where setModel gets called any number of time (e.g. see the scenario described by @musicamante in comments), there are several things you can do as a mitigation measure:

    1. To limit the lifetime of the memory leak:

      • AFAICT, all the selection models created by Qt have a parent set.
        If you make sure widgets are deleted (the easiest way to ensure everything gets cleared when you close a window is by calling myWindow->setAttribute(Qt::WA_DeleteOnClose);), these unused selection models will not outlive your views.

      If you never delete the views/widgets, then the memory leak is on you (you never delete the objects you create) rather than because of the unused selection models created by Qt.

      • Never create a selection model by yourself, at least without setting its parent. I hope it is obvious but never call mySelectionModel->setParent(nullptr); either.
        As long as all selection models have a view as their parent, they will get deleted with the view.
    2. To limit the size of the memory leak:
      You must make sure not to call setModel() more than once per view:

      • If your model supports being reset, then use this possibility.
        Example with QSqlTableModel: changing the SQL table loaded should not be done by creating a new model to substitute it to the existing one. Instead, do:
      myExistingSqlTableModel->setTable(newlySelectedTable);
      myExistingSqlTableModel->select();
      
      • In the event where you do have to change the model (e.g. because the model does not support being reset or because the new model is not of the same type as the existing one), you still have a way out thanks to QIdentityProxyModel.
        The purpose of a proxy model is not supposed to be this but it comes in handy here. The model can be changed on a view (at least, this is what it visually looks like) without calling setModel.
        It is intialized by doing:
      QIdentityProxyModel* proxyModel = new QIdentityProxyModel(myView);
      proxyModel->setSourceModel(myModel);
      myView->setModel(proxyModel);
      

      Then, attaching a new model to your view can be done:

      if (auto proxyModel = dynamic_cast<QAbstractProxyModel*>(myView->model()); proxyModel) {
          if (proxyModel->sourceModel())
              delete proxyModel->sourceModel();
          proxyModel->setSourceModel(myNewModel);
      }
      

      And voilà! no more calling setModel several time on the same view.


    On a related note, if I can make a quick (optional) recommendation:

    • Experienced Qt developers tend to favor QListView, QTableView, QTreeView over their QxxxxxWidget counterparts.
      While QTableWidget is a class of choice for beginners, simplifying a lot of the required work, it sets such limitations on flexibility that the downsides far outweigh the advantages.
    • Experienced Qt developers also tend to avoid QStandardItemModel (note QxxxxxWidget classes all use their own internal standalone model) if they can.
      If your application already has some internal structure to store the data shown on screen, it is usually more efficient to map it to a custom subclass of QAbstractItemModel rather than copy it into a standalone model such as QStandardItemModel.

    The sooner you get used to creating your own model classes, the easier your life will become down the line.
    In your case, you are working with tables, you can start your learning journey with QAbstractTableModel. It should be accessible enough as a starting point and generally speaking easier to subclass compared to QAbstractItemModel.

    The above will work with your current code so feel free to ignore this advice.