[Python] Is there a way to make the LineEdit-Part of a Combobox look like the (delegated)...

Discussão em 'Python' iniciado por Stack, Março 14, 2025.

  1. Stack

    Stack Membro Participativo

    I want to change the appearance of a QCombobox including the items and the "LineEdit-Part" to look nice/professional.

    The easy part:
    The combo box functions both as a search field and for selecting symbols that have already been found successfully. As a symbol often provides several results, these must be easily distinguishable from each other as combo box items.

    The hard part/where i failed:
    In order to make the result/Comboboxitems look good, I decided to use a QStyledItemDelegate. This works, but I have not yet found a way to transfer this look to the LineEdit part.

    Ideally, the behavior of the combobox would be as follows:


    • With no focus/was not clicked:
      Combobox.LineEdit looks optically the same as the selected Combobox-Item.


    • When clicked/has focus:
      Combobox.LineEdit looks like a LineEdit/fulfills the function of the search field

    I assume it must/can be done with a CustomQCombobox. However all my attempts failed horribly.

    A working and code snippet as base/to experiment with:

    import sys
    from PyQt5 import QtWidgets, QtCore, QtGui
    import qdarkstyle
    # from Custom_Widgets import CustomComboBox

    class ComboBoxItemDelegate(QtWidgets.QStyledItemDelegate):
    #This delegate will make the Combobox Items look nice

    def paint(self, painter, option, index):
    primary = index.data(QtCore.Qt.DisplayRole)
    secondary = index.data(QtCore.Qt.UserRole)

    if secondary is None:
    secondary = ""

    # Highlight Background if you mouseover
    if option.state & QtWidgets.QStyle.State_Selected:
    painter.fillRect(option.rect, option.palette.highlight())
    else:
    painter.fillRect(option.rect, option.palette.base())

    # Make/Define the "Boxes" where primary and secondary will be written in
    rect = option.rect.adjusted(5, 0, -5, 0)
    primaryRect = QtCore.QRect(rect.left(), rect.top(), rect.width(), rect.height()//2)
    secondaryRect = QtCore.QRect(rect.left(), rect.top() + rect.height()//2, rect.width(), rect.height()//2)

    #Draw Primary
    primaryFont = option.font
    painter.setFont(primaryFont)
    painter.setPen(option.palette.text().color())
    painter.drawText(primaryRect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, primary)

    #Draw Secondary
    secondaryFont = QtGui.QFont(option.font)
    secondaryFont.setPointSize(option.font.pointSize() - 1)
    painter.setFont(secondaryFont)
    painter.setPen(QtGui.QColor("gray"))
    painter.drawText(secondaryRect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, secondary)

    def sizeHint(self, option, index):
    #Make it fit
    size = super().sizeHint(option, index)
    size.setHeight(int(size.height() * 1.6))
    return size


    class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
    super().__init__()
    self.initUI()
    items = [{'additional_info':'HELLO', 'Item':'SYMBOL'},
    {'additional_info':'WORLD', 'Item':'G.I. JOE'},
    {'additional_info':'NOVABRAIN', 'Item':'FLATEARTH'},
    {'additional_info':'SUPERSTAR', 'Item':'BOB THE BUILDER'}]
    for item in items:
    self.initial_filling(item['Item'], item['additional_info'])

    def initial_filling(self, primary, secondary):
    self.searchComboBox.addItem(primary)
    index = self.searchComboBox.model().index(self.searchComboBox.count()-1, 0)
    self.searchComboBox.model().setData(index, secondary, role=QtCore.Qt.UserRole)

    def initUI(self):
    self.setWindowTitle("My best Widget")
    self.resize(324, 500)
    central_widget = QtWidgets.QWidget()
    self.setCentralWidget(central_widget)
    layout = QtWidgets.QVBoxLayout(central_widget)

    # Creating the Combobox
    self.searchComboBox = QtWidgets.QComboBox()
    self.searchComboBox.setEditable(True)
    self.searchComboBox.setItemDelegate(ComboBoxItemDelegate(self.searchComboBox))
    self.searchComboBox.setInsertPolicy(QtWidgets.QComboBox.NoInsert)
    layout.addWidget(self.searchComboBox)

    #Usually triggers API. Just think of it as "make menu" function
    self.searchComboBox.lineEdit().returnPressed.connect(self.on_search_return)
    #Updating LineEdit but ugly version
    self.searchComboBox.currentIndexChanged.connect(self.updateComboBoxLineEdit)

    # !(not relevant)! Dummy Table !(not relevant)!
    self.table = QtWidgets.QTableWidget()
    self.table.setColumnCount(3)
    self.table.setHorizontalHeaderLabels(["Some", "Nice", "Table"])
    self.setWindowFlag(QtCore.Qt.WindowStaysOnTopHint)
    self.table.verticalHeader().setVisible(False)
    self.table.setShowGrid(False)
    layout.addWidget(self.table)

    def on_search_return(self):
    #PREPARE INPUT FOR API CALL AND GETTING RESULT
    symbol = self.searchComboBox.currentText().strip().upper()
    if not symbol:
    return

    #.... RESULTS FROM API

    results = [{'additional_info':'HELLO', 'Item':symbol},
    {'additional_info':'WORLD', 'Item':symbol},
    {'additional_info':'GALAXY', 'Item':symbol},
    {'additional_info':'STAR', 'Item':symbol}]
    self.show_lookup_menu(results)


    def show_lookup_menu(self, results):
    #Creating Menu based on results
    menu = QtWidgets.QMenu(self)
    for result in results:
    text = f"{result['additional_info']} - {result['Item']}"
    action = QtWidgets.QAction(text, menu)
    action.setData(result)
    menu.addAction(action)
    menu.triggered.connect(self.on_menu_action_triggered)
    pos = self.searchComboBox.mapToGlobal(QtCore.QPoint(0, self.searchComboBox.height()))
    menu.exec_(pos)


    def on_menu_action_triggered(self, action):
    result = action.data()
    if result:
    print("You chose: ", result)
    primary = result["Item"]
    secondary = result["additional_info"]
    if self.searchComboBox.findText(primary) == -1:
    self.searchComboBox.addItem(primary)
    index = self.searchComboBox.model().index(self.searchComboBox.count()-1, 0)
    self.searchComboBox.model().setData(index, secondary, role=QtCore.Qt.UserRole)


    def updateComboBoxLineEdit(self, index):
    #Sadly LineEdit-Part of Combobox has no Delegate...
    if index >= 0:
    primary = self.searchComboBox.itemText(index)
    secondary = self.searchComboBox.itemData(index, role=QtCore.Qt.UserRole)
    if secondary:
    combined = f"{primary} {secondary}"
    else:
    combined = primary
    self.searchComboBox.lineEdit().setText(combined)


    def main():
    qt_app = QtWidgets.QApplication(sys.argv)
    qt_app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())

    window = MainWindow()
    window.show()

    sys.exit(qt_app.exec_())

    if __name__ == "__main__":
    main()



    Ideally, I would like to keep the look (primary/secondary written on top of each other). It would also be fine to write primary and secondary one after the other.
    It would only be important to me that secondary is gray and one level smaller (so that primary and secondary can be easily distinguished)

    Continue reading...

Compartilhe esta Página