【问题标题】:How to Use QListView to Select Which Columns are Displayed in QTableView如何使用 QListView 选择 QTableView 中显示哪些列
【发布时间】:2020-05-13 10:09:30
【问题描述】:

假设我有以下数据框:

df = {'Banana': {0: 1, 1: 2, 2: 5}, 'Apple': {0: 3, 1: 4, 2: 3}, 'Elderberry': {0: 5, 1: 4, 2: 1},
'Clementine': {0: 4, 1: 7, 2: 0}, 'Fig': {0: 1, 1: 9, 2: 3}}
   Banana  Apple  Elderberry  Clementine  Fig
0       1      3           5           4    1
1       2      4           4           7    9
2       5      3           1           0    3

传递给QAbstractTableModel并与QTableView一起显示:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.Qt import Qt
import pandas as pd


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, model):
        super().__init__()
        self.model = model
        self.table = QtWidgets.QTableView()
        self.table.setModel(self.model)
        self.setCentralWidget(self.table)


class TableModel(QtCore.QAbstractTableModel):

    def __init__(self, data):
        super(TableModel, self).__init__()
        self._data = data

    def rowCount(self, index):
        return self._data.shape[0]

    def columnCount(self, index):
        return self._data.shape[1]

    def headerData(self, section, orientation, role):
        # section is the index of the column/row.
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return str(self._data.columns[section])

            if orientation == Qt.Vertical:
                return str(self._data.index[section])

    def data(self, index, role):

        row = index.row()
        column = index.column()
        column_name = self._data.columns[column]
        value = self._data.iloc[row, column]

        if role == Qt.DisplayRole:
            print(str(value))
            return str(value)


app = QtWidgets.QApplication(sys.argv)
df = {'Banana': {0: 1, 1: 2, 2: 5}, 'Apple': {0: 3, 1: 4, 2: 3}, 'Elderberry': {0: 5, 1: 4, 2: 1}, 'Clementine': {0: 4, 1: 7, 2: 0}, 'Fig': {0: 1, 1: 9, 2: 3}}
data_model = TableModel(df)
window1 = MainWindow(data_model)
window1.resize(800, 400)
window1.show()
sys.exit(app.exec_())

我现在想使用QListView 创建一个单独的窗口,以显示df 列名的列表,允许我指定要在上面的QTableView 中出现的列。 Like this

如果我取消选中 QListView 中的列,我希望从 QTableView 中删除该列。同样,如果我检查一列,则该列应添加到表视图中。列表视图的目的本质上是允许用户指定哪些列应该出现在表格中。

这样做的最佳方法是什么?我想我可以创建一个QListWidget 并使用信号来更新QTableView。但是我有点不愿意这样做,因为我想它会变得非常混乱。

因此最佳做法是使用QListView,以便两个小部件引用相同的数据模型,并自动talk to each other like this

