1

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

Shadow
  • 65
  • 8
  • Generally I would recommend to add `print`s or use a debugger to check if variables and function results have the expected values to narrow down the problem. – Michael Butscher May 16 '23 at 09:53
  • @MichaelButscher you are right, the problem is in the first "if", I must have done something wrong or I forgot something I don't know what. – Shadow May 16 '23 at 10:54
  • In `dragMoveEvent` the `hasUrls` misses the parentheses. I guess that the mime data doesn''t contain URLs in the first place and the drag operation wouldn't have started. – Michael Butscher May 16 '23 at 11:19
  • @MichaelButscher ye but when i put the parentheses in the hasUrls the dragMove doesn´t work, the if on dropEvent is working but the "children" despear from the treewidget when i drop them anywere – Shadow May 16 '23 at 11:28
  • 1
    can you clarify what you are actually trying to do? your question is very abstract, mentioning only children and parents, however your code does stuff with URLs and local files. The approach might be very different if the solution needs to be pure QT, or if it can rely on the underlying file structure. – Hoodlum May 16 '23 at 13:05
  • 2
    @ShadowDasPizzas If you don't put the parentheses, that comparison is completely useless: it's exactly like doing `if True:`. In any case, please provide a valid [mre] (and check the indentation in the preview before submitting), as your code is inconsistent: it seems like you're creating items with paths as simple strings, but then you're checking *urls* in the drag data, which is never set in those items and therefore will never be considered in the mimedata. Also, that `QWidget` as `parent` argument is **wrong**, since `QWidget` is a class, and the parent can only be an instance or `None`. – musicamante May 16 '23 at 14:14
  • Thank you all, I will use your tips and try to rewrite my code,. – Shadow May 16 '23 at 15:01
  • Could also provide an example structure/dataset of the `QTreeWidgetItem`s you are populating your widget with? I'm mainly asking to know 1) how many levels do you have ? 2) are you setting any particular flags (`ItemIsDragEnabled` or `ItemIsDropEnabled`)? – Hoodlum May 17 '23 at 09:27
  • 1
    In any case, the reason things are disappearing when you drop them is that you've overwritten the default `dropEvent` function, which handles the insertion into the tree. you can fix this part by adding the line `super().dropEvent(event)` at the beginning of your `dropEvent` function. This will call the default `dropEvent` function first, then run your custom logic. – Hoodlum May 17 '23 at 09:37

1 Answers1

2

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()

Hoodlum
  • 950
  • 2
  • 13