Update treemodel in real time PySide

198 Views Asked by At

How can I make it so when I click the Randomize button, for the selected treeview items, the treeview updates to show the changes to data, while maintaining the expanding items states and the users selection? Is this accomplished by subclasses the StandardItemModel or ProxyModel class? Help is much appreciated as I'm not sure how to resolve this issue.

It's a very simple example demonstrating the issue. When clicking Randmoize, all it's doing is randomly assigning a new string (name) to each coaches position on the selected Team.

enter image description here

import os
import sys
import random

from PySide2 import QtGui, QtWidgets, QtCore
        

class Team(object):

    def __init__(self, name='', nameA='', nameB='', nameC='', nameD=''):
        super(Team, self).__init__()
        self.name = name
        self.headCoach = nameA
        self.assistantCoach = nameB
        self.offensiveCoach = nameC
        self.defensiveCoach = nameD


    def randomize(self):
        names = ['doug', 'adam', 'seth', 'emily', 'kevin', 'mike', 'sarah', 'cassy', 'courtney', 'henry']
        cnt = len(names)-1
        self.headCoach = names[random.randint(0, cnt)]
        self.assistantCoach = names[random.randint(0, cnt)]
        self.offensiveCoach = names[random.randint(0, cnt)]
        self.defensiveCoach = names[random.randint(0, cnt)]
        print('TRADED PLAYERS')


TEAMS = [
    Team('Cowboys', 'doug', 'adam', 'seth', 'emily'),
    Team('Packers'),
    Team('Lakers', 'kevin', 'mike', 'sarah', 'cassy'),
    Team('Yankees', 'courtney', 'henry'),
    Team('Gators'),
]

class MainDialog(QtWidgets.QMainWindow):

    def __init__(self, parent=None):
        super(MainDialog, self).__init__(parent)
        self.resize(600,400)

        self.button = QtWidgets.QPushButton('Randomize')

        self.itemModel = QtGui.QStandardItemModel()

        self.proxyModel = QtCore.QSortFilterProxyModel()
        self.proxyModel.setSourceModel(self.itemModel)
        self.proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.proxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.proxyModel.setDynamicSortFilter(True)
        self.proxyModel.setFilterKeyColumn(0)

        self.treeView = QtWidgets.QTreeView()
        self.treeView.setModel(self.proxyModel)
        self.treeView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
        self.treeView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
        self.treeView.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
        self.treeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
        self.treeView.setAlternatingRowColors(True)
        self.treeView.setSortingEnabled(True)
        self.treeView.setUniformRowHeights(False)
        self.treeView.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
        self.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)

        self.selectionModel = self.treeView.selectionModel()

        # layout
        self.mainLayout = QtWidgets.QVBoxLayout()
        self.mainLayout.addWidget(self.treeView)
        self.mainLayout.addWidget(self.button)
        self.mainWidget = QtWidgets.QWidget()
        self.mainWidget.setLayout(self.mainLayout)
        self.setCentralWidget(self.mainWidget)

        # connections
        self.selectionModel.selectionChanged.connect(self.updateControls)
        self.button.clicked.connect(self.randomizeTeams)

        # begin
        self.populateModel()
        self.updateControls()


    def randomizeTeams(self):
        for proxyIndex in self.selectionModel.selectedRows():
            sourceIndex = self.proxyModel.mapToSource(proxyIndex)
            item = self.itemModel.itemFromIndex(sourceIndex)
            team = item.data(QtCore.Qt.UserRole)
            team.randomize()

            # UPDATE UI...


    def updateControls(self):
        self.button.setEnabled(self.selectionModel.hasSelection())


    def populateModel(self):
        self.itemModel.clear()
        self.itemModel.setHorizontalHeaderLabels(['Position', 'Name'])

        # add teams
        for ts in TEAMS:
            col1 = QtGui.QStandardItem(ts.name)
            col1.setData(ts, QtCore.Qt.UserRole)

            # add coaches
            childCol1 = QtGui.QStandardItem('Head Coach')
            childCol1.setData(ts, QtCore.Qt.UserRole)
            childCol2 = QtGui.QStandardItem(ts.headCoach)
            col1.appendRow([childCol1, childCol2])
            
            childCol1 = QtGui.QStandardItem('Head Coach')
            childCol1.setData(ts, QtCore.Qt.UserRole)
            childCol2 = QtGui.QStandardItem(ts.assistantCoach)
            col1.appendRow([childCol1, childCol2])

            childCol1 = QtGui.QStandardItem('Offensive Coach')
            childCol1.setData(ts, QtCore.Qt.UserRole)
            childCol2 = QtGui.QStandardItem(ts.offensiveCoach)
            col1.appendRow([childCol1, childCol2])
            
            childCol1 = QtGui.QStandardItem('Defensive Coach')
            childCol1.setData(ts, QtCore.Qt.UserRole)
            childCol2 = QtGui.QStandardItem(ts.defensiveCoach)
            col1.appendRow([childCol1, childCol2])

            self.itemModel.appendRow([col1])

        self.itemModel.setSortRole(QtCore.Qt.DisplayRole)
        self.itemModel.sort(0, QtCore.Qt.AscendingOrder)
        self.proxyModel.sort(0, QtCore.Qt.AscendingOrder)


