目标

我的目标是拥有一个 QTableWidget,用户可以在其中在内部拖放行。也就是说,用户可以拖放一整行,将其在表格中向上或向下移动到其他两行之间的不同位置。目标如下图所示:

qt - 在 QTableWidget 中拖放行-LMLPHP

我尝试了什么,然后发生了什么

用数据填充 QTableWidget 后,我将其属性设置如下:

table.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
#select one row at a time
table.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)

类似的代码使 QListWidget 表现得很好:当你在内部移动一个项目时,它被放置在列表的两个元素之间,其余的项目以合理的方式自行排序,没有数据被覆盖(换句话说, View 行为就像上图,但它是一个列表)。

相比之下,在使用上述代码修改的表中,事情并没有按计划进行。下图显示了实际发生的情况:

qt - 在 QTableWidget 中拖放行-LMLPHP

换句话说:当行 i 被删除时,该行在表格中变为空白。此外,如果我不小心将第 i 行拖放到第 j 行(而不是两行之间的空格),第 i 行中的数据将替换第 j 行中的数据。也就是说,在这种不幸的情况下,除了第 i 行变为空白之外,第 j 行也被覆盖。

注意我也尝试添加 table.setDragDropOverwriteMode(False) 但它没有改变行为。

前进的道路?

This bug report 可能在 C++ 中包含一个可能的解决方案:似乎他们为 dropEvent 重新实现了 QTableWidget ,但我不确定如何干净地移植到 Python。

