Synchronize pandas DataFrame with PyQt5 QTableView

ⅰ亾dé卋堺 提交于 2020-07-23 06:51:05

问题


I have an editable QTableView which reads the values from a pandas DataFrame.

What I'm looking for is that when I change the value of one cell, the pandas DataFrame synchronizes automatically. The aim is to continue operating with the updated DataFrame, for example, to update the values of the other rows of the DataFrame and refresh the QTableView.

I've seen that there are similar questions in the forum, but since MVC is new to me and I'm not a professional programmer, I find it hard to understand the solutions and I haven't managed to implement them in my code.

I paste here the code I have so far.

run.py

import pandas as pd
import sys

from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableView

from cnt_init_values import CntInitialValues

from tablemodel_editable import PandasModel


class MainWindow(QMainWindow):

    def __init__(self, parent=None):

        QMainWindow.__init__(self, parent)
                    
        # Packages.
        # ---------
        self.pd = pd
        
        # Instancies of class.
        # --------------------
        self.cnt_init_val = CntInitialValues(self)
        
        # Objects of the aplication.
        # --------------------------
        self.df_table = pd.DataFrame()
        
        # PyQt5 objects.
        # --------------
        self.tableview = QTableView()
        
        # Add initial values to QTableView and show them.
        # -----------------------------------------------
        self.df_table = self.cnt_init_val.dataframe_initial()
        self.cnt_init_val.initial_values(self.df_table)

    # PyQt5 outputs.
    # ==============
    
    def table_output(self, df):
        
        print(self.df_table)
        
        model = PandasModel(df)
        self.tableview.setModel(model)
        self.tableview.resize(350, 250)
        self.tableview.show()

        
if __name__ == '__main__':

    app = QApplication(sys.argv)
    myapp = MainWindow()
    sys.exit(app.exec_())

cnt_init_values.py

from initialvalues import InitialValues

class CntInitialValues:
    """Controller"""
    
    def __init__(self, window):
        
        self.w = window
        
    def dataframe_initial(self):
        
        d_initial_values = InitialValues.d_INITIAL_VALUES
        df_initial = self.w.pd.DataFrame(d_initial_values)
        
        return df_initial
        
    def initial_values(self, df):
        
        self.w.table_output(df)

initialvalues.py

class InitialValues:
    
    vals = [float(i) for i in range(6)]
    
    d_INITIAL_VALUES = {
        'x': vals,
        'x²': [val**2 for val in vals],
        'x³': [val**3 for val in vals]
        }

tablemodel_editable.py

from PyQt5.QtCore import QAbstractTableModel, Qt


class PandasModel(QAbstractTableModel):

    def __init__(self, data):
        QAbstractTableModel.__init__(self)
        self._data = data

    def rowCount(self, parent=None):
        return self._data.shape[0]

    def columnCount(self, parnet=None):
        return self._data.shape[1]

    def data(self, index, role=Qt.DisplayRole):
        if index.isValid():

            if role == Qt.DisplayRole:

                return str(self._data.iloc[index.row(), index.column()])

            column_count = self.columnCount()
            
            for column in range(0, column_count):
                
                if (index.column() == column and role == Qt.TextAlignmentRole):

                    return Qt.AlignHCenter | Qt.AlignVCenter

        return None

    def headerData(self, col, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self._data.columns[col]
        return None

    def setData(self, index, value, role):
        if not index.isValid():
            return False
        
        if role != Qt.EditRole:
            return False
        
        row = index.row()
        
        if row < 0 or row >= len(self._data.values):
            return False
        
        column = index.column()
        
        if column < 0 or column >= self._data.columns.size:
            
            return False
        
        self._data.iloc[row][column] = value
        self.dataChanged.emit(index, index)
        
        return True

    def flags(self, index):
        flags = super(self.__class__,self).flags(index)
        flags |= Qt.ItemIsEditable
        flags |= Qt.ItemIsSelectable
        flags |= Qt.ItemIsEnabled
        flags |= Qt.ItemIsDragEnabled
        flags |= Qt.ItemIsDropEnabled
        
        return flags

回答1:


The solution is so simple that I don't understand it. If someone could just explain to me what's going on.

I point out the changes I've made in run.py:

run.py edited

class MainWindow(QMainWindow):

    def __init__(self, parent=None):

        QMainWindow.__init__(self, parent)
        uic.loadUi('table.ui', self)  # --- Loading from Qt Designer. ---
        
        # Packages.
        # ---------
        self.pd = pd
        
        # Instancies of class.
        # --------------------
        self.cnt_init_val = CntInitialValues(self)
                    
        # Attributes of the instance.
        # ---------------------------
        self.df_table = pd.DataFrame()
        self.model = ''  # --- Instance variable for model. ---
        
        # PyQt5 objects.
        # --------------
        self.tableview = self.tableView
        self.btn_print_df = self.pushButtonPrintDataFrame  # --- pushButton to print DataFrame. ---
        
        # Add initial values to QTableView and show them.
        # -----------------------------------------------
        self.df_table = self.cnt_init_val.dataframe_initial()
        self.cnt_init_val.initial_values(self.df_table)
        
        # Events.
        # -------
        self.btn_print_df.clicked.connect(self.dataframe_output) # --- pushButton prints DataFrame. ---

    # PyQt5 outputs.
    # ==============
    
    def table_output(self, df):
        
        print('\nInitial DataFrame:')
        print(self.df_table)
        
        self.model = PandasModel(df)
        self.tableview.setModel(self.model)
        self.tableview.show()
    
    # Terminal outputs.  # --- DaraFrame output. ---
    # =================
        
    def dataframe_output(self):
        
        print('\nUpdated DataFrame:')
        print(self.df_table)

Firstly, when the app is executed:

Initial DataFrame:
     x    x²     x³
0  0.0   0.0    0.0
1  1.0   1.0    1.0
2  2.0   4.0    8.0
3  3.0   9.0   27.0
4  4.0  16.0   64.0
5  5.0  25.0  125.0

After changing some values in the table:

Updated DataFrame:
       x    x²      x³
0  999.0   0.0     0.0
1    1.0   1.0     1.0
2    2.0   4.0     8.0
3    3.0   9.0    27.0
4    4.0  16.0    64.0
5    5.0  25.0  1111.0

Now I have to work with the DataFrame to make the values of the rows consistent, but that's another question.



来源:https://stackoverflow.com/questions/62812459/synchronize-pandas-dataframe-with-pyqt5-qtableview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!