pythonpyside2qcolumnview

How to add another column in a QColumnView based on the user's selection?


Using PySide2, I have an interface which contains a QColumnView widget, for which I am trying to create functionality similar to that of a contact-lookup: select a person's name and the next column over then displays more options/details about that selection.

Currently, I populate the view when the UI loads and it displays the first level of options available to the user.

Desired functionality: When the user makes a selection, another column is added to the QColumnView which displays the options available based on the user's selection.

Current problem: When the user makes a selection, since I am unsure of how to add another column/level to the QColumnView, instead, the model for the QColumnView is updated and only the next level of options are shown - meaning the previous values are overwritten in the view, so to speak. While this is functional, it is not ideal, as having the previous column still in view, enables the user to see which category they are currently searching.

As seen here, the user has selected 'Address' which then brings up options associated with 'Address' in the column to the right.

enter image description here

Just to create a basic interface that incorporates a QColumnView, it would look something like this:

import sys
from PySide2 import QtWidgets, QtCore
import PySide2

class UserInput(object):
    def setupUi(self, get_user_input=None):
        # Basic shape
        self.width = 425
        get_user_input.setObjectName("get_user_input")
        get_user_input.resize(425, self.width)
        self.frame = QtWidgets.QFrame(get_user_input)
        self.frame.setGeometry(QtCore.QRect(11, 10, 401, 381))
        self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.frame.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.frame.setObjectName("frame")
        # Creating the grid layout for most of the display elements
        self.gridLayoutWidget = QtWidgets.QWidget(self.frame)
        self.gridLayoutWidget.setGeometry(QtCore.QRect(10, 10, 381, 361))
        self.gridLayoutWidget.setObjectName("gridLayoutWidget")
        self.get_user_input_layout = QtWidgets.QGridLayout(self.gridLayoutWidget)
        self.get_user_input_layout.setContentsMargins(5, 5, 5, 5)
        self.get_user_input_layout.setObjectName("get_user_input_layout")
        # Grid layout for the buttons
        self.buttonLayoutGrid = QtWidgets.QWidget(get_user_input)
        self.buttonLayoutGrid.setGeometry(QtCore.QRect(10, 390, 401, 41))
        self.buttonLayoutGrid.setObjectName("buttonLayoutGrid")
        self.buttonLayout = QtWidgets.QGridLayout(self.buttonLayoutGrid)
        self.buttonLayout.setContentsMargins(0, 0, 0, 0)
        self.buttonLayout.setObjectName("buttonLayout")
        # Buttons
        self.buttonOK = QtWidgets.QPushButton(self.buttonLayoutGrid)
        self.buttonOK.setObjectName("buttonOK")
        self.buttonOK.setText("OK")
        self.buttonLayout.addWidget(self.buttonOK, 0, 1, 1, 1)
        self.buttonCancel = QtWidgets.QPushButton(self.buttonLayoutGrid)
        self.buttonCancel.setObjectName("buttonCancel")
        self.buttonCancel.setText("CANCEL")
        self.buttonLayout.addWidget(self.buttonCancel, 0, 2, 1, 1)

        #Column View Data
        self.t = PySide2.QtWidgets.QColumnView()
        self.t.setColumnWidths([10,10])

        model = PySide2.QtGui.QStandardItemModel()
        model.setColumnCount(1)
        model.setHorizontalHeaderLabels(['Data Collections'])

        self._dict = {'Test 1': [1, 2, 3], 'Test 2': [4, 5, 6], 'Test 3': [7, 8, 9]}
        _data = []

        for each in self._dict:
            resultItem = PySide2.QtGui.QStandardItem()
            insertText = each
            resultItem.setText(str(insertText))
            model.appendRow(resultItem)

        self.t.setModel(model)
        self.t.clicked.connect(self.column_view_clicked)
        self.get_user_input_layout.addWidget(self.t, 2,0,1,1)

    def column_view_clicked(self, row):
        model = PySide2.QtGui.QStandardItemModel()
        model.setColumnCount(1)
        model.setHorizontalHeaderLabels(['Analysis Variables'])
        _data = []

        for i in self._dict[row.data()]:
            resultItem = PySide2.QtGui.QStandardItem()
            resultItem.setText(str(i))
            model.appendRow(resultItem)

        self.t.createColumn(row)        

class UserInputPrompt(PySide2.QtWidgets.QDialog, UserInput):

    def __init__(self, path_to_image=None):
        app = PySide2.QtWidgets.QApplication(sys.argv)
        super(UserInputPrompt, self).__init__()
        self.setupUi(self)
        super(UserInputPrompt, self).exec_()        

    def get_user_input(self):
        print("Get user selection")

def main(): 
    UserInputPrompt()

if __name__ == '__main__':
    main()

When you run the above code, click on any of items on the left of the interface ('Test 1, 'Test 2' or 'Test 3') and the result will add a column to the right of the existing column but no data is currently being populated there - since I don't know how to do that at the moment.


Solution

  • What the OP points out as a requirement is just the functionality of ColumnView, the problem is that you are not passing the model with the data structured correctly. As you can see the structure is of a tree where there are parent nodes and child nodes, so you must create a model with that structure. In this case I have used a QStandardItemModel to handle the data as I show below:

    import sys
    from PySide2 import QtCore, QtGui, QtWidgets
    
    
    def populateTree(children, parent):
        for child in children:
            child_item = QtGui.QStandardItem(child)
            parent.appendRow(child_item)
            if isinstance(children, dict):
                populateTree(children[child], child_item)
    
    
    class Dialog(QtWidgets.QDialog):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.frame = QtWidgets.QFrame(
                frameShape=QtWidgets.QFrame.StyledPanel, frameShadow=QtWidgets.QFrame.Sunken
            )
            self.buttonOK = QtWidgets.QPushButton(self.tr("OK"))
            self.buttonCancel = QtWidgets.QPushButton(self.tr("CANCEL"))
    
            self.columnview = QtWidgets.QColumnView()
    
            grid_layout = QtWidgets.QGridLayout(self)
            grid_layout.addWidget(self.frame, 0, 0, 1, 2)
            grid_layout.addWidget(self.buttonOK, 1, 0)
            grid_layout.addWidget(self.buttonCancel, 1, 1)
    
            lay = QtWidgets.QVBoxLayout(self.frame)
            lay.addWidget(self.columnview)
    
            data = {
                "root1": {
                    "child11": ["subchild111", "subchild112"],
                    "child12": ["subchild121"],
                },
                "root2": {
                    "child21": ["subchild211"],
                    "child22": ["subchild221", "subchild222"],
                },
            }
    
            self.model = QtGui.QStandardItemModel(self)
            self.columnview.setModel(self.model)
            populateTree(data, self.model.invisibleRootItem())
    
            self.resize(1280, 480)
    
    
    def main():
        app = QtWidgets.QApplication(sys.argv)
        w = Dialog()
        w.show()
        sys.exit(app.exec_())
    
    
    if __name__ == "__main__":
        main()