Reputation: 25
I overwrote the dragMoveEvent
and dropEvent
methods in the QTreeWidget.I only have the second layer
effect correct, which can drag each other and prevent entry into each other. But the first and third levels
have different errors. For example, node1-1-1 and Node1-2-2 cannot be dragged to each other. Node1 can be dragged inside Node2, which is not in accordance with my finished requirements.
class TreeWidget(QTreeWidget):
def __init__(self):
super().__init__()
self.setDragDropMode(QTreeWidget.InternalMove)
def dragMoveEvent(self, event):
current_item = self.currentItem()
item = self.itemAt(event.pos())
if current_item and current_item.type() == 0:
super().dragMoveEvent(event)
elif item and item.type() == 1 and current_item.parent() == item.parent():
super().dragMoveEvent(event)
elif item and item.type() == 2 and current_item.parent() == item.parent():
super().dragMoveEvent(event)
else:
event.ignore()
def dropEvent(self, event):
current_item = self.currentItem()
item = self.itemAt(event.pos())
if current_item and current_item.type() == 0:
super(TreeWidget, self).dropEvent(event)
elif item and item.type() == 1 and current_item.parent() == item.parent():
super(TreeWidget, self).dropEvent(event)
elif item and item.type() == 2 and current_item.parent() == item.parent():
super(TreeWidget, self).dropEvent(event)
else:
event.ignore()
class BasicTreeTest1(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('QTreeWidget')
self.tree = TreeWidget()
root1 = QTreeWidgetItem(self.tree,type=0)
root1.setText(0, 'node1')
child1_1 = QTreeWidgetItem(root1,type=1)
child1_1.setText(0, 'node1-1')
child1_1.setFlags(child1_1.flags() & ~Qt.ItemIsDropEnabled)
child1_2 = QTreeWidgetItem(root1, type=1)
child1_2.setText(0, 'node1-2')
child1_2.setFlags(child1_2.flags() & ~Qt.ItemIsDropEnabled)
child1_1_1 = QTreeWidgetItem(child1_1, type=2)
child1_1_1.setText(0, 'node1-1-1')
child1_1_1.setFlags(child1_1_1.flags() & ~Qt.ItemIsDropEnabled)
child1_2_1 = QTreeWidgetItem(child1_1, type=2)
child1_2_1.setText(0, 'node1-2-1')
child1_2_1.setFlags(child1_2_1.flags() & ~Qt.ItemIsDropEnabled)
root2 = QTreeWidgetItem(self.tree,type=0)
root2.setText(0, 'node2')
child2_1 = QTreeWidgetItem(root2, type=1)
child2_1.setText(0, 'node2-1')
child2_1.setFlags(child2_1.flags() & ~Qt.ItemIsDropEnabled)
child2_2 = QTreeWidgetItem(root2, type=1)
child2_2.setText(0, 'node2-2')
child2_2.setFlags(child2_2.flags() & ~Qt.ItemIsDropEnabled)
root3 = QTreeWidgetItem(self.tree, type=0)
root3.setText(0, 'node3')
child3_1 = QTreeWidgetItem(root3, type=1)
child3_1.setText(0, 'node3_1')
child3_1.setFlags(child3_1.flags() & ~Qt.ItemIsDropEnabled)
child3_2 = QTreeWidgetItem(root3, type=1)
child3_2.setText(0, 'node3_2')
child3_2.setFlags(child3_2.flags() & ~Qt.ItemIsDropEnabled)
child3_2_1 = QTreeWidgetItem(child3_2, type=2)
child3_2_1.setText(0, 'node3-2-1')
child3_2_1.setFlags(child3_2_1.flags() & ~Qt.ItemIsDropEnabled)
child3_2_2 = QTreeWidgetItem(child3_2, type=2)
child3_2_2.setText(0, 'node3-2-2')
child3_2_2.setFlags(child3_2_2.flags() & ~Qt.ItemIsDropEnabled)
# root1.setFlags(root1.flags() & ~Qt.ItemIsDropEnabled)
# root2.setFlags(root2.flags() & ~Qt.ItemIsDropEnabled)
# root3.setFlags(root3.flags() & ~Qt.ItemIsDropEnabled)
# child22.setFlags(child22.flags() & ~Qt.ItemIsDropEnabled)
self.setCentralWidget(self.tree)
self.tree.expandAll()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = BasicTreeTest1()
w.show()
sys.exit(app.exec_())
This site is my reference: https://blog.csdn.net/this_is_id/article/details/125258736
Upvotes: 1
Views: 901
Reputation: 120578
UPDATE:
Since your latest edits change the requirements, a different approach is needed. At the moment, the only solution I can see is to get the current drop-indicator, which then makes it possible to know if the drop-target is above or below an item. Qt uses a private method for this, which must be ported to PyQt to access the same functionality. I have implemented this in the demo script below, which only allows sibling items (at any level) to be re-ordered within their own parent:
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class TreeWidget(QTreeWidget):
def __init__(self):
super().__init__()
self.setDragDropMode(QTreeWidget.InternalMove)
def dragMoveEvent(self, event):
if self.canDrop(event):
super().dragMoveEvent(event)
else:
event.ignore()
def dropEvent(self, event):
if self.canDrop(event):
super().dropEvent(event)
else:
event.ignore()
def canDrop(self, event):
target = self.itemAt(event.pos())
current = self.currentItem()
if target is not None and target.parent() is current.parent():
index = self.indexFromItem(target)
indicator = self.dragIndicator(
event.pos(), self.visualItemRect(target), index)
return (indicator == QAbstractItemView.AboveItem or
indicator == QAbstractItemView.BelowItem)
return False
def dragIndicator(self, pos, rect, index):
indicator = QAbstractItemView.OnViewport
if not self.dragDropOverwriteMode():
margin = int(max(2, min(rect.height() / 5.5, 12)))
if pos.y() - rect.top() < margin:
indicator = QAbstractItemView.AboveItem
elif rect.bottom() - pos.y() < margin:
indicator = QAbstractItemView.BelowItem
elif rect.contains(pos, True):
indicator = QAbstractItemView.OnItem
else:
touching = rect.adjust(-1, -1, 1, 1)
if touching.contains(pos, False):
indicator = QAbstractItemView.OnItem
if (indicator == QAbstractItemView.OnItem and
not self.model().flags(index) & Qt.ItemIsDropEnabled):
if pos.y() < rect.center().y():
indicator = QAbstractItemView.AboveItem
else:
indicator = QAbstractItemView.BelowItem
return indicator
class BasicTreeTest1(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('QTreeWidget')
self.tree = TreeWidget()
root1 = QTreeWidgetItem(self.tree,type=0)
root1.setText(0, 'node1')
child1_1 = QTreeWidgetItem(root1,type=1)
child1_1.setText(0, 'node1-1')
child1_2 = QTreeWidgetItem(root1, type=1)
child1_2.setText(0, 'node1-2')
child1_1_1 = QTreeWidgetItem(child1_1, type=2)
child1_1_1.setText(0, 'node1-1-1')
child1_2_1 = QTreeWidgetItem(child1_1, type=2)
child1_2_1.setText(0, 'node1-2-1')
root2 = QTreeWidgetItem(self.tree,type=0)
root2.setText(0, 'node2')
child2_1 = QTreeWidgetItem(root2, type=1)
child2_1.setText(0, 'node2-1')
child2_2 = QTreeWidgetItem(root2, type=1)
child2_2.setText(0, 'node2-2')
root3 = QTreeWidgetItem(self.tree, type=0)
root3.setText(0, 'node3')
child3_1 = QTreeWidgetItem(root3, type=1)
child3_1.setText(0, 'node3_1')
child3_2 = QTreeWidgetItem(root3, type=1)
child3_2.setText(0, 'node3_2')
child3_2_1 = QTreeWidgetItem(child3_2, type=2)
child3_2_1.setText(0, 'node3-2-1')
child3_2_2 = QTreeWidgetItem(child3_2, type=2)
child3_2_2.setText(0, 'node3-2-2')
self.setCentralWidget(self.tree)
self.tree.expandAll()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = BasicTreeTest1()
w.setGeometry(600, 100, 500, 400)
w.show()
sys.exit(app.exec_())
PREVIOUS SOLUTION:
To prevent child items dropping onto each other you can change their item-flags. The default flags include Qt.ItemIsDropEnabled
, so you just need to remove that:
child111 = QTreeWidgetItem(root1, type=1)
child111.setFlags(child111.flags() & ~Qt.ItemIsDropEnabled)
Of course, this won't stop items being dragged and dropped between parents, but your current code already prevents that by reimplementing dragMoveEvent
and dropEvent
, so it looks like the above changes are all that's needed.
Here is a complete working example based on your code:
from PyQt5 import QtCore, QtWidgets
class TreeWidget(QtWidgets.QTreeWidget):
def __init__(self):
super().__init__()
self.setDragDropMode(QtWidgets.QTreeWidget.InternalMove)
def dragMoveEvent(self, event):
current_item = self.currentItem()
item = self.itemAt(event.pos())
if item and item.type() == 1 and current_item.parent() == item.parent():
super().dragMoveEvent(event)
else:
event.ignore()
def dropEvent(self, event):
current_item = self.currentItem()
item = self.itemAt(event.pos())
if item and item.type() == 1 and current_item.parent() == item.parent():
super(TreeWidget, self).dropEvent(event)
else:
event.ignore()
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.tree = TreeWidget()
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.tree)
root1 = QtWidgets.QTreeWidgetItem(self.tree,type=0)
root1.setText(0, '1')
child111 = QtWidgets.QTreeWidgetItem(root1,type=1)
child111.setFlags(child111.flags() & ~QtCore.Qt.ItemIsDropEnabled)
child111.setText(0, '11')
child12 = QtWidgets.QTreeWidgetItem(root1, type=1)
child12.setFlags(child12.flags() & ~QtCore.Qt.ItemIsDropEnabled)
child12.setText(0, '12')
root2 = QtWidgets.QTreeWidgetItem(self.tree,type=0)
root2.setText(0, '2')
child121 = QtWidgets.QTreeWidgetItem(root2, type=1)
child121.setFlags(child121.flags() & ~QtCore.Qt.ItemIsDropEnabled)
child121.setText(0, '21')
child122 = QtWidgets.QTreeWidgetItem(root2, type=1)
child122.setFlags(child122.flags() & ~QtCore.Qt.ItemIsDropEnabled)
child122.setText(0, '22')
self.tree.expandAll()
if __name__ == '__main__':
app = QtWidgets.QApplication(['Test'])
window = Window()
window.setWindowTitle('Test')
window.setGeometry(600, 100, 300, 200)
window.show()
app.exec_()
Upvotes: 2