def main():
    app = QtWidgets.QApplication(sys.argv)
    window = MainDialog()
    window.show()
    app.exec_()


if __name__ == '__main__':
    pass
    main()
1

There are 1 best solutions below

0
ekhumoro On

Your Team class should be a subclass of QStandardItem, which will be the top-level parent in the model. This class should create its own child items (as you are currently doing in the for-loop of populateModel), and its randomize method should directly reset the item-data of those children. This will ensure the changes are immediately reflected in the model.

So - it's really just a matter of taking the code you already have and refactoring it accordingly. For example, something like this should work:

TEAMS = {
    'Cowboys': ('doug', 'adam', 'seth', 'emily'),
    'Packers': (),
    'Lakers': ('kevin', 'mike', 'sarah', 'cassy'),
    'Yankees': ('courtney', 'henry'),
    'Gators': (),
    }

class Team(QtGui.QStandardItem):
    def __init__(self, name):
        super(Team, self).__init__(name)
        for coach in ('Head', 'Assistant', 'Offensive', 'Defensive'):
            childCol1 = QtGui.QStandardItem(f'{coach} Coach')
            childCol2 = QtGui.QStandardItem()
            self.appendRow([childCol1, childCol2])

    def populate(self, head='', assistant='', offensive='', defensive=''):
        self.child(0, 1).setText(head)
        self.child(1, 1).setText(assistant)
        self.child(2, 1).setText(offensive)
        self.child(3, 1).setText(defensive)

    def randomize(self, names):
        self.populate(*random.sample(names, 4))

class MainDialog(QtWidgets.QMainWindow):
    ...
    def randomizeTeams(self):
        for proxyIndex in self.selectionModel.selectedRows():
            sourceIndex = self.proxyModel.mapToSource(proxyIndex)
            item = self.itemModel.itemFromIndex(sourceIndex)
            if not isinstance(item, Team):
                item = item.parent()
            item.randomize(self._coaches)

    def populateModel(self):
        self.itemModel.clear()
        self.itemModel.setHorizontalHeaderLabels(['Position', 'Name'])
        self._coaches = []
        # add teams
        for name, coaches in TEAMS.items():
            team = Team(name)
            team.populate(*coaches)
            self._coaches.extend(coaches)
            self.itemModel.appendRow([team])
        self.itemModel.setSortRole(QtCore.Qt.DisplayRole)
        self.itemModel.sort(0, QtCore.Qt.AscendingOrder)
        self.proxyModel.sort(0, QtCore.Qt.AscendingOrder)