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);
    }
    

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.