1

I'm in a situation where I need to dynamically generate a UI based on a randomly differing amount of parents with a random amount of nested children.

When the child QTreeWidgetItem is expanded, it's child item should look like this:

-Parent
    -Child
        -ComboBox | ComboBox | ComboBox | ComboBox

I've been trying the setItemWidget with no luck and only crashes. Below is a simple example of what I am trying to do.

#! /usr/bin/env python

from PySide2 import QtWidgets, QtCore


class TestUI(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(TestUI, self).__init__(parent)

    def setup_UI(self):
        # Setup the main window
        self.MainWindow = QtWidgets.QMainWindow(self.parent)
        self.MainWindow.resize(900, 400)
        self.MainWindow.setMinimumSize(QtCore.QSize(900, 600))
        self.MainWindow.setObjectName("TestUI")

        # Create the main tree
        self.tree_widget = QtWidgets.QTreeWidget(self.MainWindow)
        self.gridLayout = QtWidgets.QGridLayout(self.tree_widget)
        self.MainWindow.setCentralWidget(self.tree_widget)

        # Create a root parent item, repeat this for as many parents as needed
        top_item = QtWidgets.QTreeWidgetItem(self.tree_widget)
        top_item.setText(0, "Top_Parent_Item")
        self.tree_widget.addTopLevelItem(top_item)

        # Create child item
        child_item = QtWidgets.QTreeWidgetItem(top_item)
        child_item.setText(0, "Child_Item")
        top_item.addChild(child_item)

        # Create the child to the child that we use to replace the item with the combobox widget
        nested_child_item = QtWidgets.QTreeWidgetItem(child_item)
        nested_child_item.setText(0, "Nested_Child_Item")
        child_item.addChild(nested_child_item)

        # Create the combobox widget for all comboxes
        widget = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout(widget)

        # Repeat this 4 times for 4 comboboxes
        combo_box_1 = QtWidgets.QComboBox()
        values = ["1", "2", "3", "4", "5"]
        combo_box_1.addItems(values)
        layout.addWidget(combo_box_1)

        # Apply layout with everything in it
        widget.setLayout(layout)

        # This line hard crashes my code but no idea how else to go about adding a widget to an item.
        self.tree_widget.setItemWidget(nested_child_item, 1, combo_box_1)

        #Show the UI
        self.MainWindow.show()

# Launch UI
test = TestUI()
test.setup_UI()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241

1 Answers1

2

Before answering your question, it should be noted that there are many inconsistencies in your code: You have a QMainWindow that is the child of another QMainWindow, there are widgets that are useless, etc. I recommend you analyze your code well if it makes sense or not.

On the other hand a default QTreeWidget has a column so you can only set a QComboBox, and the indexes of the columns start from 0. If you want more columns to appear you must set it explicitly.

#! /usr/bin/env python

from PySide2 import QtWidgets, QtCore


class TestUI(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(TestUI, self).__init__(parent)

    def setup_UI(self):
        # Create the main tree
        self.tree_widget = QtWidgets.QTreeWidget()
        self.setCentralWidget(self.tree_widget)

        self.tree_widget.setColumnCount(4)

        top_item = QtWidgets.QTreeWidgetItem(self.tree_widget)
        top_item.setText(0, "Top_Parent_Item")
        self.tree_widget.addTopLevelItem(top_item)

        child_item = QtWidgets.QTreeWidgetItem(top_item)
        child_item.setText(0, "Child_Item")
        top_item.addChild(child_item)

        nested_child_item = QtWidgets.QTreeWidgetItem(child_item)
        nested_child_item.setText(0, "Nested_Child_Item")
        child_item.addChild(nested_child_item)

        for i in range(self.tree_widget.columnCount()):
            combo_box = QtWidgets.QComboBox(self.tree_widget)
            values = ["1", "2", "3", "4", "5"]
            combo_box.addItems(values)

            self.tree_widget.setItemWidget(nested_child_item, i, combo_box)

        self.tree_widget.expandAll()
        self.resize(640, 480)

        self.show()


if __name__ == "__main__":
    app = QtWidgets.QApplication([])
    test = TestUI()
    test.setup_UI()
    app.exec_()

enter image description here

Update:

Another possible solution is to use a delegate to provide the QComboBox automatically:

#! /usr/bin/env python

from PySide2 import QtWidgets, QtCore


class StyledItemDelegate(QtWidgets.QStyledItemDelegate):
    def paint(self, painter, option, index):
        if index.parent().parent().isValid():
            if isinstance(option.widget, QtWidgets.QAbstractItemView):
                option.widget.openPersistentEditor(index)
        else:
            super(StyledItemDelegate, self).paint(painter, option, index)

    def createEditor(self, parent, option, index):
        if index.parent().parent().isValid():
            editor = QtWidgets.QComboBox(parent)
            values = ["1", "2", "3", "4", "5"]
            editor.addItems(values)
            return editor
        return super(StyledItemDelegate, self).createEditor(parent, option, index)

    def updateEditorGeometry(self, editor, option, index):
        editor.setContentsMargins(0, 0, 0, 0)
        editor.setGeometry(option.rect)

    def sizeHint(self, option, index):
        s = super(StyledItemDelegate, self).sizeHint(option, index)
        s.setHeight(s.height() * 1.5)
        return s


class TestUI(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(TestUI, self).__init__(parent)

    def setup_UI(self):
        # Create the main tree
        self.tree_widget = QtWidgets.QTreeWidget()
        self.setCentralWidget(self.tree_widget)

        delegate = StyledItemDelegate(self)
        self.tree_widget.setItemDelegate(delegate)
        self.tree_widget.setColumnCount(4)

        top_item = QtWidgets.QTreeWidgetItem(self.tree_widget)
        top_item.setText(0, "Top_Parent_Item")
        self.tree_widget.addTopLevelItem(top_item)

        child_item = QtWidgets.QTreeWidgetItem(top_item)
        child_item.setText(0, "Child_Item")
        top_item.addChild(child_item)

        nested_child_item = QtWidgets.QTreeWidgetItem(child_item)
        child_item.addChild(nested_child_item)

        self.tree_widget.expandAll()
        self.resize(640, 480)

        self.show()


if __name__ == "__main__":
    app = QtWidgets.QApplication([])
    test = TestUI()
    test.setup_UI()
    app.exec_()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thank you again! I'll mark it as answered but I would like a little more information about the limits and how far I can go with this if that's alright. In a perfect world, instead of a nested combobox child on the child, I wanted to have the "Child_Item" include the comboboxes on itself. For example, the child to "Top_Parent_Item" would be "Child_Item - | combobox | combobox | combobox | combobox". This way I could have the comboboxes on the actual children and any nests on those children would have the same setup and so forth. Is this possible? – Nightingale Mar 19 '20 at 04:12
  • So after trying your example multiple times, I am still getting consistent crashes from the line `self.tree_widget.setItemWidget(nested_child_item, i, combo_box)` I am not able to run this at all. Not sure what else to do at this point. I'm limited to Python 2.7 as Maya doesn't use Python3, I am also using Maya's built in Pyside2 package. – Nightingale Mar 21 '20 at 00:26
  • Hallelujah moment! QtWidgets.QComboBox() is the culprit, it actually requires a parent of the main tree. combo_box = QtWidgets.QComboBox(self.tree_widget) All working now! – Nightingale Mar 21 '20 at 00:39
  • @Nightingale How strange since using `setItemWidget` the view takes the ownership of the QComboBox, but it is great that you have solved it. On the other hand, I already provided an alternative solution. – eyllanesc Mar 21 '20 at 00:49
  • I'm having another small issue with this UI and I was hoping you would have a quick solution instead of posting another big question write-up. I need to resize the QMainWindow width to match the width of the most expanded items inside the QTreeWidget. I tried getting the header width and even widget geometry size but it only returns the outer window size. I need it to return the entire header width even if it's too long and a scroll bar is automatically added, (header width behind the window) if that makes sense. Thanks in advance! – Nightingale Apr 10 '20 at 03:12