2013-12-16 3 views
5

QTreeWidgetのドラッグ&ドロップ機能に制約を追加して、別のルートの別のブランチにブランチが入ってこないようにしようとしています。pyside qtreewidgetドラッグアンドドロップを制限する

ここに、わかりやすい例があります。
私には4つのオブジェクトがあります。リンゴ、バナナ、ニンジン、ドリアンと呼ぶことができます。

ツリーは次のようになります。だから、

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

、オブジェクトをドラッグすることが許可され、ForgroundObjectsにBackgroundObjectsから落下し、同じルートでその逆が、それらはドラッグすることが許可されていないされており、別のルートのブランチにドロップされました。

dragMoveEvent、dragEnterEvent、およびdropEventを再実装およびサブクラス化しようとしましたが、dragEnterEventのイベントでacceptを呼び出すと、後で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?noredirect=1#comment56017728_34133789 – JokerMartini

1

私の解決策(最後の完全なコード)は、QTreeWidgetのサブクラス化です。私は非常に一般的なものをたくさん試してみました。 1つの問題は、ドラッグするときに視覚的な手がかりに残ります。以前のバージョンはウィンドウでは機能しませんでしたが、これが実現することを願っています。 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) 
} 

カテゴリ「家族」のすべての項目は、ドラッグを受け取ることができ、かつ唯一の「ルート」(目に見えないルート項目)にドロップすることができます。 カテゴリ "子供"の各項目は、 "家族"にしかドロップできません。


ツリー

にツールチップ「カテゴリ」とQTreeWidgetItem(strings,parent)を生成しsettingにおけるマッチングフラグaddItem(strings,category,parent=None)方法アイテムを追加します。アイテムを返します。例:

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上で同様の問題を持つリンクがいくつか見つかりました。ここにはバグレポートが1つあります:https://bugreports.qt.io/browse/QTBUG -46642。 – Mel

関連する問題