相关内容:
  • Reordering items in a QTreeWidget with Drag and Drop in PyQt
  • QT: internal drag and drop of rows in QTableView, that changes order of rows in QTableModel
  • http://www.qtcentre.org/threads/35113-QTableWidget-dropping-between-the-rows-shall-insert-the-item
  • qt: pyqt: QTreeView internal drag and drop almost working... dragged item disappears
  • How to drag & drop rows within QTableWidget
  • QListWidget drag and drop items disappearing from list on Symbian
  • QTableWidget Internal Drag Drop Entire Row
  • 最佳答案

    这似乎是非常奇怪的默认行为。无论如何,按照 bug report you linked to 中的代码,我已经成功地将一些东西移植到了 PyQt。它可能会,也可能不会像该代码那样健壮,但它至少似乎适用于您在屏幕截图中提供的简单测试用例!

    以下实现的潜在问题是:

  • 当前选中的行不跟随拖放(所以如果你移动第三行,移动后第三行保持选中状态)。这可能不太难解决!
  • 它可能不适用于具有子行的行。我什至不确定 QTableWidgetItem 是否可以有 child ,所以也许没问题。
  • 我还没有测试过选择多行,但我认为它应该可以工作
  • 出于某种原因,尽管在表中插入了新行,但我不必删除正在移动的行。这对我来说似乎很奇怪。它几乎看起来像是在任何地方插入一行,但最后不会增加表的 rowCount()
  • 我对 GetSelectedRowsFast 的实现与他们的有点不同。它可能不快,并且可能像他们那样存在一些错误(我不检查项目是否已启用或可选择)。我认为这也很容易解决,但如果您在选中一行时禁用它,然后有人执行拖放操作,这只是一个问题。在这种情况下,我认为更好的解决方案可能是在行被禁用时取消选择行,但这取决于我猜你在做什么!

  • 如果您在生产环境中使用此代码,您可能想仔细检查一下,确保一切都有意义。我的 PyQt 端口很可能存在问题,并且我的端口所基于的原始 C++ 算法也可能存在问题。然而,它确实可以证明使用 QTableWidget 可以实现您想要的。

    更新:注意 PyQt5 有一个 additional answer below,它也解决了我上面的一些问题。你可能想看看!

    代码:
    import sys, os
    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    
    class TableWidgetDragRows(QTableWidget):
        def __init__(self, *args, **kwargs):
            QTableWidget.__init__(self, *args, **kwargs)
    
            self.setDragEnabled(True)
            self.setAcceptDrops(True)
            self.viewport().setAcceptDrops(True)
            self.setDragDropOverwriteMode(False)
            self.setDropIndicatorShown(True)
    
            self.setSelectionMode(QAbstractItemView.SingleSelection)
            self.setSelectionBehavior(QAbstractItemView.SelectRows)
            self.setDragDropMode(QAbstractItemView.InternalMove)
    
        def dropEvent(self, event):
            if event.source() == self and (event.dropAction() == Qt.MoveAction or self.dragDropMode() == QAbstractItemView.InternalMove):
                success, row, col, topIndex = self.dropOn(event)
                if success:
                    selRows = self.getSelectedRowsFast()
    
                    top = selRows[0]
                    # print 'top is %d'%top
                    dropRow = row
                    if dropRow == -1:
                        dropRow = self.rowCount()
                    # print 'dropRow is %d'%dropRow
                    offset = dropRow - top
                    # print 'offset is %d'%offset
    
                    for i, row in enumerate(selRows):
                        r = row + offset
                        if r > self.rowCount() or r < 0:
                            r = 0
                        self.insertRow(r)
                        # print 'inserting row at %d'%r
    
    
                    selRows = self.getSelectedRowsFast()
                    # print 'selected rows: %s'%selRows
    
                    top = selRows[0]
                    # print 'top is %d'%top
                    offset = dropRow - top
                    # print 'offset is %d'%offset
                    for i, row in enumerate(selRows):
                        r = row + offset
                        if r > self.rowCount() or r < 0:
                            r = 0
    
                        for j in range(self.columnCount()):
                            # print 'source is (%d, %d)'%(row, j)
                            # print 'item text: %s'%self.item(row,j).text()
                            source = QTableWidgetItem(self.item(row, j))
                            # print 'dest is (%d, %d)'%(r,j)
                            self.setItem(r, j, source)
    
                    # Why does this NOT need to be here?
                    # for row in reversed(selRows):
                        # self.removeRow(row)
    
                    event.accept()
    
            else:
                QTableView.dropEvent(event)
    
        def getSelectedRowsFast(self):
            selRows = []
            for item in self.selectedItems():
                if item.row() not in selRows:
                    selRows.append(item.row())
            return selRows
    
        def droppingOnItself(self, event, index):
            dropAction = event.dropAction()
    
            if self.dragDropMode() == QAbstractItemView.InternalMove:
                dropAction = Qt.MoveAction
    
            if event.source() == self and event.possibleActions() & Qt.MoveAction and dropAction == Qt.MoveAction:
                selectedIndexes = self.selectedIndexes()
                child = index
                while child.isValid() and child != self.rootIndex():
                    if child in selectedIndexes:
                        return True
                    child = child.parent()
    
            return False
    
        def dropOn(self, event):
            if event.isAccepted():
                return False, None, None, None
    
            index = QModelIndex()
            row = -1
            col = -1
    
            if self.viewport().rect().contains(event.pos()):
                index = self.indexAt(event.pos())
                if not index.isValid() or not self.visualRect(index).contains(event.pos()):
                    index = self.rootIndex()
    
            if self.model().supportedDropActions() & event.dropAction():
                if index != self.rootIndex():
                    dropIndicatorPosition = self.position(event.pos(), self.visualRect(index), index)
    
                    if dropIndicatorPosition == QAbstractItemView.AboveItem:
                        row = index.row()
                        col = index.column()
                        # index = index.parent()
                    elif dropIndicatorPosition == QAbstractItemView.BelowItem:
                        row = index.row() + 1
                        col = index.column()
                        # index = index.parent()
                    else:
                        row = index.row()
                        col = index.column()
    
                if not self.droppingOnItself(event, index):
                    # print 'row is %d'%row
                    # print 'col is %d'%col
                    return True, row, col, index
    
            return False, None, None, None
    
        def position(self, pos, rect, index):
            r = QAbstractItemView.OnViewport
            margin = 2
            if pos.y() - rect.top() < margin:
                r = QAbstractItemView.AboveItem
            elif rect.bottom() - pos.y() < margin:
                r = QAbstractItemView.BelowItem
            elif rect.contains(pos, True):
                r = QAbstractItemView.OnItem
    
            if r == QAbstractItemView.OnItem and not (self.model().flags(index) & Qt.ItemIsDropEnabled):
                r = QAbstractItemView.AboveItem if pos.y() < rect.center().y() else QAbstractItemView.BelowItem
    
            return r
    
    
    class Window(QWidget):
        def __init__(self):
            super(Window, self).__init__()
    
            layout = QHBoxLayout()
            self.setLayout(layout)
    
            self.table_widget = TableWidgetDragRows()
            layout.addWidget(self.table_widget)
    
            # setup table widget
            self.table_widget.setColumnCount(2)
            self.table_widget.setHorizontalHeaderLabels(['Colour', 'Model'])
    
            items = [('Red', 'Toyota'), ('Blue', 'RV'), ('Green', 'Beetle')]
            for i, (colour, model) in enumerate(items):
                c = QTableWidgetItem(colour)
                m = QTableWidgetItem(model)
    
                self.table_widget.insertRow(self.table_widget.rowCount())
                self.table_widget.setItem(i, 0, c)
                self.table_widget.setItem(i, 1, m)
    
            self.show()
    
    
    app = QApplication(sys.argv)
    window = Window()
    sys.exit(app.exec_())
    

    关于qt - 在 QTableWidget 中拖放行,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/26227885/

    10-13 05:20