如何re-arrange QTableView的列顺序

How to re-arrange QTableView's Columns Order

下面的代码创建了一个由 Model/Proxy 框架驱动的 QTableView。

source-model 中声明的 self.headerNames list-variable 存储 Header 的列的名称。此列表中的名称数由相同的 source modelcolumnCount() 方法用于 return 视图中的列数:

def columnCount(self, parent=QModelIndex()):
    return len(self.headerNames)

Proxy 模型的 headerData() 通过 source model 访问此 self.headerNames 变量:

sourceModel=self.sourceModel()

if role==Qt.DisplayRole 上,代理检索并return将列的名称发送到 QTableView:

return QVariant( sourceModel.headerNames[column] )

在 header-column right-click 上实现了一个 right-click 菜单。 那部分工作正常。但是由于我找不到任何关于其他人如何做的例子,我不得不自己设计它的工作方式。如果您发现它可以改进,我将不胜感激。

接下来我要实现的是 re-arrange 以任意顺序排列列的能力。但我不确定从哪里开始。

P.s。请忽略 QTableView 中显示的项目的名称。 我想让代码尽可能简单,只关注 Header/Column 主题。

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys

class Model(QAbstractTableModel):
    def __init__(self, parent=None, *args):
        QAbstractTableModel.__init__(self, parent, *args)
        self.items = ['Item_A_001','Item_A_002','Item_B_001','Item_B_002']
        self.headerNames=['Column 0','Column 1','Column 2','Column 3','Column 4','Column 5','Column 6','Column 7']

    def rowCount(self, parent=QModelIndex()):
        return len(self.items)       
    def columnCount(self, parent=QModelIndex()):
        return len(self.headerNames)

    def data(self, index, role):
        if not index.isValid(): return QVariant()
        elif role != Qt.DisplayRole:
            return QVariant()

        row=index.row()
        if row<len(self.items):
            return QVariant(self.items[row])
        else:
            return QVariant()

class Proxy(QSortFilterProxyModel):
    def __init__(self):
        super(Proxy, self).__init__()

    def filterAcceptsRow(self, row, parent):
        return True

    def headerData(self, column, orientation, role=Qt.DisplayRole):
        sourceModel=self.sourceModel()

        if role==Qt.TextAlignmentRole:
            if orientation==Qt.Horizontal:
                return QVariant(int(Qt.AlignHCenter|Qt.AlignVCenter))
            return QVariant(int(Qt.AlignHCenter|Qt.AlignVCenter))

        if role==Qt.DisplayRole:
            if orientation==Qt.Horizontal:
                return QVariant( sourceModel.headerNames[column] )
            else:
                return QVariant()
        else:
            return QVariant()

        return QVariant(int(column+1))


class MyWindow(QWidget):
    def __init__(self, *args):
        QWidget.__init__(self, *args)

        tableModel=Model(self)               

        proxyModel=Proxy()
        proxyModel.setSourceModel(tableModel)

        self.tableview=QTableView(self) 
        self.tableview.setModel(proxyModel)
        self.tableview.horizontalHeader().setStretchLastSection(True)
        self.tableview.setSelectionMode(QAbstractItemView.MultiSelection)

        header=self.tableview.horizontalHeader()
        header.setContextMenuPolicy(Qt.CustomContextMenu)
        header.connect(header, SIGNAL("customContextMenuRequested(QPoint)" ), self.headerRightClicked)

        self.resHeaderMenu=QMenu(self)

        for column in range(proxyModel.columnCount()):
            columnName=proxyModel.headerData(column, Qt.Horizontal).toPyObject()
            actn=QAction('%s'%columnName, self.resHeaderMenu, checkable=True)
            actn.setChecked(True)
            actn.triggered.connect(self.resHeaderMenuTriggered)
            self.resHeaderMenu.addAction(actn)

        layout = QVBoxLayout(self)
        layout.addWidget(self.tableview)
        self.setLayout(layout)

    def headerRightClicked(self, QPos):
        parentPosition=self.tableview.mapToGlobal(QPoint(0, 0))        
        menuPosition=parentPosition + QPos
        self.resHeaderMenu.move(menuPosition)
        self.resHeaderMenu.show()        

    def resHeaderMenuTriggered(self, arg):
        print 'resHeaderMenuTriggered', arg
        for i, actn in enumerate(self.resHeaderMenu.actions()):
            if not actn.isChecked():
                self.tableview.setColumnHidden(i, True) 

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec_())

如果您真的想自己重新排列列:

您可以通过 horizontalHeadermoveSection and swapSections 方法移动或交换部分。

headerView.moveSection(x, y)

将移动第 x 列,使其成为 y 列之后,而

headerView.swapSections(x,y)

将 - 显然 - 交换两列的位置。

感谢您在 header-column right-click 示例中实现的 right-click 菜单。正确寻找与您相同的功能。只有一件事,当刻度为 re-checked 时,该列应该显示,即你的 resHeaderMenuTriggered() 应该写成:

def resHeaderMenuTriggered(self, arg):
    print('resHeaderMenuTriggered', arg)
    for i, actn in enumerate(self.resHeaderMenu.actions()):
        if not actn.isChecked():
            self.proxyView.setColumnHidden(i, True)
        else:
            self.proxyView.setColumnHidden(i, False)