Search code examples
pythonqtpyqtqtreeviewqcombobox

How do i select at item in a QTreeView that is inside a QComboBox


I have a QtreeView as the view in a QComboBox. In my app the root items are category labels and are not to be selected. When i create the view i would like to pre select one of the child items (the first root item is selected by default), but i can't figure out how. Examples of this are (especially for python) thin on the ground.

Here's my simplified example:

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

data = [ (("Cat A",False), [(("Thing 1",True), []),(("Thing 2",True), [])]),
    (("Cat B",False), [(("Thing 3",True), []), (("Thing 4",True), [])])]

class MyComboBox(QComboBox):
    def __init__(self):
        super(QComboBox,self).__init__()
        self.setView(QTreeView())

        self.view().setHeaderHidden(True)
        self.view().setItemsExpandable(False)
        self.view().setRootIsDecorated(False)

    def showPopup(self):
        self.view().expandAll()
        QComboBox.showPopup(self)

class Window(QWidget):
    def __init__(self):

        QWidget.__init__(self)

        self.model = QStandardItemModel()
        self.addItems(self.model, data)

        self.combo = MyComboBox()
        self.combo.setModel(self.model)

        layout = QVBoxLayout()
        layout.addWidget(self.combo)
        self.setLayout(layout)

        # I can choose which combobox item to select here, but I am unable to
        #choose child items
        #self.combo.setCurrentIndex(1)

    def addItems(self, parent, elements):
        for text, children in elements:
            item = QStandardItem(text[0])
            # root items are not selectable, users pick from child items
            item.setSelectable(text[1])
            parent.appendRow(item)
            if children:
                self.addItems(item, children)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

I have worked from the examples here and here

the question has pretty much been asked before, but not for python, and the solution posted doesn't work for me.


Solution

  • This is an alternative and more generic way for your current code. It will work for additional level of nested items and any configuration of selectable items.

    class MyComboBox(QComboBox):
        def __init__(self):
            super(MyComboBox,self).__init__()  # your super was wrong.
                                               # you need to pass the _current_ class name
            self.setView(QTreeView())
    
            self.view().setHeaderHidden(True)
            self.view().setItemsExpandable(False)
            self.view().setRootIsDecorated(False)
    
        def showPopup(self):
            self.setRootModelIndex(QModelIndex()) # you need to add this
            self.view().expandAll()
            QComboBox.showPopup(self)
    
        def setModel(self, model):
            super(MyComboBox, self).setModel(model)
            parent, row = self._firstSelectableItem()
            if row is not None:
                self.setRootModelIndex(parent)
                self.setCurrentIndex(row)
    
        def _firstSelectableItem(self, parent=QModelIndex()):
            """
            Internal recursive function for finding the first selectable item.
            """
            for i in range(self.model().rowCount(parent)):
                itemIndex = self.model().index(i,0,parent)
                if self.model().itemFromIndex(itemIndex).isSelectable():
                    return parent, i
                else:
                    itemIndex, row = self._firstSelectableItem(itemIndex)
                    if row is not None:
                        return itemIndex, row
            return parent, None