QComboBox delegate: Best way to show the popup menu immediately?
-
Hi all,
I've subclassed QStyledItemDelegate to provide a QComboBox editor (tutorial: http://qt-project.org/wiki/Combo_Boxes_in_Item_Views )
The problem is, 3 clicks are needed to bring up the popup menu. We can reduce this to 2 clicks if the popup menu is shown as soon as the editor is created and shown.
The cleanest way I can think of doing this is to use a single-shot timer to call showPopup() after creating the QComboBox. The timer is required because we need to wait for the QComboBox to be positioned + shown before calling showPopup(), or else the menu will pop up in the wrong place.
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override { Q_UNUSED(option); Q_UNUSED(index); auto cb = new QComboBox(parent); cb->addItems({"A", "B", "C"}); // ASSUMPTION: The QComboBox will be shown in the correct // location before control returns to the event loop, thus // ensuring that that the timer will fire at the right time. QTimer::singleShot(0, [cb] { cb->showPopup(); }); return cb; }
Is there a nicer way to do this? I'm also looking for a way to modify the behaviour of Qt's default delegate, which creates a QComboBox if QAbstractItemModel::data() returns a Boolean value. It would be great if I didn't have to implement my own delegate just for this.
Thanks in advance!
-
I don't know if it's nicer (has some ugliness to it too) but another way is to install the delegate as an event filter for the created widget and listen for FocusIn event.The upside is that it doesn't make any event loop related assumptions:
bool ComboBoxItemDelegate::eventFilter(QObject *src, QEvent *evt) { if(evt->type() == QEvent::FocusIn) { auto cb = qobject_cast<QComboBox*>(src); if(cb) { cb->showPopup(); //important to handle it only the first time, otherwise will result in //focus glitches cb->removeEventFilter(this); } } return QStyledItemDelegate::eventFilter(src, evt); }
Now the ugly part is that the createEditor() method is const so to install the filter you need a const_cast. Oh well, that's what they're for I guess :/
... auto cb = new QComboBox(parent); cb->installEventFilter(const_cast<ComboBoxItemDelegate*>(this)); cb->addItems({"A", "B", "C"}); ...
As for the second question - inside
createEditor()
you could call the base implementation if needed and treat the resulting widget similarly. I don't know how to do it without a custom delegate, sorry. -
Thanks Chris!
Your event filter technique looks useful for handling other kinds of interactions with editors too (e.g. dismissing the editor when the mouse leaves the cell). I'll play around with it.
It looks like I'll have to do some extra work no matter what, but we usually survive that :D
-
@Chris-Kawa First of all, thanks for your solution :D.
I am not sure if this is due to changes in version of Qt, but I had the same problem and it seems the ugly bit is not needed, because the delegate is installed as event filter in the editor by default somewhere along the pipeline.
Additionally, I had to modify your solution as I also wanted to have the combo box commit when an item is selected (and not after an extra hit on Enter). To achieve all this I did:
QWidget *MyItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { ... QComboBox *combo_box = new QComboBox(parent); ... // Make sure data is commited when an item is chosen in the combo box connect( combo_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), [=]() { // This event will be captured by the delegate eventFilter() QApplication::sendEvent( combo_box, new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier)); }); ... } bool MyItemDelegate::eventFilter(QObject *object, QEvent *event) { // Show combo box popup when the box gains focus if (event->type() == QEvent::FocusIn) { auto *combo_box = qobject_cast<QComboBox *>(object); auto *focus_event = dynamic_cast<QFocusEvent *>(event); if (combo_box && focus_event && // Do not consider focus gained when the popup closes focus_event->reason() != Qt::PopupFocusReason) { combo_box->showPopup(); } } return QStyledItemDelegate::eventFilter(object, event); }
-
@JKSH Hi JKSH, I also have the exact same requirement, i.e. to show the combo box editor's drop down in single click. I first tried
QTimer::singleShot()
and connected to a slot in QComboBox-subclass, which calls QComboBox::showPopup(). Also after looking at this post, tried the eventfilter method suggested by Chris Kawa. In both methods when I click on the table cell, combo box and the drop down appears, but immediately after that, the drop down collapses automatically. Have you seen similar issue ? I'm developing on Linux.QWidget * qDrawToolsDelegate_C::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const { if (!_model) return NULL; if (_model->columnType(index.column()) == COL_TYPE_LINE_STYLE) { qLineStyleComboBox_C * lineStyleCombobox = new qLineStyleComboBox_C(parent); QTimer::singleShot(delay, lineStyleCombobox, SLOT(slotShowPopup())); return lineStyleCombobox; } else if (_model->columnType(index.column()) == COL_TYPE_FILL_PATTERN) { qFillStippleComboBox_C * fillPatternCombobox = new qFillStippleComboBox_C(parent); fillPatternCombobox->installEventFilter(const_cast<qDrawToolsDelegate_C *>(this)); return fillPatternCombobox; } return QStyledItemDelegate::createEditor(parent, option, index); }
I have used timer for qLineStyleComboBox_C and event filter for qFillStippleComboBox_C. But result is the same. On click, editor and drop down appears/flashes and collapses.
-
Have you seen similar issue ?
It's caused by following behavior:
The combo is created (byCreateEditor), combo is set (by setEditorData) and show popup is called. Then the combo is set visible and immediatelly receives mouse release event, on which the combo box calls hide popup. It appears to me as almost Qt bug.Solution is likely to block last mouse release event on combo box by installing event filter in the delegate or by overriding editorEvent of item delegate.
-
@Pavel-Celba would you please describe how do resolve this issue in detail? because I've met same issue now and seems hard to resolve. the combo dropdown list pop up and soon disappear
-
@fanyha I had this problem too. What worked for me was calling
QComboBox::showPopup
fromQStyledItemDelegate::eventFilter
usingQTimer::singleShot
as follows:bool DeviceChannelAssignmentDelegate::eventFilter(QObject* src, QEvent* evt) { if (evt->type() == QEvent::FocusIn) { auto combo = qobject_cast<QComboBox*>(src); if (combo && _firstPopup) { QTimer::singleShot(0, [=]{combo->showPopup();}); _firstPopup = false; } } return QStyledItemDelegate::eventFilter(src, evt); }
I set
_firstPopup
totrue
inQStyledItemDelegate::createEditor
and also connectQComboBox::activated
to a slot which emitsDeviceChannelAssignmentDelegate::commitData
and hides theQComboBox
like so:QWidget* DeviceChannelAssignmentDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { auto combo = new QComboBox(parent); ... connect(combo, &QComboBox::activated, this, &DeviceChannelAssignmentDelegate::emitCommitData); _firstPopup = true; return combo; } void DeviceChannelAssignmentDelegate::emitCommitData() { auto combo = qobject_cast<QComboBox*>(sender()); emit commitData(combo); combo->hide(); }
The end result is that when I double-click on item in the
QTreeView
, theQComboBox
popup menu shows up immediately, and theQComboBox
then disappears after selecting from the popup's choices which works well for me.