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_()
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_()