【问题讨论】:

    标签: python pyqt pyqt5


    【解决方案1】:

    您必须使用代理模型为列表视图构建模型(转置原始模型,将标题映射到单行或列并添加复选框)和另一个过滤:

    import sys
    from PyQt5 import QtCore, QtGui, QtWidgets
    import pandas as pd
    
    
    class TableModel(QtCore.QAbstractTableModel):
        def __init__(self, data, parent=None):
            super(TableModel, self).__init__(parent)
            self._data = data
    
        def rowCount(self, index):
            return self._data.shape[0]
    
        def columnCount(self, index):
            return self._data.shape[1]
    
        def headerData(self, section, orientation, role):
            if role == QtCore.Qt.DisplayRole:
                if orientation == QtCore.Qt.Horizontal:
                    return str(self._data.columns[section])
                if orientation == QtCore.Qt.Vertical:
                    return str(self._data.index[section])
    
        def data(self, index, role):
            row = index.row()
            column = index.column()
            value = self._data.iloc[row, column]
            if role == QtCore.Qt.DisplayRole:
                return str(value)
    
    
    class CustomProxy(QtCore.QSortFilterProxyModel):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.show_columns = set()
    
        def show_column(self, c):
            self.show_columns.add(c)
            self.invalidateFilter()
    
        def hide_column(self, c):
            if c not in self.show_columns:
                return
            self.show_columns.remove(c)
            self.invalidateFilter()
    
        def filterAcceptsColumn(self, source_column, source_parent):
            return source_column in self.show_columns
    
    
    class HeaderProxyModel(QtCore.QIdentityProxyModel):
        checked = QtCore.pyqtSignal(int, bool)
    
        def __init__(self, parent=None):
            super().__init__(parent)
            self.checks = {}
    
        def columnCount(self, index=QtCore.QModelIndex()):
            return 1
    
        def data(self, index, role=QtCore.Qt.DisplayRole):
            if role == QtCore.Qt.DisplayRole:
                return self.headerData(index.row(), QtCore.Qt.Vertical, role)
            elif role == QtCore.Qt.CheckStateRole and index.column() == 0:
                return self.checks.get(
                    QtCore.QPersistentModelIndex(index), QtCore.Qt.Unchecked
                )
    
        def setData(self, index, value, role=QtCore.Qt.EditRole):
    
            if not index.isValid():
                return False
            if role == QtCore.Qt.CheckStateRole:
                self.checks[QtCore.QPersistentModelIndex(index)] = value
                self.checked.emit(index.row(), bool(value))
                return True
            return False
    
        def flags(self, index):
            fl = super().flags(index)
            if index.column() == 0:
                fl |= QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsUserCheckable
            return fl
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, model):
            super().__init__()
            self.model = model
    
            self.listview = QtWidgets.QListView()
            tranpose = QtCore.QTransposeProxyModel()
            tranpose.setSourceModel(self.model)
            header_model = HeaderProxyModel()
            header_model.setSourceModel(tranpose)
            self.listview.setModel(header_model)
    
            self.tableview = QtWidgets.QTableView()
            self.filter_proxy = CustomProxy()
            self.filter_proxy.setSourceModel(self.model)
            self.tableview.setModel(self.filter_proxy)
    
            header_model.checked.connect(self.on_checked)
    
            central_widget = QtWidgets.QWidget()
            hlay = QtWidgets.QHBoxLayout(central_widget)
            hlay.addWidget(self.listview)
            hlay.addWidget(self.tableview, stretch=1)
            self.setCentralWidget(central_widget)
    
        def on_checked(self, r, state):
            if state:
                self.filter_proxy.show_column(r)
            else:
                self.filter_proxy.hide_column(r)
    
    
    if __name__ == "__main__":
    
        app = QtWidgets.QApplication(sys.argv)
        df = pd.DataFrame(
            {
                "Banana": {0: 1, 1: 2, 2: 5},
                "Apple": {0: 3, 1: 4, 2: 3},
                "Elderberry": {0: 5, 1: 4, 2: 1},
                "Clementine": {0: 4, 1: 7, 2: 0},
                "Fig": {0: 1, 1: 9, 2: 3},
            }
        )
        data_model = TableModel(df)
        window1 = MainWindow(data_model)
        window1.resize(800, 400)
        window1.show()
        sys.exit(app.exec_())
    

    【讨论】:

    • 如果我理解正确的话,代理模型位于模型和视图之间,如下所示:数据 --> 模型 --> 代理 --> 视图,允许为视图配置模型无需实际更改模型。它是否正确?您的代码是有道理的,但是当复选框被选中或未选中时,我无法理解发生了什么。如果添加了一列,整个表视图是否会刷新,或者模型视图是否只绘制附加列?另外,我可以在代码中的哪里设置复选框的默认状态(目前它们都未选中作为默认状态)?
    • 我给你发了一封电子邮件
    • @Alan 好的,我会审查它,我会在几个小时内回复。
    猜你喜欢
    • 2019-03-11
    • 2023-03-25
    • 2019-02-09
    • 2011-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-09
    • 1970-01-01
    相关资源
    最近更新 更多