#python #pandas #pyside2 #qabstracttablemodel #qsortfilterproxymodel
Вопрос:
Я пытаюсь обновить QTableView при изменении источника данных (фрейм данных Pandas). Я использую QAbstractTableModel в качестве «базовой» модели таблицы и QSortFilterProxyModel для выполнения некоторой фильтрации.
Иногда во время выполнения источник данных меняется. В соответствии с этим моя цель состоит в том, чтобы сбросить «базовую» табличную модель -> уведомить модель прокси ->> обновить QTableView.
Я попробовал следующий код, но это не изменяет представление таблицы после вызова on_event() в классе MainWindow.
Ниже приведен исполняемый код…
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_()
Ответ №1:
Проблема не в QSortFilterProxyModel, но информация не обновляется в исходной модели, вы копируете df в хранилище данных, но это обновляет информацию о табличной модели, вызывая эту ошибку.
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_()
Комментарии:
1. Привет, спасибо, это работает. Итак, правильно ли я понимаю, что вы никогда не сохраняете фрейм данных в хранилище данных? И что вы на самом деле копируете с помощью этой строки «self.ds.df = new_df» new_df в атрибут класса __данные табличной модели?
2. Что я ожидал от своего решения, так это то, что источник данных (df) хранится в хранилище данных, а табличная модель содержит ссылку на этот df в (__data). Если сейчас df изменит __данные также должны измениться. Где и почему это работает не так, как ожидалось?
3. @FelixD Именно потому, что делает
obj.foo = bar
только копии, не указывает, что они используют один и тот же объект.4. Хорошо, но зачем нам тогда писать
self.__data = df.copy()
, если, как вы говорите, этоobj.foo = bar
просто копии без ссылок?5.@FelixD Чтобы вы меня поняли, ваша логика такова:
df = pd.DataFrame(...)
foo = df
bar = df
в этом коде вы присваиваете один и тот же объект фрейма данных 2 переменным (на самом деле 3 , если мы также учитываем df, но в вашем случае это локальная переменная, поэтому это не имеет значения). И затем вы делаетеfoo = new_df
то, что вы назначаете объекту фрейма данных foo, почему это означает, что с помощью этого кодаnew_df
назначаетсяbar
?