QComboBox item model with ItemIsUserCheckable and ItemIsSelectable
-
Hello,
Maybe this is not possible (at least that simple) but I'll ask anyway.
When I do this:
cmbSelector=new QComboBox(this); QStandardItemModel *modItemModel=qobject_cast<QStandardItemModel*>(cmbSelector->model()); ... for(int iK=0;iK< ... ) { cmbSelector->addItem(...); ... modItemModel->item(iK)->setFlags(Qt::ItemFlag::ItemIsEnabled| Qt::ItemFlag::ItemIsSelectable); modItemModel->item(iK)->setData(Qt::CheckState::Unchecked, Qt::ItemDataRole::CheckStateRole); }
The resulting combo box is shown with check boxes, but unable to be checked.
When I replace the setFlags() call with this:
modItemModel->item(iK)->setFlags(Qt::ItemFlag::ItemIsEnabled| Qt::ItemFlag::ItemIsUserCheckable);
The combo box items can be checked this time, but of course, it's not possible to set any index because the items are not selectable.
I know this looks stupid but, how can I have the mixed functionality? I.e., being able to select individual items (and changing the combo box's index and current text accordingly), but also being able to check one or more?
The ultimate usage for that is something like this:
[ ] option 1 [ ] option 2 ... [ ] option n Done
So if the user picks a single option (clicking on the option text), the combo box closes and shows that option selected. And If the user clicks over some check boxes and then hits "Done", the combo box closes and whatever happens next (with the selection), is handled by me.
Is that possible? And if it isn't, what's the simplest way of implementing this behavior?
Thanks for your help. ;)
-
@Alvein
I don't know if this way will distinguish between clicking on the checkbox vs on the text, but since you want both selectable & checkable to be allowed have you tried:modItemModel->item(iK)->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsUserCheckable);
If that does not work/distinguish, it sounds like you would need to look at the mouse click event and see whether it's on the checkbox or the text yourself.
I don't know whether such behaviour is advisable. If user expects click on the text to be same as click on checkbox (e.g. HTML checkboxes work this way), you may want to think of a alternative interface.
-
@JonB ItemIsSelectable seems to have precedence over ItemIsUserCheckable. So, mixing the two flags, does not show the checkboxes.
The problem with not using ItemIsSelectable is that the clicks over the items text are not being registered. So there's no way of knowing when the click wasn't over the check boxes. BTW, I don't think this behavior is so rare. I'm pretty sure a lot of applications register clicks on a check box text as if they were in the check box itself.
It's a pity that it's not possible to add widgets inside combo boxes. I'll have to either implement a drop-down list widget or forget the dual behavior and just add a selectable "Done" item at the end of the list (and annoy the user in the process, because of the lack of clicks registering over the other items text).
-
@Alvein said in QComboBox item model with ItemIsUserCheckable and ItemIsSelectable:
So, mixing the two flags, does not show the checkboxes.
I will have to take your word for it. But I did find Google examples out there using both flags, and nobody mentioned what you say.
When using "native" comboboxes on platforms I don't think they allow checkboxes, I'm used to they just offer a single-label list to single-select from, though I could be wrong. I don't know how that relates to the Qt
QComboBox
ones. -
@JonB Sorry about the misunderstanding I've caused. I don't know what happened yesterday, but I've been doing a bunch of edits, so there was probably one which behaved that way.
I rechecked, and the truth is, the items actually become selectable AND the check boxes are shown. However, Only the selectable part works. Clicking over the check boxes just picks the selected item as if the check box didn't exist in first place.
Anyway, does this mean I'm close to find a solution or this is just a dead end and those check boxes won't be functional ever without doing a way more complex thing?
TBH, I've not found a single solution doing this that simple.
-
@JonB I've noticed that pressing space when a given combo box option is selected, the respective check box gets checked/unchecked. This gives me the impression that this functionality is half baked.
Anyway, I've used an event filter to deal with the clicks and keep the combo box unfolded until the "done" option is selected, where it behaves normally.
cmbSelector=new QComboBox(this); QStandardItemModel *modItemModel=qobject_cast<QStandardItemModel*>(cmbSelector->model()); ... for(int iK=0;iK< ... ) { cmbSelector->addItem(...); ... modItemModel->item(iK)->setFlags(Qt::ItemFlag::ItemIsEnabled| Qt::ItemFlag::ItemIsSelectable| Qt::ItemFlag::ItemIsUserCheckable); modItemModel->item(iK)->setData(Qt::CheckState::Unchecked, Qt::ItemDataRole::CheckStateRole); } cmbSelector->addItem("done"); cmbSelector->setCurrentIndex(-1); cmbSelector->view()->viewport()->installEventFilter(this);
My current event filter is something like this:
bool MyWindow::eventFilter(QObject *objO,QEvent *evnE) { if(evnE->type()==QEvent::Type::MouseButtonRelease) { QMouseEvent *evnM=static_cast<QMouseEvent *>(evnE); QWidget *wgtSender=qobject_cast<QWidget *>(objO); QComboBox *cmbSender=qobject_cast<QComboBox *>(wgtSender-> parentWidget()-> parentWidget()-> parentWidget()); QStandardItemModel *modSender=qobject_cast<QStandardItemModel*>(cmbSender->model()); int iRow=cmbSender->view()->indexAt(evnM->pos()).row(); if(iRow<0) return true; else if(iRow<cmbSender->count()-1) { if(modSender->item(iRow)->checkState()==Qt::CheckState::Checked) modSender->item(iRow)->setCheckState(Qt::CheckState::Unchecked); else modSender->item(iRow)->setCheckState(Qt::CheckState::Checked); return true; } } return QObject::eventFilter(objO,evnE); }
It's simple enough, but I totally dislike the way I'm reaching the combo box widget.
Isn't there a nicer way to do this? Considering the combo box is created dynamically, of course.
I am not very convinced about the way I reach the clicked item either.
The code is fantastically short, but the important lines look terribly complicated. :/
Opinions?
...
Well, so far, everything works, but I'll need to tell apart the clicks over the check boxes and item texts. ATM, no idea about how to proceed.
Thanks for reading this excessive brainstorming haha.
-
@Alvein
I don't know the correct answer for this.However, may I just say: my thought is if it involves this much code, is it worth/right doing this?
My understanding of what you are saying is:
- You have a dropdown of multiple items, with checkboxes against each.
- The user can individually check/uncheck items my clicking on the checkboxes.
- But if the user chooses to click on the text of an item, then that item gets selected (toggled?) and the dropdown closes.
Is there anything else which has such an interface? Does clicking on a text cause all other previously-checked boxes to be unchecked? Is this an interface for selecting one item or multiple items? Does only clicking "Done" take into account multiple checked selections, while clicking a text behaves differently? I admit I haven;t seen the interface, but if it's unusual it may confuse the user and it's no wonder you're finding it not easy to implement.
"K.I.S.S"!
-
Something like this?
#include <QVBoxLayout> #include <QStandardItemModel> #include <QApplication> #include <QComboBox> #include <QAbstractItemView> int main(int argc, char *argv[]) { QApplication a(argc, argv); QWidget mainWid; QStandardItemModel* model = new QStandardItemModel(&mainWid); model->insertColumn(0); model->insertRows(0,3); for(int rowIter = 0, maxRow = model->rowCount();rowIter<maxRow;++rowIter){ const QModelIndex idx = model->index(rowIter,0); model->setData(idx,QStringLiteral("Option %1").arg(rowIter+1)); model->setData(idx,Qt::Unchecked,Qt::CheckStateRole); model->setData(idx,Qt::Unchecked,Qt::UserRole + Qt::CheckStateRole); model->itemFromIndex(idx)->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); } QComboBox* mixedCombo = new QComboBox(&mainWid); mixedCombo->setModel(model); QObject::connect(mixedCombo->view(),&QAbstractItemView::clicked,[mixedCombo](const QModelIndex& idx)->void{ if(idx.parent().isValid() || idx.data(Qt::CheckStateRole)!=idx.data(Qt::UserRole + Qt::CheckStateRole)) return; mixedCombo->hidePopup(); mixedCombo->setCurrentIndex(idx.row()); }); QObject::connect(model,&QAbstractItemModel::dataChanged,model,[model](const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles){ if(!roles.contains(Qt::CheckStateRole) || bottomRight.parent().isValid() || topLeft.parent().isValid()) return; for(int rowI = topLeft.row();rowI<=bottomRight.row();++rowI){ for(int colI = topLeft.column();colI<=bottomRight.column();++colI){ const QModelIndex idx = model->index(rowI,colI); model->setData(idx,idx.data(Qt::CheckStateRole),Qt::UserRole + Qt::CheckStateRole); } } },Qt::QueuedConnection); QVBoxLayout* mainLay = new QVBoxLayout(&mainWid); mainLay->addWidget(mixedCombo); mainWid.show(); return a.exec(); }
-
-
@VRonin
Fair enough! But according to the interface I think the OP is proposing, and I don't know if it applies to yours, the behaviour is:- I carefully spend ages clicking on checkboxes to switch multiple items on.
- I go to click one more item's checkbox, but I accidentally click on the text instead of the checkbox.
- If I understand OP/you right, at this point it throws away all the checks I have done and only checks/returns that single item.
- This is where I throw the software out, because I'm so frustrated at the mis-click behaviour!?
-
@VRonin Jesus, that code looks so short, yet so complex that I think I'd need a 6-month training to understand everything. The problem now is that I need to customize that functionality, like adding some no-check box options, etc. I'll see what can I do. Thank you very much!
@JonB This is how I envision the thing:
- Users click to drop down the combo box
- If they want to pick only one option, they click on the wanted item's text to confirm the action.
- If they want to multi-select, they click on the wanted items check boxes, and then click on a special option (like "done" or any word that suits better for multi-selection), which confirms the action.
- The combo box value is set, either simple or special, and the list is rolled back up.
- The combo box has memory of the multi-selection in case the user wants to edit something.
The selection would never disappear because of user mistakes. But now that you mention it, it's a good feature to have another special option for clearing all (and probably yet another one to selecting all).
In the real application where this kind of combo box is planned to be used, the multi-selection is not very common. So users will want to just click and go, caring about the check boxes when it's actually required. It's a good trade I guess.
...
Now that I have your attention, my updated event filter looks like this:
bool MyWindow::eventFilter(QObject *objO,QEvent *evnE) { if(evnE->type()==QEvent::Type::MouseButtonRelease) { QMouseEvent *evnM=static_cast<QMouseEvent *>(evnE); QWidget *wgtSender=qobject_cast<QWidget *>(objO); QComboBox *cmbSender=qobject_cast<QComboBox *>(wgtSender-> parentWidget()-> parentWidget()-> parentWidget()); QStandardItemModel *modSender=qobject_cast<QStandardItemModel*>(cmbSender->model()); QList<QBoxLayout *> llTest=cmbSender->findChildren<QBoxLayout *>(); int iRow=cmbSender->view()->indexAt(evnM->pos()).row(), iCheckableWidth=llTest.last()->geometry().width(); if(iRow<0) // Ignores clicks over empty space (RARE but possible) return true; else if(iRow<cmbSender->count()-1) // The special option is handled as usual if(evnM->pos().x()<iCheckableWidth) { // Clicks outside the check box area are handled as usual if(evnM->button()==Qt::MouseButton::LeftButton) { // Right-clicks over the check boxes are ignored if(modSender->item(iRow)->checkState()==Qt::CheckState::Checked) modSender->item(iRow)->setCheckState(Qt::CheckState::Unchecked); else modSender->item(iRow)->setCheckState(Qt::CheckState::Checked); } return true; } } return QObject::eventFilter(objO,evnE); }
So, two questions:
#1. Is there a way to get to the combo box widget simpler than my parentWidget() galore?
Remember that I install the filter in the combo box's view()->viewport().#2. The way I get the check boxes area width:
QList<QBoxLayout *> llTest=cmbSender->findChildren<QBoxLayout *>();
and then
iCheckableWidth=llTest.last()->geometry().width();
isn't like overkill?
I was unable to find any check box inside the modified combo box children.
Any method of getting the check boxes width?Thanks again for your help. This discussion have been very helpful. =)