Search code examples
pythonpandaspyside2qabstracttablemodelqsortfilterproxymodel

Updating QTableView on the fly when data source changes with a QSortFilterProxyModel in between


I'm trying to update a QTableView when the data source (Pandas Dataframe) changes. I'm using a QAbstractTableModel as the "base" table model and a QSortFilterProxyModel to do some filtering.

Somewhen during runtime the data source changes. According to that my goal is to reset the "base" table model -> notify the proxy model -> update the QTableView.

I tried the following code, but this doesn't change the TableView after calling on_event() in the MainWindow class.

Below is the executable code...

import sys

import pandas as pd
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtCore import Qt

class DataStore(object):
    def __init__(self):

        data = {'Name':  ['name1', 'name2',"name3"],
        'Value': ['value1', 'value2',"value3"],
            'Setting': ["TRUE", "TRUE","FALSE"]
        }

        df = pd.DataFrame (data, columns = ['Name','Value','Setting'])

        self.df = df
    
        self.model = TableModel(self.df)
        self.proxy_model = ProxyModel(self.model)
        self.proxy_model.setSourceModel(self.model)
        self.proxy_model.setDynamicSortFilter(True)




class ProxyModel(QtCore.QSortFilterProxyModel):
    def __init__(self, parent):
        QtCore.QSortFilterProxyModel.__init__(self, parent)

    # Random filtering
    def filterAcceptsRow(self, sourceRow, sourceParent):
        print("Entered filterAcceptsRow function with source row: {}".format(sourceRow))
        idx = self.sourceModel().index(sourceRow, 2, sourceParent)
        value = idx.data()
        if value == "TRUE":
            return True
        if value == "FALSE":
            return False
    

class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self.__data = data

    def data(self, index, role):
        if role == Qt.DisplayRole:
            value = self.__data.iloc[index.row(), index.column()]
            return str(value)

    def rowCount(self, index):
        return self.__data.shape[0]

    def columnCount(self, index):
        return self.__data.shape[1]

    def headerData(self, section, orientation, role):
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return str(self.__data.columns[section])

            if orientation == Qt.Vertical:
                return str(self.__data.index[section])




class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.ds = DataStore()
        self.table = QtWidgets.QTableView()       
        self.table.setModel(self.ds.proxy_model)
        self.on_event()
        self.setCentralWidget(self.table)
        self.setGeometry(600, 100, 400, 200)

    def on_event(self):
        data = {'Name':  ['new_name1', 'new_name2',"new_name3"],
        'Value': ['new_value1', 'new_value2',"new_value3"],
        'Description': ['new_descr1', 'new_descr2',"new_descr3"],
            'Setting': ["TRUE", "FALSE","TRUE"]
        }

        new_df = pd.DataFrame (data, columns = ['Name','Value','Setting'])
        self.ds.model.beginResetModel()
        self.ds.df = new_df
        self.ds.model.endResetModel()

app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

Solution

  • The problem is not the QSortFilterProxyModel but the information is not being updated in the sourceModel, you are copying the df to the DataStore but that updates the TableModel information causing that error.

    import sys
    
    import pandas as pd
    from PySide2 import QtCore, QtGui, QtWidgets
    from PySide2.QtCore import Qt
    
    
    class DataStore(object):
        def __init__(self):
    
            data = {
                "Name": ["name1", "name2", "name3"],
                "Value": ["value1", "value2", "value3"],
                "Setting": ["TRUE", "TRUE", "FALSE"],
            }
    
            df = pd.DataFrame(data, columns=["Name", "Value", "Setting"])
            self.model = TableModel(df)
            self.proxy_model = ProxyModel()
            self.proxy_model.setSourceModel(self.model)
            self.proxy_model.setDynamicSortFilter(True)
    
        @property
        def df(self):
            return self.model.df
    
        @df.setter
        def df(self, df):
            self.model.df = df
    
    
    class ProxyModel(QtCore.QSortFilterProxyModel):
        def filterAcceptsRow(self, sourceRow, sourceParent):
            print("Entered filterAcceptsRow function with source row: {}".format(sourceRow))
            idx = self.sourceModel().index(sourceRow, 2, sourceParent)
            value = idx.data()
            if value == "TRUE":
                return True
            if value == "FALSE":
                return False
    
    
    class TableModel(QtCore.QAbstractTableModel):
        def __init__(self, data):
            super().__init__()
            self.__data = data
    
        @property
        def df(self):
            return self.__data
    
        @df.setter
        def df(self, df):
            self.beginResetModel()
            self.__data = df.copy()
            self.endResetModel()
    
        def data(self, index, role):
            if role == Qt.DisplayRole:
                value = self.__data.iloc[index.row(), index.column()]
                return str(value)
    
        def rowCount(self, index):
            return self.__data.shape[0]
    
        def columnCount(self, index):
            return self.__data.shape[1]
    
        def headerData(self, section, orientation, role):
            if role == Qt.DisplayRole:
                if orientation == Qt.Horizontal:
                    return str(self.__data.columns[section])
    
                if orientation == Qt.Vertical:
                    return str(self.__data.index[section])
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
            self.ds = DataStore()
            self.table = QtWidgets.QTableView()
            self.table.setModel(self.ds.proxy_model)
            self.on_event()
            self.setCentralWidget(self.table)
            self.setGeometry(600, 100, 400, 200)
    
        def on_event(self):
            data = {
                "Name": ["new_name1", "new_name2", "new_name3"],
                "Value": ["new_value1", "new_value2", "new_value3"],
                "Description": ["new_descr1", "new_descr2", "new_descr3"],
                "Setting": ["TRUE", "FALSE", "TRUE"],
            }
    
            new_df = pd.DataFrame(data, columns=["Name", "Value", "Setting"])
            self.ds.df = new_df
    
    
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()