Search code examples
pythonpyqtpysideqcompleter

QComboBox with autocompletion works in PyQt4 but not in PySide


I've got a combo box with a custom completer that worked fine in PyQt4, but isn't working in PySide.

I have verified that the new completer is replacing the QComboBox's built in completer because inline completion is no longer occurring. However when run with PySide, the completer doesn't popup with a filtered list of options.

I've also tried ensuring that all text is all str or all unicode to avoid differences between the PyQt API 1 with QStrings and PySide's use of Python unicode types. Changing the text types has had no effect on either PyQt or PySide's behavior (PyQt keeps working, PySide doesn't work).

Here is my code:

from PySide import QtCore
from PySide import QtGui 

#from PyQt4 import QtCore
#from PyQt4 import QtGui


class AdvComboBox(QtGui.QComboBox):
    def __init__(self, parent=None):
        super(AdvComboBox, self).__init__(parent)

        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.setEditable(True)

        # add a filter model to filter matching items
        self.pFilterModel = QtGui.QSortFilterProxyModel(self)
        self.pFilterModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.pFilterModel.setSourceModel(self.model())

        # add a completer, which uses the filter model
        self.completer = QtGui.QCompleter(self.pFilterModel, self)
        # always show all (filtered) completions
        self.completer.setCompletionMode(QtGui.QCompleter.UnfilteredPopupCompletion)

        self.setCompleter(self.completer)

        # connect signals

        def filter(text):
            print "Edited: ", text, "type: ", type(text)
            self.pFilterModel.setFilterFixedString(str(text))

        self.lineEdit().textEdited[unicode].connect(filter)
        self.completer.activated.connect(self.on_completer_activated)

    # on selection of an item from the completer, select the corresponding item from combobox
    def on_completer_activated(self, text):
        print "activated"
        if text:
            print "text: ", text
            index = self.findText(str(text))
            print "index: ", index
            self.setCurrentIndex(index)


    # on model change, update the models of the filter and completer as well
    def setModel(self, model):
        super(AdvComboBox, self).setModel(model)
        self.pFilterModel.setSourceModel(model)
        self.completer.setModel(self.pFilterModel)


    # on model column change, update the model column of the filter and completer as well
    def setModelColumn(self, column):
        self.completer.setCompletionColumn(column)
        self.pFilterModel.setFilterKeyColumn(column)
        super(AdvComboBox, self).setModelColumn(column)


if __name__ == "__main__":
    import sys

    app = QtGui.QApplication(sys.argv)

    combo = AdvComboBox()

    names = ['bob', 'fred', 'bobby', 'frederick', 'charles', 'charlie', 'rob']

    # fill the standard model of the combobox
    combo.addItems(names)
    combo.setModelColumn(0)
    combo.resize(300, 40)
    combo.show()

    sys.exit(app.exec_())

Solution

  • I figured it out while writing the question...

    It appears that while the PySide QCompleter documentation lists an option to initialize the QCompleter with a model and a parent, it isn't actually working.

    The solution is to set the model of the completer after it is initialized.

    Here is the working code:

    from PySide import QtCore
    from PySide import QtGui 
    
    
    class AdvComboBox(QtGui.QComboBox):
        def __init__(self, parent=None):
            super(AdvComboBox, self).__init__(parent)
    
            self.setFocusPolicy(QtCore.Qt.StrongFocus)
            self.setEditable(True)
    
            # add a filter model to filter matching items
            self.pFilterModel = QtGui.QSortFilterProxyModel(self)
            self.pFilterModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
            self.pFilterModel.setSourceModel(self.model())
    
            # add a completer
            self.completer = QtGui.QCompleter(self)
            #Set the model that the QCompleter uses
            # - in PySide doing this as a separate step worked better
            self.completer.setModel(self.pFilterModel)
            # always show all (filtered) completions
            self.completer.setCompletionMode(QtGui.QCompleter.UnfilteredPopupCompletion)
    
            self.setCompleter(self.completer)
    
            # connect signals
    
            def filter(text):
                print "Edited: ", text, "type: ", type(text)
                self.pFilterModel.setFilterFixedString(str(text))
    
            self.lineEdit().textEdited[unicode].connect(filter)
            self.completer.activated.connect(self.on_completer_activated)
    
        # on selection of an item from the completer, select the corresponding item from combobox
        def on_completer_activated(self, text):
            print "activated"
            if text:
                print "text: ", text
                index = self.findText(str(text))
                print "index: ", index
                self.setCurrentIndex(index)
    
    
        # on model change, update the models of the filter and completer as well
        def setModel(self, model):
            super(AdvComboBox, self).setModel(model)
            self.pFilterModel.setSourceModel(model)
            self.completer.setModel(self.pFilterModel)
    
    
        # on model column change, update the model column of the filter and completer as well
        def setModelColumn(self, column):
            self.completer.setCompletionColumn(column)
            self.pFilterModel.setFilterKeyColumn(column)
            super(AdvComboBox, self).setModelColumn(column)
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtGui.QApplication(sys.argv)
    
        combo = AdvComboBox()
    
        names = ['bob', 'fred', 'bobby', 'frederick', 'charles', 'charlie', 'rob']
    
        # fill the standard model of the combobox
        combo.addItems(names)
        combo.setModelColumn(0)
        combo.resize(300, 40)
        combo.show()
    
        sys.exit(app.exec_())