我有下面的代码中定义的类来显示一个Pandas DataFrame(用于表示2D表的数据结构)与MultiHeader(具有多个级别的标头),如下所示:
我使用两个QTableView执行此操作,一个用于DataFrame本身的数据,另一个用于MultiHeader标签。但是,我希望能够将DataFrame存储在单个模型中,并将这些多个QTableView连接到它。理想情况下,我可以从视图中向data()方法传递一个额外的参数,指示视图是用于标题还是正文,但我不认为这是可能的?
我想将它们组合成一个模型的一些原因......
header.model().df is data.model().df
开始是真的,但是一旦调用delete_first_column
并且self.df
被覆盖,则为假如何重构此代码,以便视图仅连接到单个DataFrame的单个模型?
from PyQt5 import QtGui, QtCore, QtWidgets
import pandas as pd
import numpy as np
import sys
# DataTableModel and DataTableView show the data in the rows of the DataFrame
class DataTableModel(QtCore.QAbstractTableModel):
"""
Model for DataTableView to connect for DataFrame data
"""
def __init__(self, df, parent=None):
super().__init__(parent)
self.df = df
# Headers for DataTableView are hidden. Header data is shown in HeaderView
def headerData(self, section, orientation, role=None):
pass
def columnCount(self, parent=None):
return len(self.df.columns)
def rowCount(self, parent=None):
return len(self.df)
# Returns the data from the DataFrame
def data(self, index, role=None):
if role == QtCore.Qt.DisplayRole:
row = index.row()
col = index.column()
cell = self.df.iloc[row, col]
return str(cell)
class DataTableView(QtWidgets.QTableView):
def __init__(self, df):
super().__init__()
# Create and set model
model = DataTableModel(df)
self.setModel(model)
# Hide the headers. The DataFrame headers (index & columns) will be displayed in the DataFrameHeaderViews
self.horizontalHeader().hide()
self.verticalHeader().hide()
# HeaderModel and HeaderView show the header of the DataFrame, in this case a 3 level header
class HeaderModel(QtCore.QAbstractTableModel):
def __init__(self, df):
super().__init__()
self.df = df
def columnCount(self, parent=None):
return len(self.df.columns.values)
def rowCount(self, parent=None):
if type(self.df.columns) == pd.MultiIndex:
if type(self.df.columns.values[0]) == tuple:
return len(self.df.columns.values[0])
else:
return 1
def data(self, index, role):
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.ToolTipRole:
if type(self.df.columns) == pd.MultiIndex:
row = index.row()
col = index.column()
return str(self.df.columns.values[col][row])
else: # Not MultiIndex
col = index.column()
return str(self.df.columns.values[col])
# A simple example of some way this model might modify its data
def delete_first_column(self):
self.beginResetModel()
self.df = self.df.drop(self.df.columns[0], axis=1)
self.endResetModel()
class HeaderView(QtWidgets.QTableView):
def __init__(self, df):
super().__init__()
self.setModel(HeaderModel(df))
self.clicked.connect(self.model().delete_first_column)
self.horizontalHeader().hide()
self.verticalHeader().hide()
self.setFixedHeight(115)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.setStyle('Windows XP')
tuples = [('A', 'one', 'x'), ('A', 'one', 'y'), ('A', 'two', 'x'), ('A', 'two', 'y'),
('B', 'one', 'x'), ('B', 'one', 'y'), ('B', 'two', 'x'), ('B', 'two', 'y')]
columns = pd.MultiIndex.from_tuples(tuples, names=['first', 'second', 'third'])
multidf = pd.DataFrame(np.arange(40).reshape(5,8), columns=columns[:8])
container = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
container.setLayout(layout)
header = HeaderView(multidf)
data = DataTableView(multidf)
layout.addWidget(header)
layout.addWidget(data)
print(header.model().df is data.model().df)
container.show()
sys.exit(app.exec_())
我不确定我能否明确地回答这个问题,但我尝试的方法是保留你已经拥有的两个模型和两个视图,然后创建一个新的模型并查看在统一接口上的行为。实际上,它们只是对现有模型和视图的包装。因此,如果有人在您的包装器类上调用delete_first_column(),它会处理将其传递给底层主体和标头的细节,使它们保持同步。
如果您喜欢冒险,可以使用QTableView.setSpan()在视图中创建合并列的外观。