21

Using a regular QComboBox populated with items, if currentIndex is set to -1, the widget is empty. It would be very useful to instead have an initial descriptive text visible in the combo box(e.g. "--Select Country--", "--Choose Topic--", etc.) which is not shown in the dropdown list.

I couldn't find anything in the documentation, nor any previous questions with answers.

swalog
  • 4,403
  • 3
  • 32
  • 60

3 Answers3

29

It doesn't appear that case was anticipated in the Combo Box API. But with the underlying model flexibility it seems you should be able to add your --Select Country-- as a first "legitimate" item, and then keep it from being user selectable:

QStandardItemModel* model =
        qobject_cast<QStandardItemModel*>(comboBox->model());
QModelIndex firstIndex = model->index(0, comboBox->modelColumn(),
        comboBox->rootModelIndex());
QStandardItem* firstItem = model->itemFromIndex(firstIndex);
firstItem->setSelectable(false);

Depending on what precise behavior you want, you might want to use setEnabled instead. Or I'd personally prefer it if it was just a different color item that I could set it back to:

Qt, How do I change the text color of one item of a QComboBox? (C++)

(I don't like it when I click on something and then get trapped to where I can't get back where I was, even if it's a nothing-selected-yet-state!)

Community
  • 1
  • 1
  • Excellent! There is apparently a part of Qt I haven't touched, which seems very useful for customization. From the SO question you linked to, changing the background color makes it look very nice, and probably better than what I had originally envisioned. firstItem->setData(Qt::lightGray , Qt::BackgroundRole); (I wouldn't mind if you added this to your answer). – swalog Oct 03 '11 at 11:28
  • @EXIT_FAILURE I think your comment covers your alternative, as I haven't tried it so I don't know if I'd like it. :) But yes, Qt offers some originality, if we all gang up maybe we can stop GTK and wxWidgets! :-/ http://stackoverflow.com/questions/7545804/modeless-parentless-wxdialog-still-always-above-wxframe-window-in-z-order/ – HostileFork says dont trust SE Oct 04 '11 at 11:07
  • 2
    Since Qt 5.15 the proper way to do this is `setPlaceholderText` as answered here https://stackoverflow.com/a/63937462/20984 – Luc Touraille Jul 27 '21 at 08:33
11

One way you can do something similar is to set a placeholder:

comboBox->setPlaceholderText(QStringLiteral("--Select Country--"));
comboBox->setCurrentIndex(-1);

This way you have a default that cannot be selected.

enter image description here

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
2

Leaving my solution here from PyQt5. Create a proxy model and shift all the rows down one, and return a default value at row 0.

class NullRowProxyModel(QAbstractProxyModel):
    """Creates an empty row at the top for null selections on combo boxes
    """

    def __init__(self, src, text='---', parent=None):
        super(NullRowProxyModel, self).__init__(parent)
        self._text = text
        self.setSourceModel(src)

    def mapToSource(self, proxyIndex: QModelIndex) -> QModelIndex:
        if self.sourceModel():
            return self.sourceModel().index(proxyIndex.row()-1, proxyIndex.column())
        else:
            return QModelIndex()

    def mapFromSource(self, sourceIndex: QModelIndex) -> QModelIndex:
        return self.index(sourceIndex.row()+1, sourceIndex.column())

    def data(self, proxyIndex: QModelIndex, role=Qt.DisplayRole) -> typing.Any:
        if proxyIndex.row() == 0 and role == Qt.DisplayRole:
            return self._text
        elif proxyIndex.row() == 0 and role == Qt.EditRole:
            return None
        else:
            return super(NullRowProxyModel, self).data(proxyIndex, role)

    def index(self, row: int, column: int, parent: QModelIndex = ...) -> QModelIndex:
        return self.createIndex(row, column)

    def parent(self, child: QModelIndex) -> QModelIndex:
        return QModelIndex()

    def rowCount(self, parent: QModelIndex = ...) -> int:
        return self.sourceModel().rowCount()+1 if self.sourceModel() else 0

    def columnCount(self, parent: QModelIndex = ...) -> int:
        return self.sourceModel().columnCount() if self.sourceModel() else 0

    def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...) -> typing.Any:
        if not self.sourceModel():
            return None
        if orientation == Qt.Vertical:
            return self.sourceModel().headerData(section-1, orientation, role)
        else:
            return self.sourceModel().headerData(section, orientation, role)
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
kblst
  • 493
  • 8
  • 12
  • Thanks for this. Worked great on an older version of Qt on a raspberry pi (5.11.3). Created a more elaborate answer with a working example [here](https://stackoverflow.com/a/64455830/2974621) – Chris Oct 21 '20 at 02:31