PyQt模型用于连接多个不同的QTableViews?

问题描述 投票:0回答:1

我有下面的代码中定义的类来显示一个Pandas DataFrame(用于表示2D表的数据结构)与MultiHeader(具有多个级别的标头),如下所示:

enter image description here

Excel中的数据可能是什么样的:enter image description here

我使用两个QTableView执行此操作,一个用于DataFrame本身的数据,另一个用于MultiHeader标签。但是,我希望能够将DataFrame存储在单个模型中,并将这些多个QTableView连接到它。理想情况下,我可以从视图中向data()方法传递一个额外的参数,指示视图是用于标题还是正文,但我不认为这是可能的?

我想将它们组合成一个模型的一些原因......

  • DataFrame不会在标题和正文之间同步。换句话说,header.model().df is data.model().df开始是真的,但是一旦调用delete_first_column并且self.df被覆盖,则为假
  • 在结构上它更有意义,因为DataFrame是单个对象
  • 这种当前结构需要跨两个模型进行代码复制和通信,例如delete_first_column()应该应用于正文和标题,但是它只适用于它所在的模型。

如何重构此代码,以便视图仅连接到单个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_())
python pandas pyqt pyqt5 qtableview
1个回答
0
投票

我不确定我能否明确地回答这个问题,但我尝试的方法是保留你已经拥有的两个模型和两个视图,然后创建一个新的模型并查看在统一接口上的行为。实际上,它们只是对现有模型和视图的包装。因此,如果有人在您的包装器类上调用delete_first_column(),它会处理将其传递给底层主体和标头的细节,使它们保持同步。

如果您喜欢冒险,可以使用QTableView.setSpan()在视图中创建合并列的外观。

© www.soinside.com 2019 - 2024. All rights reserved.