2013-12-16 12 views
5

Ich versuche, eine Einschränkung auf die QTreeWidget Drag-and-Drop-Funktion hinzuzufügen, um zu verhindern, dass die Zweige einen anderen Zweig in einem anderen Stamm eingeben.pyside qtreewidget Constrain drag and drop

Hier ist ein Beispiel, um die Dinge klarer zu machen:
Ich habe 4 Objekte. Nennen wir sie Apfel, Banane, Karotte, Durian.

Der Baum sieht wie folgt aus:

isDelicious (Root) 
|-- BackgroundObjects (Branch) 
    |-- Durian 
|-- ForgroundObjects (Branch) 
    |-- Apple 
    |-- Banana 
    |-- Carrot 
isSmelly (Root) 
|-- BackgroundObjects (Branch) 
    |-- Apple 
    |-- Carrot 
|-- ForgroundObjects (Branch) 
    |-- Banana 
    |-- Durian 

So dürfen die Objekte gezogen und von BackgroundObjects zu ForgroundObjects fallen gelassen werden, und umgekehrt auf der gleichen Wurzel, aber sie werden nicht gezogen werden darf und fiel auf einen Ast auf einer anderen Wurzel.

Ich habe versucht, dragMoveEvent, dragEnterEvent und dropEvent reimplementieren und Unterklassen, und wenn ich accept für das Ereignis in dragEnterEvent aufrufen, ruft es DragMoveEvent nach (was ich erwarte). DropEvent wird jedoch nur aufgerufen, wenn ich außerhalb von QTreeWidget absteige.

Was ich tun möchte, ist die Großeltern der ausgewählten Objekte zu überprüfen, bevor sie bewegt werden, und die vorgeschlagenen neuen Großeltern, um zu sehen, ob sie gleich sind. Wenn ja, dann akzeptiere den Umzug. Ansonsten ignoriere den Umzug.

Ich habe gesucht, um zu sehen, ob es irgendwelche Antworten gibt, und bis jetzt habe ich keine gesehen, für das, was ich versuche zu tun. Wahrscheinlich wäre der nächste diese beiden Fragen von Stack-Überlauf sein:
https://stackoverflow.com/questions/17134289/managing-drag-and-drop-within-qtreewidgets-in-pyside
qt: QTreeView - limit drag and drop to only happen within a particlar grandparent (ancestor)

Antwort

3

Qt scheint nicht so etwas sehr einfach zu machen.

Das Beste, was ich mir vorstellen konnte, war, die Elementmarkierungen während der Ereignisse mit Ziehen und Ziehen zeitweilig zurückzusetzen. Im folgenden Beispiel wird der aktuelle Artikel auf oberster Ebene dynamisch berechnet, um Drag & Drop zu vermeiden. Es könnte aber auch setData() verwendet werden, um jedem Element einen Bezeichner hinzuzufügen.

from PyQt4 import QtCore, QtGui 

class TreeWidget(QtGui.QTreeWidget): 
    def __init__(self, parent=None): 
     QtGui.QTreeWidget.__init__(self, parent) 
     self.setDragDropMode(self.InternalMove) 
     self.setDragEnabled(True) 
     self.setDropIndicatorShown(True) 
     self._dragroot = self.itemRootIndex() 

    def itemRootIndex(self, item=None): 
     root = self.invisibleRootItem() 
     while item is not None: 
      item = item.parent() 
      if item is not None: 
       root = item 
     return QtCore.QPersistentModelIndex(
      self.indexFromItem(root)) 

    def startDrag(self, actions): 
     items = self.selectedItems() 
     self._dragroot = self.itemRootIndex(items and items[0]) 
     QtGui.QTreeWidget.startDrag(self, actions) 

    def dragEnterEvent(self, event): 
     self._drag_event(event, True) 

    def dragMoveEvent(self, event): 
     self._drag_event(event, False) 

    def _drag_event(self, event, enter=True): 
     items = [] 
     disable = False 
     item = self.itemAt(event.pos()) 
     if item is not None: 
      disable = self._dragroot != self.itemRootIndex(item) 
      if not disable: 
       rect = self.visualItemRect(item) 
       if event.pos().x() < rect.x(): 
        disable = True 
     if disable: 
      for item in item, item.parent(): 
       if item is not None: 
        flags = item.flags() 
        item.setFlags(flags & ~QtCore.Qt.ItemIsDropEnabled) 
        items.append((item, flags)) 
     if enter: 
      QtGui.QTreeWidget.dragEnterEvent(self, event) 
     else: 
      QtGui.QTreeWidget.dragMoveEvent(self, event) 
     for item, flags in items: 
      item.setFlags(flags) 

class Window(QtGui.QWidget): 
    def __init__(self): 
     QtGui.QWidget.__init__(self) 
     self.tree = TreeWidget(self) 
     self.tree.header().hide() 
     def add(root, *labels): 
      item = QtGui.QTreeWidgetItem(self.tree, [root]) 
      item.setFlags(item.flags() & 
          ~(QtCore.Qt.ItemIsDragEnabled | 
          QtCore.Qt.ItemIsDropEnabled)) 
      for index, title in enumerate(
       ('BackgroundObjects', 'ForegroundObjects')): 
       subitem = QtGui.QTreeWidgetItem(item, [title]) 
       subitem.setFlags(
        subitem.flags() & ~QtCore.Qt.ItemIsDragEnabled) 
       for text in labels[index].split(): 
        child = QtGui.QTreeWidgetItem(subitem, [text]) 
        child.setFlags(
         child.flags() & ~QtCore.Qt.ItemIsDropEnabled) 
     add('isDelicious', 'Durian', 'Apple Banana Carrot') 
     add('isSmelly', 'Apple Carrot', 'Banana Durian') 
     root = self.tree.invisibleRootItem() 
     root.setFlags(root.flags() & ~QtCore.Qt.ItemIsDropEnabled) 
     self.tree.expandAll() 
     layout = QtGui.QVBoxLayout(self) 
     layout.addWidget(self.tree) 

if __name__ == '__main__': 

    import sys 
    app = QtGui.QApplication(sys.argv) 
    window = Window() 
    window.setGeometry(500, 300, 300, 300) 
    window.show() 
    sys.exit(app.exec_()) 
+0

Sie es erwähnen könnte getan mit dem setData. Könnten Sie zeigen, wie das gemacht würde? – JokerMartini

+0

@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

+0

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

1

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) 
... 

table example


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.

    next to drop target

  • 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_()) 
+0

Ich bin mir nicht ganz sicher, ob das richtig funktioniert. Wenn ich einen Gegenstand trage, verschwindet er für immer ...? – JokerMartini

+0

Es funktionierte gut unter Linux, aber ich habe gerade zu Hause auf Windows getestet und in der Tat verschwindet das Element. Könnte auch Python-Version sein oder ich habe irgendwie eine "triviale" Änderung gemacht und den Code kaputt gemacht ... – Mel

+0

Ich habe einige Links mit ähnlichen Problemen unter Windows gefunden, hier ist ein Fehlerbericht: https://bugreports.qt.io/browse/QTBUG -46642. – Mel