2013-12-16 14 views
5

Tôi đang cố gắng thêm một ràng buộc vào chức năng kéo và thả QTreeWidget để ngăn các chi nhánh xâm nhập vào một chi nhánh khác trong thư mục gốc khác.pyside qtreewidget hạn chế kéo và thả

Dưới đây là ví dụ để làm cho mọi thứ rõ ràng hơn:
Tôi có 4 đối tượng. Cho phép gọi chúng là táo, chuối, cà rốt, sầu riêng.

Cây trông như thế này:

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

Vì vậy, các đối tượng được phép kéo và thả từ BackgroundObjects để ForgroundObjects, và lại visa trên cùng một gốc, nhưng họ không được phép kéo và rơi vào một nhánh trên một gốc khác.

Tôi đã thử triển khai lại và phân lớp dragMoveEvent, dragEnterEvent và dropEvent và nếu tôi gọi chấp nhận sự kiện trong dragEnterEvent, nó sẽ gọi hàm dragMoveEvent sau (mà tôi mong đợi). Tuy nhiên, dropEvent chỉ được gọi khi tôi thả bên ngoài QTreeWidget.

Điều tôi muốn làm là kiểm tra ông bà của các đối tượng đã chọn trước khi chúng được di chuyển và ông bà mới được đề xuất để xem chúng có giống nhau hay không. Nếu có thì hãy chấp nhận di chuyển. Nếu không thì hãy bỏ qua di chuyển.

Tôi đã tìm kiếm để xem có câu trả lời nào không, và cho đến nay tôi chưa thấy bất kỳ câu trả lời nào cho những gì tôi đang cố gắng làm. Có lẽ là gần nhất sẽ là hai câu hỏi này từ Stack Overflow:
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)

Trả lời

3

Qt dường như không làm cho các loại điều này rất dễ dàng.

Điều tốt nhất tôi có thể đưa ra là tạm thời đặt lại cờ mục trong các sự kiện kéo-nhập và kéo-di chuyển. Ví dụ dưới đây tính toán mục cấp cao nhất hiện tại để hạn chế kéo và thả. Nhưng nó cũng có thể được thực hiện bằng cách sử dụng setData() để thêm số nhận dạng cho từng mục.

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

bạn đề cập đến nó có thể là được thực hiện bằng cách sử dụng setData. Bạn có thể cho thấy cách đó sẽ được thực hiện? – JokerMartini

+0

@JokerMartini. Tôi đã sửa một lỗi trong ví dụ, nhưng tôi không nghĩ rằng giải pháp tổng thể là rất đáng tin cậy, và tôi có lẽ sẽ không khuyên bạn nên sử dụng nó ngay bây giờ. Sử dụng 'setData' sẽ không tạo ra bất kỳ sự khác biệt nào. Tại thời điểm này, tôi e rằng tôi không có bất kỳ ý tưởng nào tốt hơn và không có thời gian để xem xét thêm nữa. – ekhumoro

+0

bạn có thể giúp tôi với tình huống của tôi được không? Tôi đã cập nhật bài đăng của mình ở đây.Tôi có nó gần như làm việc nhưng nó có một vài lỗi http://stackoverflow.com/questions/34133789/controlling-drag-n-drop-disable-enable-of-qtreewidget-items-python?noredirect=1#comment56017728_34133789 – JokerMartini

1

Đây là giải pháp của tôi (mã đầy đủ ở cuối), phân lớp QTreeWidget. Tôi đã cố gắng để có một cái gì đó rất chung chung mà nên làm việc cho rất nhiều trường hợp. Một vấn đề vẫn còn với các dấu hiệu trực quan khi kéo. Các phiên bản trước đã không hoạt động trên các cửa sổ, tôi hy vọng điều này sẽ. Nó hoạt động hoàn toàn tốt trên Linux.


loại Xác định

Mỗi mục trong cây có một loại (một chuỗi), mà tôi lưu trữ trong QtCore.Qt.ToolTipRole. Bạn cũng có thể phân lớp QTreeWidgetItem để có thuộc tính cụ thể category.

Chúng tôi xác định trong từ điển settings tất cả các danh mục, với danh sách các danh mục chúng có thể được thả vào và cờ cần đặt. Ví dụ:

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

Mỗi mục của thể loại "gia đình" có thể nhận kéo, và chỉ có thể được thả trong "root" (mục gốc vô hình). Mọi mục của danh mục "trẻ em" chỉ có thể rơi vào "gia đình".


Thêm mục vào cây

Phương pháp addItem(strings,category,parent=None) tạo ra một QTreeWidgetItem(strings,parent) với một công cụ tip "category" và những lá cờ tương ứng trong setting. Nó trả về mục. Ví dụ:

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

table example


reimplementation bằng cách kéo và thả

Các mục được kéo được xác định với self.currentItem() (nhiều lựa chọn không được xử lý). Danh sách các danh mục mà mục này có thể bị xóa là okList=self.settings[itemBeingDragged.data(0,role)][0].

Mục dưới chuột, còn gọi là "mục tiêu thả", là self.itemAt(event.pos()). Nếu chuột ở trên không gian trống, mục tiêu thả được đặt thành mục gốc.

  • dragMoveEvent (visual cue cho dù sự sụt giảm sẽ được chấp nhận/bỏ qua)
    Nếu mục tiêu thả là trong okList, chúng ta gọi là thường xuyên dragMoveEvent. Nếu không, chúng tôi phải kiểm tra "bên cạnh mục tiêu thả". Trong hình ảnh dưới đây, mục dưới chuột là Robertsons, nhưng mục tiêu thả thực sự là mục gốc (xem dòng dưới đây Robertsons?). Để khắc phục điều này, chúng tôi kiểm tra xem mục có thể được kéo vào phụ huynh của mục tiêu thả hay không. Nếu không, chúng tôi gọi số event.ignore().

    Vấn đề còn lại duy nhất là khi chuột thực sự là trên "Robertsons": sự kiện kéo được chấp nhận. Dấu hiệu trực quan cho biết sự sụt giảm sẽ được chấp nhận khi không.

    next to drop target

  • dropEvent
    Thay vì chấp nhận hoặc bỏ qua sự sụt giảm, đó là rất khó khăn vì "bên cạnh thả mục tiêu", chúng tôi luôn chấp nhận sự sụt giảm, và sau đó sửa chữa những sai lầm.
    Nếu cha mẹ mới giống với cha mẹ cũ hoặc nếu nó nằm trong okList, chúng tôi sẽ không làm gì cả. Nếu không, chúng tôi sẽ đặt lại mục đã kéo trong phụ huynh cũ.

    Đôi khi mục giảm sẽ sụp đổ, nhưng điều này có thể dễ dàng được cố định với itemBeingDragged.setExpanded()


Cuối cùng, toàn bộ mã với hai ví dụ:

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

Tôi không hoàn toàn chắc chắn rằng nó hoạt động chính xác. Khi tôi dragndrop một mục, nó biến mất mãi mãi ....? – JokerMartini

+0

Nó hoạt động tốt trên Linux, nhưng tôi chỉ thử nghiệm trên Windows ở nhà và thực sự mục biến mất. Cũng có thể là phiên bản python hoặc bằng cách nào đó tôi đã thực hiện thay đổi "tầm thường" và đã phá vỡ mã ... – Mel

+0

Tôi đã tìm thấy một số liên kết có vấn đề tương tự trên cửa sổ, đây là một báo cáo lỗi: https://bugreports.qt.io/browse/QTBUG -46642. – Mel