【问题标题】:PyQt item column lost if QTreeWidget is set to DragDrop mode如果 QTreeWidget 设置为 DragDrop 模式,PyQt 项目列会丢失
【发布时间】:2019-07-09 05:56:39
【问题描述】:

我正在使用 QTreeWidget 创建一个类似文件夹树的结构。每个项目有 2 列:

  1. 文件夹名称。
  2. 文件夹id,由tree.hideColumn(1)设置为隐藏。

目标是在 QTreeWidget 内以及跨小部件启用拖放,这需要设置 tree.setDragDropMode(DragDrop)。但是,将模式从InternalMove更改为DrapDrop后,我发现拖动的QTreeWidgetItem只保留了它的第1列,之前存在的第2列丢失了。如果我查询item.data(1,0),它会给出None

如果我不隐藏第二列,它不会在拖动过程中丢失。

我很困惑。任何帮助表示赞赏。

下面是一个工作示例。如果您将任何项目拖到另一个项目上,控制台将打印column counts 1。与重命名(双击)拖动的项目相同。

import sys
from PyQt5.QtWidgets import QTreeWidget, QVBoxLayout,\
        QMainWindow, QWidget, QTreeWidgetItem, QApplication, QAbstractItemView
from PyQt5.QtCore import Qt

class MainWindow(QMainWindow):

    def __init__(self):
        super(self.__class__, self).__init__()
        frame=QWidget()
        self.setCentralWidget(frame)
        hl=QVBoxLayout()
        frame.setLayout(hl)

        self.tree=QTreeWidget(self)
        self.tree.setColumnCount(2)

        # if I don't hide 2nd column, it won't get lost during the drag.
        self.tree.hideColumn(1)
        self.tree.setDragEnabled(True)

        # InternalMove gives 2 columns: name and id.
        # DragDrop would only give the 1st column after a drag/drop

        #self.tree.setDragDropMode(QAbstractItemView.InternalMove)
        self.tree.setDragDropMode(QAbstractItemView.DragDrop)

        hl.addWidget(self.tree)

        # add treewidgetitems
        data=[['Folder 1', '1'],
              ['Folder 2', '2'],
              ['Folder 3', '3']
              ]
        for ii in range(3):
            item=QTreeWidgetItem(data[ii])
            self.tree.addTopLevelItem(item)

        self.tree.itemDoubleClicked.connect(self.rename)
        self.tree.itemChanged.connect(self.postRename, Qt.QueuedConnection)

        self.show()


    def rename(self):
        item=self.tree.selectedItems()
        if item:
            item=item[0]
            item.setFlags(item.flags() | Qt.ItemIsEditable)
            self.tree.scrollToItem(item)
            self.tree.editItem(item)

    def postRename(self,item,column):

        print('postRename: column counts', item.columnCount())
        text=item.data(0,0)
        itemid=item.data(1,0)
        print('postRename: item text=',text, 'item id', itemid)
        return


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

【问题讨论】:

  • 你只是用第2列保存id信息吗?
  • 是的。我不知道你建议的UserRole,第二列除了存储一个ID之外没有其他用途。

标签: python pyqt drag-and-drop qtreewidget


【解决方案1】:

views的startDrag方法中,仅将selectedIndexes()返回的索引作为base,selectedIndexes()仅返回未隐藏的selectionModel()索引,所以隐藏列不发送信息.所以解决方法是selectedIndexes() 方法不过滤隐藏索引:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class TreeWidget(QtWidgets.QTreeWidget):
    def selectedIndexes(self):
        return self.selectionModel().selectedIndexes()

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(self.__class__, self).__init__()

        self.tree = TreeWidget(columnCount=2,
            dragDropMode=QtWidgets.QAbstractItemView.DragDrop,
            dragEnabled=True)
        self.tree.hideColumn(1)

        self.tree.itemDoubleClicked.connect(self.rename)
        self.tree.itemChanged.connect(self.postRename)

        # add treewidgetitems
        data=[['Folder 1', '1'],
              ['Folder 2', '2'],
              ['Folder 3', '3']
              ]
        for d in data:
            item = QtWidgets.QTreeWidgetItem(d)
            self.tree.addTopLevelItem(item)

        frame = QtWidgets.QWidget()
        self.setCentralWidget(frame)
        hl = QtWidgets.QVBoxLayout(frame)
        hl.addWidget(self.tree)

    @QtCore.pyqtSlot()
    def rename(self):
        item=self.tree.selectedItems()
        if item:
            item=item[0]
            item.setFlags(item.flags() | Qt.ItemIsEditable)
            self.tree.scrollToItem(item)
            self.tree.editItem(item)

    @QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem, int)
    def postRename(self, item, column):
        print('postRename: column counts', item.columnCount())
        text = item.data(0,0)
        itemid = item.data(1,0)
        print('postRename: item text=',text, 'item id', itemid)


