【问题标题】:PyQt model for connecting to multiple different QTableViews?用于连接多个不同 QTableViews 的 PyQt 模型?
【发布时间】:2019-09-13 06:50:34
【问题描述】:

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

数据在 Excel 中的样子:

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

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

  • DataFrame 不同步标头和正文。换句话说,header.model().df is data.model().df 开始时为 True,但一旦调用 delete_first_column 并且 self.df 被覆盖,则为 False
  • 在结构上更有意义,因为 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】:

    我不确定我是否能明确回答这个问题,但我尝试的方法是保留您已有的两个模型和两个视图,然后创建一个新模型和视图以在统一界面上运行。事实上,它们只是您现有模型和视图的包装器。因此,如果有人在您的包装类上调用 delete_first_column(),它会处理将其传递给底层正文和标题的细节,使它们保持同步。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-04
      • 2014-01-23
      • 1970-01-01
      • 2016-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多