Вот мое решение (полный код в конце), подкласса QTreeWidget
. Я пытался иметь что-то очень общее, которое должно работать во многих случаях. Одна проблема остается с визуальными подсказками при перетаскивании. Предыдущая версия не работала над окнами, я надеюсь, что это будет. Он отлично работает в 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)
}
Каждый элемент категории «семья» может получить сопротивление, и может быть падение только в «корень» (невидимый корневой элемент). Каждый элемент категории «дети» может попасть только в «семью».
Добавление элементов в дереве
Метод 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()
.
Единственная оставшаяся проблема заключается в том, что мышь фактически находится на «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.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_())
вы упоминаете это может быть с помощью setData. Не могли бы вы показать, как это будет сделано? – JokerMartini
@JokerMartini. Я исправил ошибку в примере, но я не думаю, что общее решение очень надежное, и я, вероятно, не рекомендую его сейчас. Использование 'setData' не будет иметь никакого значения. На данный момент, боюсь, у меня нет лучших идей, и у меня нет времени заглядывать в нее дальше. – ekhumoro
Вы могли бы помочь мне с моей ситуацией, пожалуйста? Я обновил свой пост, увиденный здесь.У меня это почти работает, но у него есть несколько ошибок http://stackoverflow.com/questions/34133789/controlling-drag-n-drop-disable-enable-of-qtreewidget-items-python?noredirect1_comment56017728_34133789 – JokerMartini