Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QComboBox delegate: Best way to show the popup menu immediately?


  • Moderators

    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!


  • Moderators

    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.


  • Moderators

    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.


Log in to reply