1
  1. Nodes of the same level can be dragged and prevented from entering each other, not other parent nodes.

enter image description here

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

  • Sorry but your question is really confuse. Can you try to clarify what you want (or don't want) to happen? If you have difficulties with English language, please find someone experienced enough to help you translate your question from your mother tongue. – musicamante Jul 14 '22 at 15:13
  • Please try to explain what is your *final* and *actual* purpose. Do you want to just prevent that `11` is able to be dropped into `12`? Or that `12` won't accept *any* drop? Or that `11` cannot be dropped on any/some items? The difference is extremely important: drag and drop is quite complex, and if you need some *specific* behavior depending on the "source" object(s) and "target" ones, that can be tricky: you can prevent *any* drop on an item, but if you want to decide if the drop is acceptable depending on the source, things are much more complex. – musicamante Jul 15 '22 at 01:56
  • Sorry but you didn't really answer my questions. For instance, what if the user wants to drop `22` into `12`? Tree structures are *extremely* complex: objects can be dropped into a common ancestor, or to a completely different parent. You have to be more specific in what you want to do, and, most importantly, you have to understand the possible ramifications of drag and drop for those complex structures. Please, take your time and [edit] your question by carefully explaining what is your actual purpose. – musicamante Jul 15 '22 at 04:01
  • I tried to update the question, would that be a better statement? – elephant111 Jul 18 '22 at 06:56

1 Answers1

2

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_()
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • I didn't use serFlag in my code, I found a similar problem, but I couldn't read his code. I am a beginner of PyQt. **https://stackoverflow.com/questions/20618044/pyside-qtreewidget-constrain-drag-and-drop** – elephant111 Jul 17 '22 at 12:56
  • My requirement is not to prevent node 1 and node 2 from dragging and dropping, but node 1 and node 2 can drag and drop each other, but node 2 cannot go inside node 1.Thanks to your editor, which is important to me as a new stackOverflow user. Thank you very much! – elephant111 Jul 17 '22 at 12:59
  • That is exactly what my solution does - it prevents dropping ***INTO*** items, but it does not prevent any other drag and drop operations, so the items can still be moved around. I have added a complete example to my answer that works as you asked - please try it. Note that all tree-widget items have a *default set of flags* which control how they behave, so that is why they need to be changed using `setFlags`. – ekhumoro Jul 17 '22 at 13:57
  • My god! Now I see what you mean, this code prevents item from being dragged in by other items.Your first answer said delete, not add, which led to my misunderstanding. – elephant111 Jul 17 '22 at 14:03
  • Combined with the code you proposed, I have modified it, but combined with my latest problem, I have encountered difficulties. For example, **setFlags(child3_2.flags() & ~Qt.ItemIsDropEnabled)** for node3_2 would cause its child nodes to be unable to drag, which is not what I want. I want nodes at the same level to be able to drag and drop each other and not get inside each other.. – elephant111 Jul 18 '22 at 07:03
  • @elephant111 I have updated my answer with an alternative solution, which should do what you want. But ***please note carefully*** that the drop target between items is quite small (since dropping *on* items isn't allowed), so the mouse cursor must be positioned fairly precisely. – ekhumoro Jul 18 '22 at 12:50
  • I have put your code into my project, thank you very much for your help. – elephant111 Jul 19 '22 at 11:47