if __name__ == "__main__":
     app = QtWidgets.QApplication(sys.argv)
     form = MainWindow()
     form.show()
     sys.exit(app.exec_())

另一方面,如果您建立第 1 列的目的只是为了保存数据,则最好使用避免隐藏列的角色:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

IDRole = QtCore.Qt.UserRole + 1000

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(self.__class__, self).__init__()

        self.tree = QtWidgets.QTreeWidget(columnCount=1,
            dragDropMode=QtWidgets.QAbstractItemView.DragDrop,
            dragEnabled=True)
        self.tree.hideColumn(1)

        self.tree.itemDoubleClicked.connect(self.rename)
        self.tree.itemChanged.connect(self.postRename)

        # add treewidgetitems
        data=[['Folder 1', '1'],
              ['Folder 2', '2'],
              ['Folder 3', '3']
              ]
        for d in data:
            text, itemid = d
            item = QtWidgets.QTreeWidgetItem([text])
            item.setData(0, IDRole, itemid)
            self.tree.addTopLevelItem(item)

        frame = QtWidgets.QWidget()
        self.setCentralWidget(frame)
        hl = QtWidgets.QVBoxLayout(frame)
        hl.addWidget(self.tree)

    @QtCore.pyqtSlot()
    def rename(self):
        item=self.tree.selectedItems()
        if item:
            item=item[0]
            item.setFlags(item.flags() | Qt.ItemIsEditable)
            self.tree.scrollToItem(item)
            self.tree.editItem(item)

    @QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem, int)
    def postRename(self, item, column):
        print('postRename: column counts', item.columnCount())
        text = item.text(0)
        itemid = item.data(0, IDRole)
        print('postRename: item text=',text, 'item id', itemid)

if __name__ == "__main__":
     app = QtWidgets.QApplication(sys.argv)
     form = MainWindow()
     form.show()
     sys.exit(app.exec_())

【讨论】:

  • 非常感谢。我永远不会自己弄清楚如此复杂的差异。我正在采用您的第一种方法,因为它可以最大限度地减少我需要进行的更改。似乎DragDrop 模式意味着对模型或视图进行了相当多的更改,请注意itemChanged 信号是在DragDrop 模式下触发的,但不是在InternalMove 模式下触发的。现在我必须修补这些东西以应对这种差异。
  • 并且在DragDrop 模式下,每次拖放都会触发itemChanged 信号两次,似乎每次都针对一列,即使两列中的数据都没有“更改”。引擎盖下一定发生了一些令人讨厌的事情,不是吗?
  • @Jason itemChanged 每次在项目中发生更改时都会发出,并且某些内容可能是另一列中的角色,例如文本。放置完成后,使用的 QModelIndex 不像 QTreeWidgetsItem 那样表示行,而是表示行和列的元素,也就是说,如果您的 QTreeWidget 有 n 列,则将创建与 QTreeWidgetItem 关联的 n QModelIndex,通过创建每个 QModelIndex 都将修改相同的 QTreeWidgetItem,这就是 Qt 将您作为列号的第二个参数传递的原因。
  • 我觉得连接itemChanged就像连接定时炸弹,我的是连续发射4次atm。我一直在试图限制这个触发器的所有地方断开/重新连接。有没有更好的方法从内容变化中过滤掉角色、标志、QModelIndex 变化?
  • @Jason 您希望该信号何时触发?
猜你喜欢
  • 1970-01-01
  • 2015-05-23
  • 2012-10-15
  • 2014-11-13
  • 2017-04-23
  • 1970-01-01
  • 2017-01-25
  • 2012-02-16
  • 1970-01-01
相关资源
最近更新 更多