pythoncomboboxpyqt

How to return combobox item to main window when item is selected


New to Qt. I'm trying to create a general purpose ComboBox that can be used in multiple situations. This code works when the Save button is pressed. However I would like to dispense with the save button and have the combobox selected item returned to the main window when the item is selected. I have been unable to figure this out and none of the examples I've seen help. Grateful for any suggestions.

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QDialog, QPushButton

from demoGen import Ui_demoGen

class demo(QtWidgets.QDialog):
    selected_item = ''

    def Save(self):
        self.selected_item = self.ui.demo_cb.currentText()
        self.done(0)
        return(self.selected_item)

    def closeEvent(self, event):
        event.accept()

    def __init__(self, info, drive_list):
        super(demo, self).__init__()
        self.ui = Ui_demoGen()
        self.ui.setupUi(self)

        self.selected_item = '#'
        self.ui.demo_save_btn.clicked.connect(lambda: self.Save())

        if info:
            self.ui.labe_1.setText(info)
        self.ui.demo_cb.clear()
        self.ui.demo_cb.addItems(item_list)
        self.ui.demo_cb.setCurrentIndex(-1)
        self.ui.demo_save_btn.setFocus()

        if info:
            self.ui.labe_1.setText(info)
        self.ui.demo_cb.clear()
        self.ui.demo_cb.addItems(item_list)
        self.ui.demo_cb.setCurrentIndex(-1)
        self.ui.demo_save_btn.setFocus()

if __name__ == "__main__":

    app = QtWidgets.QApplication([])
    info = 'Select Item'
    item_list = ['A','B','C','D','E']
    application = demo( info, list(reversed(item_list)))
    application.exec_()
    val = application.Save()
    if val == '':
        print('Z')
    else:
        print('val is ',val)

Solution

  • Premise

    Don't do that. It's a bad UX choice.

    The concept behind a dialog window is to communicate something to the users and prompt some response from them; hence the term "dialog", which should be clear and unambiguous to both parts: the user must understand and know what they're doing, the program must do what the user expects.

    A combo box is an element that shows the current choice between multiple items, so it is important that simply selecting an item doesn't cause a definitive choice.
    Users do make mistakes, and when many items are shown in a crowded list (the drop-down popup) it's easy to choose the wrong one; also, since combo boxes usually accept keyboard input and mouse wheel scrolling to cycle through them, it's even easier to trigger an item change by mistake.

    Possible implementation

    None of the QComboBox signals can be used for this: items can be selected using the keyboard, either by using navigation keys (arrows, page up/down, home/end) or letters (selecting the first match in a sequence of keys), which will trigger "changed" signals such as currentIndexChanged, similarly to what the mouse wheel does, along with the activated signal.

    In order to avoid accidental selection or unexpected behavior, you need to use the signals of the view shown in the popup, which is a QListView. Since QComboBox installs an event filter on the view, preventing its clicked signal to be emitted, we can only use its own pressed or activated signals: the former will act on any mouse button pressed on an item on the popup, the latter will allow using the keyboard Return or Enter keys when an item is highlighted.

    Then, since you need the result, you could simply override the exec() function of QDialog and return whatever you want.

    from PyQt5.QtWidgets import *
    
    class ComboDialog(QDialog):
        def __init__(self, items, info='', parent=None):
            super().__init__(parent)
            self.infoLabel = QLabel(info)
            if not info:
                self.infoLabel.setText('Select an item')
            self.combo = QComboBox()
            self.combo.addItems(items)
            self.combo.setCurrentIndex(-1)
    
            layout = QVBoxLayout(self)
            layout.addWidget(self.infoLabel)
            layout.addWidget(self.combo)
    
            self.combo.view().activated.connect(self.accept)
            self.combo.view().pressed.connect(self.accept)
    
        def exec(self):
            super().exec()
            return self.combo.currentText()
    
    
    if __name__ == "__main__":
        import sys
        app = QApplication(sys.argv)
        info = 'Select Item'
        item_list = ['A','B','C','D','E']
        dialog = ComboDialog(item_list, info)
        val = dialog.exec()
        if val == '':
            print('Z')
        else:
            print('val is ',val)
    

    No, don't do that

    While the above may be acceptable for your request, it has issues related to the UX aspects mentioned before.

    Seeing a dialog with just a label and a combo box is disorienting to the user. What happens when I select an item? I don't see a button, as most dialog normally have. Will it appear later? As a heavy keyboard user, should I expect that the selection changes when using the keyboard? I need to test it, so I can try to use the arrow key to select an item, but then?

    An user interface should make clear to the users what they can do and what happens when they do it, before they do it.

    Having only the combo doesn't provide any benefit at all, but does cause many issues in return.

    The simplest alternative is, obviously, to use a button. Also, for consistency, you should check if the user has closed the windo from the title bar:

    class ComboDialog(QDialog):
        def __init__(self, items, info='', parent=None):
            super().__init__(parent)
            self.infoLabel = QLabel(info)
            if not info:
                self.infoLabel.setText('Select an item')
            self.combo = QComboBox()
            self.combo.addItems(items)
            self.combo.setCurrentIndex(-1)
            self.saveButton = QPushButton('Save')
    
            layout = QVBoxLayout(self)
            layout.addWidget(self.infoLabel)
            layout.addWidget(self.combo)
            layout.addWidget(self.saveButton)
    
            self.saveButton.clicked.connect(self.accept)
    
        def exec(self):
            if super().exec():
                return self.combo.currentText()
            return ''
    

    If the item count isn't high, there are options that may allow "shortening" the user interaction:

    For the list widget case, you may consider to avoid the button by allowing the selection through double clicking (through the itemDoubleClicked signal), but only if you clearly explain the user that they need to double click an item to apply the selection.
    Yet, I wouldn't suggest that: the button(s) approach is still more appropriate to user expectancy.