QListWidget with checkboxes - checking more than one row at a time
-
I think the challenge I'm having with that is differentiating between when the user clicks on the checkbox and when the user clicks anywhere in the QListView.
I have tried subclassing QListView and reimplementing mouseReleaseEvent, but it never seems to be called:
class MassCheckView : public QListView { Q_OBJECT public: explicit MassCheckView(QWidget* parent = nullptr) : QListView(parent) {} void mouseReleaseEvent(QMouseEvent *event) override; };
--
void MassCheckView::mouseReleaseEvent(QMouseEvent *event) { auto under_mouse = childAt(event->position()); if (under_mouse) { const auto indices = selectionModel()->selectedIndexes(); const auto first_checked = model()->data(indices.at(0), Qt::CheckStateRole).value<Qt::CheckState>(); for (const auto& idx : indices) { model()->setData(model()->index(idx.row(), idx.column(), QModelIndex()), first_checked ? Qt::Unchecked : Qt::Checked, Qt::CheckStateRole); } return; } QListView::mouseReleaseEvent(event); }
-
Now you're using a QListView instead a QListWidget... you have to monitor your model to see when the checkstate changed then.
-
-
Oh shoot; that's my mistake -- I've meant QListView for this whole post.
How might I go about that? I can connect something to dataChanged, but as far as I'm aware it will still only change one index at a time regardless of how many rows are selected.
@Jackmill said in QListWidget with checkboxes - checking more than one row at a time:
far as I'm aware it will still only change one index at a time regardless of how many rows are selected.
Yes, you get the dataChanged() signal, then lookup the selection and set the rest.
-
I see. This seems to work:
connect(model, &QAbstractListModel::dataChanged, this, [table, model](const QModelIndex& topLeft, const QModelIndex& bottomRight) { const auto selection = table->selectionModel()->selectedIndexes(); if (selection.isEmpty()) { return; } const auto check = model->data(topLeft, Qt::CheckStateRole).value<Qt::CheckState>(); model->setDataChangedEnabled(false); for (const auto& idx : selection) { if (!idx.isValid() || idx == topLeft) { continue; } model->setData(idx, check ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); } model->setDataChangedEnabled(true); });
Enabling and disabling the emission of the dataChanged like this seems a little clunky but without it there's an endless loop and segfault.
-
I see. This seems to work:
connect(model, &QAbstractListModel::dataChanged, this, [table, model](const QModelIndex& topLeft, const QModelIndex& bottomRight) { const auto selection = table->selectionModel()->selectedIndexes(); if (selection.isEmpty()) { return; } const auto check = model->data(topLeft, Qt::CheckStateRole).value<Qt::CheckState>(); model->setDataChangedEnabled(false); for (const auto& idx : selection) { if (!idx.isValid() || idx == topLeft) { continue; } model->setData(idx, check ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); } model->setDataChangedEnabled(true); });
Enabling and disabling the emission of the dataChanged like this seems a little clunky but without it there's an endless loop and segfault.
@Jackmill said in QListWidget with checkboxes - checking more than one row at a time:
Enabling and disabling the emission of the dataChanged like this seems a little clunky but without it there's an endless loop and segfault.
Blocking the dataChanged signal means that the view won't know that a particular cell needs to be redrawn. A flag or list of indexes to be modified could accomplish the same goal while leaving view management intact.
-
I see. This seems to work:
connect(model, &QAbstractListModel::dataChanged, this, [table, model](const QModelIndex& topLeft, const QModelIndex& bottomRight) { const auto selection = table->selectionModel()->selectedIndexes(); if (selection.isEmpty()) { return; } const auto check = model->data(topLeft, Qt::CheckStateRole).value<Qt::CheckState>(); model->setDataChangedEnabled(false); for (const auto& idx : selection) { if (!idx.isValid() || idx == topLeft) { continue; } model->setData(idx, check ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); } model->setDataChangedEnabled(true); });
Enabling and disabling the emission of the dataChanged like this seems a little clunky but without it there's an endless loop and segfault.
@Jackmill said in QListWidget with checkboxes - checking more than one row at a time:
check ? Qt::Checked
And this is wrong - see https://doc.qt.io/qt-6/qt.html#CheckState-enum
simply passcheck
Also notice that you have to map the indexes as soon as you have a proxy model in between.
-
@Jackmill said in QListWidget with checkboxes - checking more than one row at a time:
Enabling and disabling the emission of the dataChanged like this seems a little clunky but without it there's an endless loop and segfault.
Blocking the dataChanged signal means that the view won't know that a particular cell needs to be redrawn. A flag or list of indexes to be modified could accomplish the same goal while leaving view management intact.
@jeremy_k said in QListWidget with checkboxes - checking more than one row at a time:
A flag or list of indexes to be modified could accomplish the same goal while leaving view management intact.
How might I do this? I'm having trouble thinking of a way to call setData without causing an endless loop.
-
Just remeber which you already have set and that you're currently setting the other checkboxes.
-
@Jackmill said in QListWidget with checkboxes - checking more than one row at a time:
@jeremy_k said in QListWidget with checkboxes - checking more than one row at a time:
A flag or list of indexes to be modified could accomplish the same goal while leaving view management intact.
How might I do this? I'm having trouble thinking of a way to call setData without causing an endless loop.
void onDataChanged(const QModelIndex topLeft, const QModelIndex bottomRight, const QList<int> &roles) { static bool updating = false; if (roles.contains(ItemDataRole::CheckStateRole) && !updating) { updating = true; auto value = topLeft.data(ItemDataRole::CheckStateRole); for (auto index : selectionModel.selectedIndexes()) model->setData(index, ItemDataRole::CheckStateRole, value); updating = false; } }
The code could also disconnect this (and only this) slot from the signal prior to the loop, and reconnect it at the end.