Search code examples
pythonmodel-view-controllerqtableviewqlistviewpyside2

add feature for previous question PySide2 QListView and QTableView


The previous question was PySide2 QListView QTableView sync problem

Imagine to have another dict4 in the data structure:

'dict4':{'k8':'v8', 'EXISTING_DICT':'dict2'},

Meaning, that a dictionary could include another existing dictionary.

So the representation in the QTableView shouldn't be directly showed, but:

1) Show just the name of it in the QTableView:

k1 | v1
-------
k2 | v2
-------
k3 | v3
-------
dict2

2) If double click it on it: select the existing dictionary in the QListView, which will trigger to show its contents in the QTableView, which in this case:

k4 | v4

Solution

  • In this case the idea is to use a role to indicate that the field points to another item, then use setSpan to join items and finally check if the item selected in the QTableView points to another element by selecting if it is so

    from PySide2 import QtCore, QtGui, QtWidgets
    
    dict_of_dicts={
        'dict1':{'k1':'v1', 'k2':'v2', 'k3':'v3', 'EXISTING_DICT': 'dict3'},
        'dict2':{'k4':'v4', 'EXISTING_DICT': 'dict1'},
        'dict3':{'k5':'v5', 'k6':'v6', 'k7':'v7', 'EXISTING_DICT': 'dict4'},
        'dict4':{'k8':'v8', 'EXISTING_DICT':'dict2'},
    }
    
    def create_model_from_dict(d, parent=None):
        model = QtGui.QStandardItemModel(0, 2, parent)
        for k, v in dict_of_dicts.items():
            it = QtGui.QStandardItem(k)
            model.appendRow(it)
            for k_, v_ in v.items():
                if k_ != "EXISTING_DICT":
                    it.appendRow([QtGui.QStandardItem(k_), QtGui.QStandardItem(v_)])
                else:
                    child_it = QtGui.QStandardItem(v_)
                    child_it.setTextAlignment(QtCore.Qt.AlignCenter)
                    child_it.setData(True, QtCore.Qt.UserRole + 1000)
                    it.appendRow(child_it)
        return model
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Widget, self).__init__(parent)
            model = create_model_from_dict(dict_of_dicts, self)
    
            self.tableview = QtWidgets.QTableView()
            self.tableview.setModel(model)
            self.tableview.selectionModel().selectionChanged.connect(self.handleSelectionChangedTV)
    
            self.listview = QtWidgets.QListView()
            self.listview.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
            self.listview.setModel(model)
            self.listview.selectionModel().selectionChanged.connect(self.handleSelectionChangedLV)
            self.listview.selectionModel().select(model.index(0, 0), QtCore.QItemSelectionModel.Select)
    
            hlay = QtWidgets.QHBoxLayout(self)
            hlay.addWidget(self.listview)
            hlay.addWidget(self.tableview)
    
        @QtCore.Slot(QtCore.QItemSelection)
        def handleSelectionChangedLV(self, item):
            ixs = item.indexes()
            if ixs:
                self.tableview.setRootIndex(ixs[0])
                model = self.tableview.model()
                self.tableview.clearSpans()
                for r in range(model.rowCount(self.tableview.rootIndex())):
                    index = model.index(r, 0, self.tableview.rootIndex())
                    if index.data(QtCore.Qt.UserRole + 1000):
                        self.tableview.setSpan(r, 0, 1, 2)
    
        @QtCore.Slot(QtCore.QItemSelection)
        def handleSelectionChangedTV(self, item):
            ixs = item.indexes()
            if ixs:
                ix = ixs[0]
                if ix.data(QtCore.Qt.UserRole + 1000):
                    items = self.listview.model().findItems(ix.data())
                    if items:
                        self.tableview.clearSelection()
                        self.listview.selectionModel().select(items[0].index(), QtCore.QItemSelectionModel.ClearAndSelect)
    
    if __name__ == '__main__':
        import sys
        app = QtWidgets.QApplication(sys.argv)
        w = Widget()
        w.show()
        sys.exit(app.exec_())
    

    UPDATE:

    from PySide2 import QtCore, QtGui, QtWidgets
    
    dict_of_dicts={
        'dict1':{'k1':'v1', 'k2':'v2', 'k3':'v3', 'EXISTING_DICT': 'dict3'},
        'dict2':{'k4':'v4', 'EXISTING_DICT': 'dict1'},
        'dict3':{'k5':'v5', 'k6':'v6', 'k7':'v7', 'EXISTING_DICT': 'dict4'},
        'dict4':{'k8':'v8', 'EXISTING_DICT':'dict2'},
    }
    
    def create_model_from_dict(d, parent=None):
        model = QtGui.QStandardItemModel(0, 2, parent)
        for k, v in dict_of_dicts.items():
            it = QtGui.QStandardItem(k)
            model.appendRow(it)
            for k_, v_ in v.items():
                if k_ != "EXISTING_DICT":
                    it.appendRow([QtGui.QStandardItem(k_), QtGui.QStandardItem(v_)])
                else:
                    child_it = QtGui.QStandardItem(v_)
                    child_it.setTextAlignment(QtCore.Qt.AlignCenter)
                    child_it.setData(True, QtCore.Qt.UserRole + 1000)
                    it.appendRow(child_it)
        return model
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Widget, self).__init__(parent)
            model = create_model_from_dict(dict_of_dicts, self)
    
            self.tableview = QtWidgets.QTableView()
            self.tableview.setModel(model)
            self.tableview.doubleClicked.connect(self.handleSelectionChangedTV)
    
            self.listview = QtWidgets.QListView()
            self.listview.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
            self.listview.setModel(model)
            self.listview.selectionModel().selectionChanged.connect(self.handleSelectionChangedLV)
            self.listview.selectionModel().select(model.index(0, 0), QtCore.QItemSelectionModel.Select)
    
            hlay = QtWidgets.QHBoxLayout(self)
            hlay.addWidget(self.listview)
            hlay.addWidget(self.tableview)
    
        @QtCore.Slot(QtCore.QItemSelection)
        def handleSelectionChangedLV(self, item):
            ixs = item.indexes()
            if ixs:
                self.tableview.setRootIndex(ixs[0])
                model = self.tableview.model()
                self.tableview.clearSpans()
                for r in range(model.rowCount(self.tableview.rootIndex())):
                    index = model.index(r, 0, self.tableview.rootIndex())
                    if index.data(QtCore.Qt.UserRole + 1000):
                        self.tableview.setSpan(r, 0, 1, 2)
    
        @QtCore.Slot(QtCore.QModelIndex)
        def handleSelectionChangedTV(self, ix):
            if ix.data(QtCore.Qt.UserRole + 1000):
                items = self.listview.model().findItems(ix.data())
                if items:
                    self.tableview.clearSelection()
                    self.listview.selectionModel().select(items[0].index(), QtCore.QItemSelectionModel.ClearAndSelect)
    
    if __name__ == '__main__':
        import sys
        app = QtWidgets.QApplication(sys.argv)
        w = Widget()
        w.show()
        sys.exit(app.exec_())
    

    UPDATE2

    from PySide2 import QtCore, QtGui, QtWidgets
    
    dict_of_dicts={
        'dict1':{'k1':'v1', 'k2':'v2', 'k3':'v3', 'EXISTING_DICT': 'dict3'},
        'dict2':{'k4':'v4', 'EXISTING_DICT': 'dict1'},
        'dict3':{'k5':'v5', 'k6':'v6', 'k7':'v7', 'EXISTING_DICT': 'dict4'},
        'dict4':{'k8':'v8', 'EXISTING_DICT':'dict2'},
    }
    
    def create_model_from_dict(d, parent=None):
        model = QtGui.QStandardItemModel(0, 2, parent)
        for k, v in dict_of_dicts.items():
            it = QtGui.QStandardItem(k)
            model.appendRow(it)
            for k_, v_ in v.items():
                if k_ != "EXISTING_DICT":
                    it.appendRow([QtGui.QStandardItem(k_), QtGui.QStandardItem(v_)])
                else:
                    child_it = QtGui.QStandardItem(v_)
                    child_it.setTextAlignment(QtCore.Qt.AlignCenter)
                    child_it.setData(True, QtCore.Qt.UserRole + 1000)
                    it.appendRow(child_it)
        return model
    
    class TableView(QtWidgets.QTableView):
        leftDoubleClicked = QtCore.Signal(QtCore.QModelIndex)
    
        def mouseDoubleClickEvent(self, event):
            super(TableView, self).mouseDoubleClickEvent(event)
            if event.buttons() & QtCore.Qt.LeftButton:
                ix = self.indexAt(event.pos())
                if ix.isValid(): self.leftDoubleClicked.emit(ix)
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Widget, self).__init__(parent)
            model = create_model_from_dict(dict_of_dicts, self)
    
            self.tableview = TableView()
            self.tableview.setModel(model)
            self.tableview.leftDoubleClicked.connect(self.handleSelectionChangedTV)
    
            self.listview = QtWidgets.QListView()
            self.listview.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
            self.listview.setModel(model)
            self.listview.selectionModel().selectionChanged.connect(self.handleSelectionChangedLV)
            self.listview.selectionModel().select(model.index(0, 0), QtCore.QItemSelectionModel.Select)
    
            hlay = QtWidgets.QHBoxLayout(self)
            hlay.addWidget(self.listview)
            hlay.addWidget(self.tableview)
    
        @QtCore.Slot(QtCore.QItemSelection)
        def handleSelectionChangedLV(self, item):
            ixs = item.indexes()
            if ixs:
                self.tableview.setRootIndex(ixs[0])
                model = self.tableview.model()
                self.tableview.clearSpans()
                for r in range(model.rowCount(self.tableview.rootIndex())):
                    index = model.index(r, 0, self.tableview.rootIndex())
                    if index.data(QtCore.Qt.UserRole + 1000):
                        self.tableview.setSpan(r, 0, 1, 2)
    
        @QtCore.Slot(QtCore.QModelIndex)
        def handleSelectionChangedTV(self, ix):
            if ix.data(QtCore.Qt.UserRole + 1000):
                items = self.listview.model().findItems(ix.data())
                if items:
                    self.tableview.clearSelection()
                    self.listview.selectionModel().select(items[0].index(), QtCore.QItemSelectionModel.ClearAndSelect)
    
    if __name__ == '__main__':
        import sys
        app = QtWidgets.QApplication(sys.argv)
        w = Widget()
        w.show()
        sys.exit(app.exec_())