2013-12-16 47 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 

所以,對象可以被拖動並從BackgroundObjects下降到ForgroundObjects,並於同根反之亦然,但他們不能被拖動和放到不同根的分支上。

我試過重新實現和繼承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。我試圖讓一些非常一般的東西適用於很多情況。拖動時,視覺提示仍然存在一個問題。以前的版本沒有在Windows上工作,我希望這個會。它在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) 
} 

類別「家庭」的每個項目都可以接收拖動,並且只能放在「root」(不可見的根項目)中。 每個類別的「孩子」只能放入「家庭」。


添加項目到樹

的方法addItem(strings,category,parent=None)用刀尖「類別」和setting匹配標誌創建QTreeWidgetItem(strings,parent)。它返回該項目。例如:

dupont=ex.addItem(["Dupont"],"family") 
robert=ex.addItem(["Robertsons"],"family") 
ex.addItem(["Laura"],"children",dupont) 
ex.addItem(["Matt"],"children",robert) 
... 

table example


拖動的重新實現和Drop

被拖動與self.currentItem()確定的項目(多選擇不被處理)。該項目可以被刪除的類別列表是okList=self.settings[itemBeingDragged.data(0,role)][0]

鼠標下的項目,即「放置目標」,爲self.itemAt(event.pos())。如果將鼠標放在空白處,放置目標被設置爲根項目。

  • dragMoveEvent
    如果放置目標是okList(爲下降是否會被接受/忽略視覺提示),我們稱之爲正規dragMoveEvent。 如果不是,我們必須檢查「下降目標」。在圖像下方,鼠標下的物品是羅伯遜,但真正的下落目標是根物品(參見羅伯遜的行)。爲了解決這個問題,我們檢查它可以拖放放置目標的父項。如果沒有,我們稱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上進行測試,實際上項目消失。也可能是蟒蛇版本,或者我做了一個「微不足道」的變化,並打破了代碼... – Mel

+0

我發現一些類似的問題在Windows上的鏈接,這裏有一個錯誤報告:https://bugreports.qt.io/browse/QTBUG -46642。 – Mel