如何在 QTableWidget 中将拖放与排序结合起来? [英] How to combine drag and drop with sorting in a QTableWidget?

查看:158
本文介绍了如何在 QTableWidget 中将拖放与排序结合起来?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 QTableWidget 中,我希望能够:

  • 按列对表格进行排序和
  • 通过拖动和更改行顺序将行放到不同的位置

经过大量过时或令人困惑的代码片段,到目前为止,对我来说,移动行的最清晰解决方案是

解决方案

您应该关闭自动排序并改用按需排序.这只会在单击标题时按列排序,并且可以通过表小部件的标题非常简单地实现:

header = self.horizo​​ntalHeader()header.setSortIndicatorShown(True)header.sortIndicatorChanged.connect(self.sortItems)

唯一需要的其他更改是删除所有 setSortingEnabled 调用,并在填充表时设置初始排序.因此,您的示例将如下所示:

导入系统从 PyQt5.QtCore 导入 Qt从 PyQt5.QtWidgets 导入 QApplication、QWidget、QTableWidget、QVBoxLayout、QTableWidgetItem、QAbstractItemView从 PyQt5.QtGui 导入 QDropEvent, QDragMoveEvent从日期时间导入日期时间随机导入类 TableWidgetDragRows(QTableWidget):def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)# 设置按需排序header = self.horizo​​ntalHeader()header.setSortIndicatorShown(True)header.sortIndicatorChanged.connect(self.sortItems)self.setDragEnabled(True)self.setAcceptDrops(True)self.viewport().setAcceptDrops(True)self.setDragDropOverwriteMode(False)self.setDropIndicatorShown(True)self.setSelectionMode(QAbstractItemView.ExtendedSelection)self.setSelectionBehavior(QAbstractItemView.SelectRows)self.setDragDropMode(QAbstractItemView.InternalMove)def dropEvent(self, event: QDropEvent):如果不是 event.isAccepted() 和 event.source() == self:drop_row = self.drop_on(event)rows = sorted(set(item.row() for item in self.selectedItems()))rows_to_move = [[QTableWidgetItem(self.item(row_index, column_index)) for column_index in range(self.columnCount())]对于行中的 row_index]对于反转(行)中的 row_index:self.removeRow(row_index)如果 row_index = rect.center().y()类 MyWindow(QWidget):def __init__(self):super(MyWindow,self).__init__()self.setGeometry(100,100,600,300)self.layout = QVBoxLayout()self.setLayout(self.layout)self.tw = TableWidgetDragRows(self)self.layout.addWidget(self.tw)self.tw.setRowCount(5)self.tw.setColumnCount(3)对于范围内的行(self.tw.rowCount()):对于范围内的 col(self.tw.columnCount()):如果列==0:myValue = "".join([chr(random.randint(65,90)) for i in range(0,4)])别的:myValue = random.randint(0,100)twi = QTableWidgetItem()twi.setData(Qt.DisplayRole,myValue)self.tw.setItem(row, col, twi)# 进行初始排序self.tw.sortItems(0)自我展示()如果 __name__ == '__main__':app = QApplication(sys.argv)app.setStyle(融合")窗口 = MyWindow()sys.exit(app.exec_())

In a QTableWidget I would like to be able to:

  • sort the table by columns and
  • change the row order by drag & drop of rows to a different position

After a lot of outdated or confusing code snippets, so far, for me the clearest solution for moving a row I found here. And sorting can simply be enabled/disabled by setSortingEnabled(True/False).

Draging and dropping works alone and sorting works alone, but not together.

I guess what's going on is when dropping the table will be sorted again. So, I thought if I switch off sorting during dragging it should get the desired result, but it's not the case. Apparently, I doing something wrong.

I am sure it must be a small detail, but what am I missing here? It looks like I need to get rid of the sorting arrow in the column header.

Code: (should be copy & paste & run):

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QTableWidget, QVBoxLayout, QTableWidgetItem, QAbstractItemView
from PyQt5.QtGui import QDropEvent, QDragMoveEvent
from datetime import datetime
import random

