QCheckBox and QDataWidgetMapper
-
I am trying to drive a group of QCheckBox using model driven approach using QDataWidgetMapper. However I encounter several issues, for starters the data method never get called:
QVariant CheckboxModel::data(const QModelIndex& index, int role) const
therefore I am unable to set the enabled/disabled state of the checkbox
Furthermore, in the method:
bool CheckboxModel::setData(const QModelIndex& index, const QVariant& value, int role)
when I do check for the state of the checkbox like :
if (role == Qt::EditRole || role == Qt::CheckStateRole) { if (value.isValid() && !value.isNull()) { Qt::CheckState cb_qt_state = static_cast<Qt::CheckState>(value.toUInt()); if (Qt::CheckState::Checked == cb_qt_state) { m_cbStates->SetState(static_cast<CheckboxID>(index.column()), CheckboxState::On); } if (Qt::CheckState::Unchecked == cb_qt_state) { m_cbStates->SetState(static_cast<CheckboxID>(index.column()), CheckboxState::Off); } if (Qt::CheckState::PartiallyChecked == cb_qt_state) { m_cbStates->SetState(static_cast<CheckboxID>(index.column()), CheckboxState::On); } } }it is always in state Qt::CheckState::PartiallyChecked when it should be fully checked, if looking at the GUI.
Any suggestions on how to achieve the desired behavior using QDataWidgetMapper or some example of stand alone checkboxes not part of table/list views?
-
I am trying to drive a group of QCheckBox using model driven approach using QDataWidgetMapper. However I encounter several issues, for starters the data method never get called:
QVariant CheckboxModel::data(const QModelIndex& index, int role) const
therefore I am unable to set the enabled/disabled state of the checkbox
Furthermore, in the method:
bool CheckboxModel::setData(const QModelIndex& index, const QVariant& value, int role)
when I do check for the state of the checkbox like :
if (role == Qt::EditRole || role == Qt::CheckStateRole) { if (value.isValid() && !value.isNull()) { Qt::CheckState cb_qt_state = static_cast<Qt::CheckState>(value.toUInt()); if (Qt::CheckState::Checked == cb_qt_state) { m_cbStates->SetState(static_cast<CheckboxID>(index.column()), CheckboxState::On); } if (Qt::CheckState::Unchecked == cb_qt_state) { m_cbStates->SetState(static_cast<CheckboxID>(index.column()), CheckboxState::Off); } if (Qt::CheckState::PartiallyChecked == cb_qt_state) { m_cbStates->SetState(static_cast<CheckboxID>(index.column()), CheckboxState::On); } } }it is always in state Qt::CheckState::PartiallyChecked when it should be fully checked, if looking at the GUI.
Any suggestions on how to achieve the desired behavior using QDataWidgetMapper or some example of stand alone checkboxes not part of table/list views?
@pip010 By default the QDataWidgetMapper uses the widget's userProperty, and in the case of the QCheckBox it is the "checked" property that returns a boolean causing conflict with Qt::CheckState. The solution is to create a QProperty based on Qt::CheckState.
#include <QApplication> #include <QCheckBox> #include <QDataWidgetMapper> #include <QGridLayout> #include <QStandardItemModel> #include <QTableView> class CheckBox: public QCheckBox{ Q_OBJECT Q_PROPERTY(Qt::CheckState checkState READ checkState WRITE setCheckState NOTIFY checkStateChanged) public: CheckBox(QWidget *parent=nullptr): QCheckBox(parent){ connect(this, &QCheckBox::stateChanged, this, &CheckBox::checkStateChanged); } Q_SIGNALS: void checkStateChanged(); }; class StandardItemModel: public QStandardItemModel{ public: using QStandardItemModel::QStandardItemModel; QVariant data(const QModelIndex &index, int role) const{ return QStandardItemModel::data(index, role == Qt::EditRole ? Qt::CheckStateRole: role); } bool setData(const QModelIndex &index, const QVariant &value, int role){ return QStandardItemModel::setData(index, value, role == Qt::EditRole ? Qt::CheckStateRole: role); } }; int main(int argc, char *argv[]) { QApplication a(argc, argv); StandardItemModel model(0, 5); for(int row =0; row < 6; row++){ for(int column = 0; column < model.columnCount(); column++){ QStandardItem *item = new QStandardItem; item->setCheckable(true); item->setCheckState(((row + column) % 2 == 0) ? Qt::Checked: Qt::Unchecked); item->setUserTristate(true); model.setItem(row, column, item); } } QTableView *view = new QTableView; view->setSelectionBehavior(QAbstractItemView::SelectRows); view->setSelectionMode(QAbstractItemView::SingleSelection); view->setModel(&model); QDataWidgetMapper mapper; mapper.setModel(&model); QObject::connect(view->selectionModel(), &QItemSelectionModel::currentChanged, &mapper,[&mapper](const QModelIndex & current, const QModelIndex &){ mapper.setCurrentIndex(current.row()); }); QWidget widget; QGridLayout *lay = new QGridLayout(&widget); lay->addWidget(view, 0, 0, 1, model.columnCount()); for(int i=0; i < model.columnCount(); i++){ CheckBox *checkbox = new CheckBox; checkbox->setTristate(true); lay->addWidget(checkbox, 1, i); mapper.addMapping(checkbox, i, "checkState"); QObject::connect(checkbox, &CheckBox::stateChanged, &mapper, &QDataWidgetMapper::submit); } widget.show(); return a.exec(); } #include "main.moc" -
Thanks @eyllanesc , I think understand what you mean by 'QDataWidgetMapper uses the widget's userProperty' but I am super surprised that the default QCheckBox is not working out of the box. Creating a custom checkbox will drive working through the designer (GUI) impossible and I really appreciate it.
However, it is still unclear how I am supposed to drive the enabled/disabled state for each checkbox. Btw, I am not using any view (QTableView) and using QAbstractTableModel for the model, treating each column data for the state of a checkbox.
class CheckboxModel : public QAbstractTableModel { Q_OBJECT nn::ICheckBoxStates* m_cbStates; public: using Columns = nn::ISurfacesCollectionGUI::Columns; public: CheckboxModel(QObject* parent = 0); void Bind(nn::ICheckBoxStates&); Qt::ItemFlags flags(const QModelIndex& index) const override; //QModelIndex index(int, int, const QModelIndex&) const override; //QModelIndex parent(const QModelIndex&) const override; int rowCount(const QModelIndex&) const override; int columnCount(const QModelIndex&) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; //QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; };but again, my problem is that neither the data method get called when the GUI shows nor the flags method which should determine the enabled/disabled state of the checkbox. So is there a way to drive this through the model and not resort to code hacks?
-
here is the cpp file:
CheckboxModel::CheckboxModel( QObject* parent) : QAbstractTableModel(parent), m_cbStates(nullptr) { } void CheckboxModel::Bind(nn::ICheckBoxStates& p_cbStates) { beginInsertRows(QModelIndex(), 0, 1); m_cbStates = &p_cbStates; endInsertRows(); //emit dataChanged(createIndex(1,0), createIndex(1, static_cast<int>(nn::CheckboxID::COUNT))); m_cbStates->Connect< CheckBoxStatesEvents::StateChanged >([this](CheckboxID p_id, CheckboxState p_val) { //auto idx1 = createIndex(0, 0); auto idx = createIndex(0, static_cast<int>(p_id)); emit dataChanged(idx, idx); }); } Qt::ItemFlags CheckboxModel::flags(const QModelIndex& index) const { if (!index.isValid()) return Qt::ItemIsEnabled; return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; } //QModelIndex CheckboxModel::index(int r, int c, const QModelIndex&) const //{ // return createIndex(r, c); //} // //QModelIndex CheckboxModel::parent(const QModelIndex& p) const //{ // return p; //} int CheckboxModel::rowCount(const QModelIndex&) const { return 1; } int CheckboxModel::columnCount(const QModelIndex&) const { return static_cast<int>(nn::CheckboxID::COUNT); } QVariant CheckboxModel::data(const QModelIndex& index, int role) const { if (index.column() < static_cast<size_t>(CheckboxID::COUNT)) { if (role == Qt::DisplayRole || role == Qt::EditRole) { CheckboxState val = m_cbStates->GetState(static_cast<CheckboxID>(index.row())); return val == CheckboxState::On ? QVariant::fromValue(1) : QVariant::fromValue(0); } } return QVariant(); } bool CheckboxModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (role == Qt::EditRole || role == Qt::CheckStateRole) { if (value.isValid() && !value.isNull()) { auto vvv = value.toInt(); Qt::CheckState cb_qt_state = static_cast<Qt::CheckState>(value.toUInt()); if (Qt::CheckState::Checked == cb_qt_state) { m_cbStates->SetState(static_cast<CheckboxID>(index.column()), CheckboxState::On); } if (Qt::CheckState::Unchecked == cb_qt_state) { m_cbStates->SetState(static_cast<CheckboxID>(index.column()), CheckboxState::Off); } if (Qt::CheckState::PartiallyChecked == cb_qt_state) { m_cbStates->SetState(static_cast<CheckboxID>(index.column()), CheckboxState::On); } } } return true; } -
Thanks @eyllanesc , I think understand what you mean by 'QDataWidgetMapper uses the widget's userProperty' but I am super surprised that the default QCheckBox is not working out of the box. Creating a custom checkbox will drive working through the designer (GUI) impossible and I really appreciate it.
However, it is still unclear how I am supposed to drive the enabled/disabled state for each checkbox. Btw, I am not using any view (QTableView) and using QAbstractTableModel for the model, treating each column data for the state of a checkbox.
class CheckboxModel : public QAbstractTableModel { Q_OBJECT nn::ICheckBoxStates* m_cbStates; public: using Columns = nn::ISurfacesCollectionGUI::Columns; public: CheckboxModel(QObject* parent = 0); void Bind(nn::ICheckBoxStates&); Qt::ItemFlags flags(const QModelIndex& index) const override; //QModelIndex index(int, int, const QModelIndex&) const override; //QModelIndex parent(const QModelIndex&) const override; int rowCount(const QModelIndex&) const override; int columnCount(const QModelIndex&) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; //QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; };but again, my problem is that neither the data method get called when the GUI shows nor the flags method which should determine the enabled/disabled state of the checkbox. So is there a way to drive this through the model and not resort to code hacks?
@pip010 1) in my demo I use the QTableView only to visualize the change, 2) The QCheckbox by default handles the property checked (true, false) but not checkState. so if you want to use my custom QCheckbox then create a new file (.h and .cpp) and copy my code there, and then promote it: https://doc.qt.io/qt-5/designer-using-custom-widgets.html
-
@eyllanesc thanks for the tip, will look into it. How about the more general question about driving the checkboxes from the model alone? Why is my data and flags methods never called?
-
@eyllanesc thanks for the tip, will look into it. How about the more general question about driving the checkboxes from the model alone? Why is my data and flags methods never called?
-
OK, as I found out you CANNOT control the state of checkbox via model. Quite unfortunate. On the previous point about how to handle the 3state of QCheckBox (why a 3state to begin with) the solution seems a complete overkill, again every unfortunate that QDataWidgetMapper does not support it by default. Unlike QCombobox, similar issue, where one can opt for banding different property:
dataWidgetMapper->addMapping(combo, columnInTheModel, "currentIndex");
such constructs seems not possible with QCheckbox.