问题
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