class TableWidgetDragRows(QTableWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setSortingEnabled(True)    # enable sorting by default
        self.setDragEnabled(True)
        self.setAcceptDrops(True)
        self.viewport().setAcceptDrops(True)
        self.setDragDropOverwriteMode(False)
        self.setDropIndicatorShown(True)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setDragDropMode(QAbstractItemView.InternalMove)

    def dragEvent(self, event: QDragMoveEvent):
        self.setSortingEnabled(False)   # disable sorting during dragging

    def dropEvent(self, event: QDropEvent):
        self.setSortingEnabled(False)   # disable sorting during dropping

        if not event.isAccepted() and event.source() == self:
            drop_row = self.drop_on(event)

            rows = sorted(set(item.row() for item in self.selectedItems()))
            rows_to_move = [[QTableWidgetItem(self.item(row_index, column_index)) for column_index in range(self.columnCount())]
                            for row_index in rows]
            for row_index in reversed(rows):
                self.removeRow(row_index)
                if row_index < drop_row:
                    drop_row -= 1

            for row_index, data in enumerate(rows_to_move):
                row_index += drop_row
                self.insertRow(row_index)
                for column_index, column_data in enumerate(data):
                    self.setItem(row_index, column_index, column_data)
            event.accept()

            for row_index in range(len(rows_to_move)):   # maybe can be done smarter
                for col in range(self.columnCount()):
                    self.item(drop_row + row_index, col).setSelected(True)

        super().dropEvent(event)
        self.setSortingEnabled(True)

    def drop_on(self, event):
        index = self.indexAt(event.pos())
        if not index.isValid():
            return self.rowCount()
        return index.row() + 1 if self.is_below(event.pos(), index) else index.row()

    def is_below(self, pos, index):
        rect = self.visualRect(index)
        margin = 2
        if pos.y() - rect.top() < margin:
            return False
        elif rect.bottom() - pos.y() < margin:
            return True
        # noinspection PyTypeChecker
        return rect.contains(pos, True) and not (int(self.model().flags(index)) & Qt.ItemIsDropEnabled) and pos.y() >= rect.center().y()

class MyWindow(QWidget):
    def __init__(self):
        super(MyWindow,self).__init__()
        self.setGeometry(100,100,600,300)
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        
        self.tw = TableWidgetDragRows(self)
        self.layout.addWidget(self.tw)
        self.tw.setRowCount(5)
        self.tw.setColumnCount(3)
        #
        for row in range(self.tw.rowCount()):
            for col in range(self.tw.columnCount()):
                if col==0:
                    myValue = "".join([chr(random.randint(65,90)) for i in range(0,4)])
                else:
                    myValue = random.randint(0,100)
                twi = QTableWidgetItem()
                twi.setData(Qt.DisplayRole,myValue)
                self.tw.setItem(row, col, twi)
        self.show()
            
if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    window = MyWindow()
    sys.exit(app.exec_())

Result:

解决方案

You should turn off automatic sorting and use on-demand sorting instead. This will only sort by column when the header is clicked, and can be implemented quite simply via the header of the table-widget:

header = self.horizontalHeader()
header.setSortIndicatorShown(True)
header.sortIndicatorChanged.connect(self.sortItems)

The only other changes needed are to remove all setSortingEnabled calls, and to set an initial sort whenever the table is populated. So your example would then look like this:

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QTableWidget, QVBoxLayout, QTableWidgetItem, QAbstractItemView
from PyQt5.QtGui import QDropEvent, QDragMoveEvent
from datetime import datetime
import random

class TableWidgetDragRows(QTableWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # set up on demand sorting
        header = self.horizontalHeader()
        header.setSortIndicatorShown(True)
        header.sortIndicatorChanged.connect(self.sortItems)

        self.setDragEnabled(True)
        self.setAcceptDrops(True)
        self.viewport().setAcceptDrops(True)
        self.setDragDropOverwriteMode(False)
        self.setDropIndicatorShown(True)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setDragDropMode(QAbstractItemView.InternalMove)

    def dropEvent(self, event: QDropEvent):
        if not event.isAccepted() and event.source() == self:
            drop_row = self.drop_on(event)

            rows = sorted(set(item.row() for item in self.selectedItems()))
            rows_to_move = [[QTableWidgetItem(self.item(row_index, column_index)) for column_index in range(self.columnCount())]
                            for row_index in rows]
            for row_index in reversed(rows):
                self.removeRow(row_index)
                if row_index < drop_row:
                    drop_row -= 1

            for row_index, data in enumerate(rows_to_move):
                row_index += drop_row
                self.insertRow(row_index)
                for column_index, column_data in enumerate(data):
                    self.setItem(row_index, column_index, column_data)
            event.accept()

            for row_index in range(len(rows_to_move)):   # maybe can be done smarter
                for col in range(self.columnCount()):
                    self.item(drop_row + row_index, col).setSelected(True)

        super().dropEvent(event)

    def drop_on(self, event):
        index = self.indexAt(event.pos())
        if not index.isValid():
            return self.rowCount()
        return index.row() + 1 if self.is_below(event.pos(), index) else index.row()

    def is_below(self, pos, index):
        rect = self.visualRect(index)
        margin = 2
        if pos.y() - rect.top() < margin:
            return False
        elif rect.bottom() - pos.y() < margin:
            return True
        # noinspection PyTypeChecker
        return rect.contains(pos, True) and not (int(self.model().flags(index)) & Qt.ItemIsDropEnabled) and pos.y() >= rect.center().y()

class MyWindow(QWidget):
    def __init__(self):
        super(MyWindow,self).__init__()
        self.setGeometry(100,100,600,300)
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        self.tw = TableWidgetDragRows(self)
        self.layout.addWidget(self.tw)
        self.tw.setRowCount(5)
        self.tw.setColumnCount(3)
        for row in range(self.tw.rowCount()):
            for col in range(self.tw.columnCount()):
                if col==0:
                    myValue = "".join([chr(random.randint(65,90)) for i in range(0,4)])
                else:
                    myValue = random.randint(0,100)
                twi = QTableWidgetItem()
                twi.setData(Qt.DisplayRole,myValue)
                self.tw.setItem(row, col, twi)
        # do initial sort
        self.tw.sortItems(0)
        self.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    window = MyWindow()
    sys.exit(app.exec_())

这篇关于如何在 QTableWidget 中将拖放与排序结合起来?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