Обновление QTableView на лету при изменении источника данных с помощью промежуточной модели QSortFilterProxyModel

#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 ?