这是我的解决方案(最后是完整代码),子类化 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”(不可见的根项目)中。
“儿童”类别的每一项都只能归入“家庭”。
向树中添加项目
addItem(strings,category,parent=None) 方法创建一个带有工具提示“类别”的QTreeWidgetItem(strings,parent) 和setting 中的匹配标志。它返回项目。示例:
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()。
剩下的唯一问题是当鼠标实际上在“罗伯逊”上时:拖动事件被接受。视觉提示说,如果不接受,则将接受丢弃。
-
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.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
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:
# possible "next to drop target" case
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
#item being dragged
itemBeingDragged=self.currentItem()
okList=self.settings[itemBeingDragged.data(0,role)][0]
#parent before the drag
oldParent=itemBeingDragged.parent()
if oldParent is None:
oldParent=self.invisibleRootItem()
oldIndex=oldParent.indexOfChild(itemBeingDragged)
#accept any drop
super(CustomTreeWidget,self).dropEvent(event)
#look at where itemBeingDragged end up
newParent=itemBeingDragged.parent()
if newParent is None:
newParent=self.invisibleRootItem()
if newParent.data(0,role) in okList:
# drop was ok
return
else:
# drop was not ok, put back the item
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
#family example
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_())
#food example: issue with "in between"
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_())