Hier ist meine Lösung (vollständiger Code am Ende), Subklassen ein QTreeWidget
. Ich habe versucht, etwas sehr allgemeines zu haben, das für viele Fälle funktionieren sollte. Ein Problem bleibt bei den visuellen Hinweisen beim Ziehen bestehen. Die vorherige Version hat nicht auf Windows funktioniert, ich hoffe, das wird. Unter Linux funktioniert es einwandfrei.
definieren Kategorien
Jedes Element in der Struktur hat eine Kategorie (ein String), dass ich in QtCore.Qt.ToolTipRole
gespeichert. Sie könnten auch die Unterklasse QTreeWidgetItem
mit einem bestimmten Attribut category
versehen.
Wir definieren in einem Verzeichnis settings
alle Kategorien, mit der Liste der Kategorien, in die sie fallen können, und dem zu setzenden Flag. Zum Beispiel:
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)
}
Jeder Artikel der Kategorie „Familie“ können per Drag erhalten und kann nur in „root“ (die unsichtbare Wurzel Artikel) werden fallen. Jedes Element der Kategorie "Kinder" kann nur in eine "Familie" fallen.
Artikel zu dem Baum hinzufügen
Das Verfahren schafft ein addItem(strings,category,parent=None)
QTreeWidgetItem(strings,parent)
mit einer Werkzeugspitze „Kategorie“ und den passenden Fahnen in setting
. Es gibt den Artikel zurück. Beispiel:
dupont=ex.addItem(["Dupont"],"family")
robert=ex.addItem(["Robertsons"],"family")
ex.addItem(["Laura"],"children",dupont)
ex.addItem(["Matt"],"children",robert)
...
Reimplementierung Drag and Drop
Der Artikel gezogen wird mit self.currentItem()
bestimmt wird (Mehrfachauswahl wird nicht behandelt). Die Liste der Kategorien, in denen dieser Artikel gelöscht werden kann, lautet okList=self.settings[itemBeingDragged.data(0,role)][0]
.
Der Gegenstand unter der Maus, auch bekannt als "Drop Target", ist self.itemAt(event.pos())
. Wenn sich die Maus in einem leeren Feld befindet, wird als Ablageziel das Stammelement festgelegt.
dragMoveEvent
(visueller Hinweis dafür, ob der Abfall akzeptiert/ignoriert)
Wenn das Drop-Ziel in okList
ist, rufen wir die regelmäßigen dragMoveEvent
. Wenn nicht, müssen wir nach "next to drop target" suchen. Im folgenden Bild ist der Gegenstand unter der Maus Robertsons, aber das eigentliche Ziel ist das Wurzelobjekt (siehe die Zeile unter Robertsons?). Um dies zu beheben, überprüfen wir, ob das Objekt auf das übergeordnete Element des Ablageziels gezogen werden kann. Wenn nicht, rufen wir event.ignore()
.
Das einzige verbleibende Problem ist, wenn die Maus tatsächlich auf "Robertsons": das Ziehereignis ist akzeptiert. Der visuelle Hinweis besagt, dass der Drop akzeptiert wird, wenn dies nicht der Fall ist.
dropEvent
Statt zu akzeptieren oder den Tropfen zu ignorieren, die wegen „neben Drop-Ziel“ sehr heikel ist, dass wir den Abfall immer akzeptieren, und dann Fehler zu beheben.
Wenn das neue Elternteil das gleiche wie das alte Elternteil ist oder wenn es in okList
ist, tun wir nichts. Andernfalls legen wir das gezogene Element in das alte Elternelement zurück.
Manchmal wird das abgelegte Element zusammengebrochen, was aber leicht mit itemBeingDragged.setExpanded()
behoben werden konnte
schließlich der vollständige Code mit zwei Beispielen:
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_())
Sie es erwähnen könnte getan mit dem setData. Könnten Sie zeigen, wie das gemacht würde? – JokerMartini
@JokerMartini. Ich habe einen Fehler im Beispiel behoben, aber ich denke nicht, dass die Gesamtlösung sehr zuverlässig ist, und ich würde sie wahrscheinlich nicht empfehlen. Die Verwendung von 'setData' macht keinen Unterschied. Im Moment habe ich leider keine besseren Ideen und habe keine Zeit mehr, mich darum zu kümmern. – ekhumoro
Könnten Sie mir bitte mit meiner Situation helfen? Ich habe meinen Beitrag hier aktualisiert.Ich habe es fast funktioniert, aber es hat ein paar Bugs http://stackoverflow.com/questions/34133789/controlling-drag-n-drop-disable-enable-of-qtreewidget-items-python?noredirect=1#comment56017728_34133789 – JokerMartini