Đâ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)
...
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.
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_())
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
@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
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