Arrow appearance for QComboBox in QTableWidget
-
I would like to have a QTableWidget with a column that uses QComboBox for input. Using a QStyledItemDelegate with QComboBox works as in the example below.
My problem is that the combo-box arrow only appears for cells that are being edited, when I would like them to appear always.
Appearance with another cell selected:
Appearance with a combo-box cell selected:
Is there a way to keep the combo-box look for deselected cells?
main.cpp:
#include <QApplication> #include <QTableWidget> #include "comboboxitemdelegate.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); QTableWidget tw; ComboBoxItemDelegate* cbid = new ComboBoxItemDelegate(&tw); tw.setItemDelegateForColumn(1, cbid); tw.setColumnCount(2); tw.setRowCount(2); tw.show(); return a.exec(); }
comboboxitemdelegate.h:
#ifndef COMBOBOXITEMDELEGATE_H #define COMBOBOXITEMDELEGATE_H #include <QStyledItemDelegate> class ComboBoxItemDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit ComboBoxItemDelegate(QObject *parent = nullptr); QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setEditorData(QWidget *editor, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; }; #endif // COMBOBOXITEMDELEGATE_H
comboboxitemdelegate.cpp:
#include "comboboxitemdelegate.h" #include <QComboBox> ComboBoxItemDelegate::ComboBoxItemDelegate(QObject *parent) : QStyledItemDelegate{parent} { } QWidget *ComboBoxItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { QComboBox *cb = new QComboBox(parent); cb->addItem("foo"); cb->addItem("bar"); return cb; } void ComboBoxItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QComboBox *cb = qobject_cast<QComboBox *>(editor); Q_ASSERT(cb); const QString currentText = index.data(Qt::EditRole).toString(); const int cbIndex = cb->findText(currentText); if (cbIndex >= 0) cb->setCurrentIndex(cbIndex); } void ComboBoxItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QComboBox *cb = qobject_cast<QComboBox *>(editor); Q_ASSERT(cb); model->setData(index, cb->currentText(), Qt::EditRole); }
-
@tgbe The delegate only creates a widget when an item is being edited. If you want the item to look like a combo when it's not being edited you can override the delegate's paint method and use QStyle methods to draw a combo there.
-
@Chris-Kawa Thank you, I did not realize this was the intended behavior.
I dug some more and found this Stack Overflow thread, which seems to implement your suggestion:
https://stackoverflow.com/questions/47844275/display-qcombobox-down-arrow-in-custom-tree-view
Another method seems to be using openPersistentEditor().
-
-
@tgbe This is a performance optimization. You don't want to have many widgets visible all at once, especially if your data set is large. The intended design in this case is to use custom painting on the items that are not currently edited. Painting a picture is a lot cheaper than creating a widget and managing its memory, geometry and events.
-
@Chris-Kawa Ok, overriding
paint()
seems to be the efficient way. I added this to the example above, and it works nicely:void ComboBoxItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionComboBox comboBoxOption; comboBoxOption.rect = option.rect; comboBoxOption.currentText = index.data(Qt::DisplayRole).toString(); QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &comboBoxOption, painter); QApplication::style()->drawControl(QStyle::CE_ComboBoxLabel, &comboBoxOption, painter); }
I only have one small issue with this solution: It takes several clicks for the combo-box popup to appear, even with
QAbstractItemView::AllEditTriggers
set for the QTableWidget. I assume the first click creates the combo box, which then goes into line-edit mode. Then it reacts to further clicks like a static combo box. If I click outside the table first, it even takes three clicks for the popup to appear.Ideally, I would like to line-edit when the text area is clicked and pop-up when the arrow button is clicked. Is there a way to achieve this?
-
@tgbe Unfortunately there's a bit of time between creating a widget, showing it and it starting to process events, so the click that caused it to be created can't be directly forwarded to it.
The way I usually deal with it is synthetize a dummy press and release events and send them to the widget using postEvent() once it's shown. Since there are multiple possible trigger types you can override editorEvent() to check what type of event caused the edit (click, keyboard etc.) and get the mouse position for the dummy click.
I do this often enough that I wish this was a built-in option in the Qt delegates, but sadly it's not, so here we are.