Question

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.

Was it helpful?

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

OTHER TIPS

This will work if you use a QTreeWidget as both the view and model on your combobox i.e.

self.tree = QTreeWidget()
self.combo.setModel(self.tree.model())
self.combo.setView(self.tree)

Additionally you will need to change your addItems() function to construct a QTreeWidget with QTreeWidgetItem's as children. Once you've done this, the following will select an item in your treewidget:

# make item current in tree to get hold of its index
self.tree.setCurrentItem(ITEMTOSELECT)
# make item's parent reference point and provide index in relation to parent
self.combo.setRootModelIndex(self.tree.currentIndex().parent())
self.combo.setCurrentIndex(self.tree.currentIndex().row())
# reset combobox to display full tree again
self.tree.setCurrentItem(self.tree.invisibleRootItem())
self.combo.setRootModelIndex(self.tree.currentIndex()) 

This is based on the example found here.

Hope this helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top