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.
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:
selectionModel()
/ setSelectionModel(QItemSelectionModel*)
For that last one, spare a minute to carefully read what the documentation says, that is:
Note that, if you call setModel() after this function, the given selectionModel will be replaced by one created by the view.
It is up to the application to delete the old selection model if it is no longer needed [...]
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:
To limit the lifetime of the memory leak:
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.
mySelectionModel->setParent(nullptr);
either.To limit the size of the memory leak:
You must make sure not to call setModel()
more than once per view:
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();
QIdentityProxyModel
.setModel
.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:
QListView
, QTableView
, QTreeView
over their QxxxxxWidget
counterparts.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.QStandardItemModel
(note QxxxxxWidget
classes all use their own internal standalone model) if they can.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.