2013-12-16 1 views
5

Я пытаюсь добавить ограничение функции перетаскивания QTreeWidget, чтобы ветви не входили в другую ветку в другом корне.pyside qtreewidget constrain drag and drop

Вот пример, чтобы сделать более понятным:
У меня есть 4 объекта. Позволяет называть их яблоком, бананом, морковью, дурианом.

Дерево выглядит следующим образом:

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

Таким образом, объекты могут быть перетянуты из BackgroundObjects в ForgroundObjects, и наоборот на тот же корень, но они не могут быть втянуты и упал на ветку с другим корнем.

Я попытался переопределить и подклассифицировать dragMoveEvent, dragEnterEvent и dropEvent, и если я вызову accept в событии в dragEnterEvent, он вызовет dragMoveEvent после (что я ожидаю). Однако dropEvent вызывается только при выходе из QTreeWidget.

Что я хочу сделать, это проверить бабушку и дедушку выбранных объектов перед их перемещением, а также предлагаемую новую дедушку и бабушку, чтобы увидеть, совпадают ли они. Если да, тогда примите ход. В противном случае игнорируйте ход.

Я искал, есть ли ответы, и до сих пор я не видел ни одного, что я пытаюсь сделать. Вероятно, ближе всего было бы эти два вопроса от переполнения стека:
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)

ответ

3

Qt, кажется, не делают такого рода вещи очень легко.

Лучшее, что я мог придумать, это временно сбросить флаги элементов во время событий перетаскивания и перетаскивания. В приведенном ниже примере динамически вычисляется текущий элемент верхнего уровня, чтобы избежать перетаскивания. Но это также можно сделать, используя setData(), чтобы добавить идентификатор к каждому элементу.

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

вы упоминаете это может быть с помощью setData. Не могли бы вы показать, как это будет сделано? – JokerMartini

+0

@JokerMartini. Я исправил ошибку в примере, но я не думаю, что общее решение очень надежное, и я, вероятно, не рекомендую его сейчас. Использование 'setData' не будет иметь никакого значения. На данный момент, боюсь, у меня нет лучших идей, и у меня нет времени заглядывать в нее дальше. – ekhumoro

+0

Вы могли бы помочь мне с моей ситуацией, пожалуйста? Я обновил свой пост, увиденный здесь.У меня это почти работает, но у него есть несколько ошибок http://stackoverflow.com/questions/34133789/controlling-drag-n-drop-disable-enable-of-qtreewidget-items-python?noredirect1_comment56017728_34133789 – JokerMartini

1

Вот мое решение (полный код в конце), подкласса 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) 
... 

table example


переопределение Перетаскивание

Элемент перетаскиваемый определяются с self.currentItem() (множественным выбором не обрабатывается). Список категорий, в которых этот элемент можно удалить, - okList=self.settings[itemBeingDragged.data(0,role)][0].

Элемент под мышкой, также известный как «цель падения», находится в self.itemAt(event.pos()). Если указатель мыши находится в пустом пространстве, то целевой точкой перехода назначается корневой элемент.

  • dragMoveEvent (визуальный сигнал для будет ли принята капля/игнорируется)
    Если цель капля в okList, мы называем регулярной dragMoveEvent. Если нет, мы должны проверить «рядом с целью снижения». На рисунке ниже элемент под мышкой - Robertsons, но реальная цель перетаскивания - это корневой элемент (см. Строку ниже Robertsons?). Чтобы исправить это, мы проверяем, что элемент можно перетащить на родителя целевой цели. Если нет, мы вызываем event.ignore().

    Единственная оставшаяся проблема заключается в том, что мышь фактически находится на «Robertsons»: событие перетаскивания принято. Визуальный сигнал говорит, что падение будет принято, когда это не так.

    next to drop target

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

Я не совсем уверен, что это работает правильно. Когда я рисую предмет, он исчезает вечно ....? – JokerMartini

+0

Он отлично работал на Linux, но я просто тестировал Windows дома, и даже элемент исчез. Также может быть версия python или я как-то сделал «тривиальное» изменение и сломал код ... – Mel

+0

Я нашел некоторые ссылки с похожими проблемами в Windows, вот один отчет об ошибке: https://bugreports.qt.io/browse/QTBUG -46642. – Mel