这是我的解决方案(完整代码在结尾处),通过子类化
QTreeWidget
实现。我试图做出一些非常通用的东西,应该适用于许多情况。但拖动时的视觉提示还存在一个问题。之前的版本在Windows上无法工作,希望这个版本可以。在Linux上它工作得非常好。
定义分类
树中的每个项目都有一个类别(字符串),我将其存储在QtCore.Qt.ToolTipRole
中。您还可以子类化QTreeWidgetItem
以具有特定属性category
。
我们在字典settings
中定义所有类别,包括它们可以投放到的类别列表和要设置的标志。例如:
default=QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled
drag=QtCore.Qt.ItemIsDragEnabled
drop=QtCore.Qt.ItemIsDropEnabled
settings={
"family":(["root"],default|drag|drop),
"children":(["family"],default|drag)
}
类别为“family”的每个项目都可以接收拖动操作,并且只能被放置在“root”(不可见的根项目)中。
类别为“children”的每个项目只能被放置在“family”中。
向树中添加项目
方法addItem(strings,category,parent=None)
会创建一个带有工具提示“category”和匹配标志的QTreeWidgetItem(strings,parent)
。它返回该项。例如:
dupont=ex.addItem(["Dupont"],"family")
robert=ex.addItem(["Robertsons"],"family")
ex.addItem(["Laura"],"children",dupont)
ex.addItem(["Matt"],"children",robert)
...
重新实现拖放功能
被拖动的项目可以通过 self.currentItem()
确定(不支持多选)。可以将该项目放置在哪些类别下的列表存储在 okList=self.settings[itemBeingDragged.data(0,role)][0]
中。
鼠标下方的项目,也称为“放置目标”,可以通过 self.itemAt(event.pos())
获得。如果鼠标位于空白区域,则放置目标被设置为根项目。
dragMoveEvent
(用于指示拖放是否被接受/忽略的视觉提示)
如果拖放目标在okList
中,我们调用常规的dragMoveEvent
。否则,我们必须检查“下一个放置目标”。在下面的图像中,鼠标下方的项目是Robertsons,但真正的放置目标是根项目(请参见Robertsons下方的线?)。为了解决这个问题,我们检查该项目是否可以拖到放置目标的父级上。如果不行,我们调用event.ignore()
。
唯一剩下的问题是当鼠标实际位于“Robertsons”上时:拖动事件被接受。视觉提示显示将接受拖放,但实际上并没有。
dropEvent
与其接受或忽略拖放(由于“下一个放置目标”非常棘手),我们总是接受拖放,然后修复错误。
如果新父级与旧父级相同,或者它在okList
中,我们什么也不做。否则,我们将拖动的项目放回旧父级。
有时,被拖动的项目将被折叠,但这很容易通过itemBeingDragged.setExpanded()
来修复。
最后,带有两个示例的完整代码如下:
import sys
from PyQt4 import QtCore, QtGui
class CustomTreeWidget( QtGui.QTreeWidget ):
def __init__(self,settings, parent=None):
QtGui.QTreeWidget.__init__(self, parent)
self.setItemsExpandable(True)
self.setAnimated(True)
self.setDragEnabled(True)
self.setDropIndicatorShown(True)
self.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
self.settings=settings
root=self.invisibleRootItem()
root.setData(0,QtCore.Qt.ToolTipRole,"root")
def dragMoveEvent(self, event):
role=QtCore.Qt.ToolTipRole
itemToDropIn = self.itemAt(event.pos())
itemBeingDragged=self.currentItem()
okList=self.settings[itemBeingDragged.data(0,role)][0]
if itemToDropIn is None:
itemToDropIn=self.invisibleRootItem()
if itemToDropIn.data(0,role) in okList:
super(CustomTreeWidget, self).dragMoveEvent(event)
return
else:
parent=itemToDropIn.parent()
if parent is None:
parent=self.invisibleRootItem()
if parent.data(0,role) in okList:
super(CustomTreeWidget, self).dragMoveEvent(event)
return
event.ignore()
def dropEvent(self, event):
role=QtCore.Qt.ToolTipRole
itemBeingDragged=self.currentItem()
okList=self.settings[itemBeingDragged.data(0,role)][0]
oldParent=itemBeingDragged.parent()
if oldParent is None:
oldParent=self.invisibleRootItem()
oldIndex=oldParent.indexOfChild(itemBeingDragged)
super(CustomTreeWidget,self).dropEvent(event)
newParent=itemBeingDragged.parent()
if newParent is None:
newParent=self.invisibleRootItem()
if newParent.data(0,role) in okList:
return
else:
newParent.removeChild(itemBeingDragged)
oldParent.insertChild(oldIndex,itemBeingDragged)
def addItem(self,strings,category,parent=None):
if category not in self.settings:
print("unknown categorie" +str(category))
return False
if parent is None:
parent=self.invisibleRootItem()
item=QtGui.QTreeWidgetItem(parent,strings)
item.setData(0,QtCore.Qt.ToolTipRole,category)
item.setExpanded(True)
item.setFlags(self.settings[category][1])
return item
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
default=QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsEditable
drag=QtCore.Qt.ItemIsDragEnabled
drop=QtCore.Qt.ItemIsDropEnabled
settings={
"family":(["root"],default|drag|drop),
"children":(["family"],default|drag)
}
ex = CustomTreeWidget(settings)
dupont=ex.addItem(["Dupont"],"family")
robert=ex.addItem(["Robertsons"],"family")
smith=ex.addItem(["Smith"],"family")
ex.addItem(["Laura"],"children",dupont)
ex.addItem(["Matt"],"children",dupont)
ex.addItem(["Kim"],"children",robert)
ex.addItem(["Stephanie"],"children",robert)
ex.addItem(["John"],"children",smith)
ex.show()
sys.exit(app.exec_())
settings={
"food":([],default|drop),
"allVegetable":(["food"],default|drag|drop),
"allFruit":(["food"],default|drag|drop),
"fruit":(["allFruit","fruit"],default|drag|drop),
"veggie":(["allVegetable","veggie"],default|drag|drop),
}
ex = CustomTreeWidget(settings)
top=ex.addItem(["Food"],"food")
fruits=ex.addItem(["Fruits"],"allFruit",top)
ex.addItem(["apple"],"fruit",fruits)
ex.addItem(["orange"],"fruit",fruits)
vegetable=ex.addItem(["Vegetables"],"allVegetable",top)
ex.addItem(["carrots"],"veggie",vegetable)
ex.addItem(["lettuce"],"veggie",vegetable)
ex.addItem(["leek"],"veggie",vegetable)
ex.show()
sys.exit(app.exec_())
setData
不会有任何区别。目前,恐怕我没有更好的想法,并且没有时间进一步研究它。 - ekhumoro