elephant111
elephant111

Reputation: 25

PyQt5: Drag and drop between item peers in a QTreeWidget

  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

Upvotes: 1

Views: 901

Answers (1)

ekhumoro
ekhumoro

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

Related Questions