Search code examples
pythonpyside6qtreewidget

Drag and Drop TreeWidget


I'm trying to make a treewidget that can change children between parents with the drag and drop method, but a children can never be turned in a parent. Right now this is my class:

class Tree(QTreeWidget):
def __init__(self, parent=QWidget):
    QTreeWidget.__init__(self,parent)

    
    self.setAcceptDrops(True)

    self.setDragEnabled(True)
    self.setDropIndicatorShown(True)
    self.setEditTriggers(QAbstractItemView.AllEditTriggers)
    self.setDragDropMode(QAbstractItemView.InternalMove)
    self.setSelectionMode(QAbstractItemView.ExtendedSelection)

def dragMoveEvent(self, event):
    event.setDropAction(QtCore.Qt.CopyAction)
    event.accept()
    
        
def dropEvent(self, event):
    event.setDropAction(QtCore.Qt.MoveAction)
    event.accept()

    target_item= self.itemAt(event.pos())
    if target_item:
        object_name= target_item.text(0)
      
        if self.currentItem().parent():
            parent_item= self.currentItem().parent()
            parent_name = parent_item.text(0)
            print("Número de Serie:", self.currentItem().text(0))
            print("Solto no:",object_name)
            print("Codigo de barras:",parent_name)
        else:
            print("Objeto de destino: ", object_name)
    else:
        print("Solto fora de qualquer")

So i upgrade my code and i tried to print were the child is droped and its all working but the same problem still stand´s the child when is dragged it always disapear from de treewidget and i dont know why.

Every help is welcome, thanks


Solution

  • Assuming we have the following tree structure, with only two levels of data:

    root
    ├─ A 
    │  ├─ A1
    │  └─ A2
    ├─ B
    │  └─ B1
    └─ C
    

    Our task is as follows :

    1. move children between parents
    2. children may never become parents

    Furthermore assuming :

    • parents cannot become children
    • parents do not need to be moved

    We can say :

    1. parents must allow drops, but cannot be dragged
    2. children can be dragged, but must not accept drops
    3. the root cannot be dragged and must not accept drops

    This is actually possible to achieve without modifying the default QTreeWidget's behavior - it's a question of setting the correct flags on the QTreeWidgetItems.

    from PyQt5 import QtCore, QtWidgets
    
    class Tree(QtWidgets.QTreeWidget):
        def __init__(self, parent: QtWidgets.QWidget | None = None):
            super().__init__(parent)
    
            self.setHeaderHidden(True)
            self.setAcceptDrops(True)
            self.setDragEnabled(True)
            self.setDropIndicatorShown(True)
    
            self.setEditTriggers(
                QtWidgets.QAbstractItemView.EditTrigger.AllEditTriggers)
            self.setDragDropMode(
                QtWidgets.QAbstractItemView.DragDropMode.InternalMove)
            self.setSelectionMode(
                QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
            
            # create the `root` item, which is "fixed" and cannot be modified by user
            self._root = QtWidgets.QTreeWidgetItem(self, ['.'])
            self._root.setFlags(
                # the root cannot be dragged and must not accept drops
                ~(QtCore.Qt.ItemFlag.ItemIsDropEnabled | QtCore.Qt.ItemFlag.ItemIsDragEnabled)
                )
            self.addTopLevelItem(self._root)
            # hide the root from user
            self.setRootIndex(self.indexFromItem(self._root, 0))
    
        def add_parent(self, parent_name:str, children_names:list[str]) : 
            '''takes a `parent` string and it's `child` strings to populate the widget'''
            # create the parent item
            parent_item = QtWidgets.QTreeWidgetItem(self._root, [parent_name])
            parent_item.setFlags(
                # parents must allow drops, but cannot be dragged
                (parent_item.flags() | QtCore.Qt.ItemFlag.ItemIsDropEnabled) & ~QtCore.Qt.ItemFlag.ItemIsDragEnabled
                )
            self._root.addChild(parent_item)
            
            # create the children items
            for child_name in children_names:
                child_item = QtWidgets.QTreeWidgetItem(parent_item, [child_name])
                child_item.setFlags(
                    # children can be dragged, but must not accept drops
                    (child_item.flags() | QtCore.Qt.ItemFlag.ItemIsDragEnabled) & ~QtCore.Qt.ItemFlag.ItemIsDropEnabled
                    )
                parent_item.addChild(child_item)
    
        def populate(self, data):
            '''populate the widget from an initial dataset'''
            for parent_name, children_names in data:
                self.add_parent(parent_name, children_names)
    
    if __name__ == "__main__":
        import sys
        app = QtWidgets.QApplication(sys.argv)
    
        # create tree widget
        tw = Tree()
    
        # populate with fake data :
        fakedata = [
            ('A', [ 'A1', 'A2' ]),
            ('B', [ 'B1' ]),
            ('C', []),
        ]
        tw.populate(fakedata)
    
        # run app
        tw.show()
        app.exec()