-1

I'm having a problem where a simple QTreeWidget drag and drop of items causes another item to disappear from the tree. This is on Mac Monterey 12.4, using Python 2.7 and PySide2. I swear that it had been previously working on a Windows platform, although it's been a while since I've tested that. I am also having problems with any widgets that had been added to the item getting lost when it re-creates the item in it's new place, so bonus points if anyone knows how to prevent that as well and can show a simple example.

(I'm working in Maya, so there is some code for displaying the UI there, and I haven't tested the section that would be more generic for use outside of Maya.)

Thanks so much for any help!...

import sys
from PySide2 import QtCore, QtWidgets


def main():
    try:
        app = QtWidgets.QApplication(sys.argv)
        ui = TreeUI()
        ui.show()
        app.exec_()
    except RuntimeError:
        from maya import OpenMayaUI as omui
        try:
            import shiboken2 as shiboken
        except ImportError:
            import shiboken
        pointer = omui.MQtUtil.mainWindow()
        win = shiboken.wrapInstance(long(pointer), QtWidgets.QWidget)
        ui = TreeUI(parent=win)
        ui.show()


class Tree(QtWidgets.QTreeWidget):
    def __init__(self, parent=None):
        super(Tree, self).__init__(parent)
        self.setHeaderLabels(('name', 'widget'))
        self.setSelectionMode(self.SingleSelection)
        self.setDragEnabled(True)
        self.setDropIndicatorShown(True)
        self.setDragDropMode(self.InternalMove)


class TreeUI(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(TreeUI, self).__init__(parent)
        widget = QtWidgets.QWidget(self)
        self.setCentralWidget(widget)
        tree = Tree()
        for x in range(0, 6):
            item = QtWidgets.QTreeWidgetItem(tree, ('item{}'.format(x), None))
            item.setFlags(item.flags() & ~QtCore.Qt.ItemIsDropEnabled)
            button = QtWidgets.QPushButton('Button{}'.format(x))
            tree.setItemWidget(item, 1, button)
        layout = QtWidgets.QVBoxLayout(widget)
        layout.addWidget(tree)


main()
Jesse
  • 143
  • 1
  • 1
  • 10
  • 1
    It would more useful to know the *Qt* version used: `print(QtCore.__version__)`, as it might be related to [qtbug-77427](https://bugreports.qt.io/browse/QTBUG-77427). You can't prevent removal of item widgets on drag&drop, because that operation actually creates a *new* index based on serialized data of the dragged one. The only way to achieve this is to connect to the model's [`rowsInserted`](https://doc.qt.io/qt-5/qabstractitemmodel.html#rowsInserted) signal to a function that creates a *new* widget for the new row when appropriate. – musicamante Jun 24 '22 at 11:34
  • Yes, thanks, it's version 5.12.5. I had read about a bug related to this, and wasn't sure if there was a work around. I've also read up about destroying and re-creating the tree item, and I gather that I need to do something with QMimeType and deleting then re-creating entries on the fly, I just haven't fully wrapped my head around how that works yet. – Jesse Jun 24 '22 at 16:13
  • I don't use Maya, so I cannot be completely sure, but as far as I know it uses an internal Qt library (not installed system wide), which means that you probably cannot update it and need to wait for a possible Maya update. If that's the case, and as far as I can see, you could try override `dropEvent()`, call `event.accept()` and then call the base implementation with `super().dropEvent(event)`. Unfortunately, I cannot test it, so you have to test on your own. – musicamante Jun 24 '22 at 16:22
  • Ah hah! Just accepting the event and calling the super dropEvent didn't work but referencing the following post, and using self.model().dropMimeData() did help: https://stackoverflow.com/questions/59116961/how-to-alter-dropevent-action-in-treeview-without-loosing-basic-drag-n-drop-func – Jesse Jun 28 '22 at 03:44

1 Answers1

0

Ah hah! I got it working, thanks to @musicamante, I was able to get it working. Adding in a custom dropEvent that calls the dropMimeData function, and then accepts the event, seems to fix it up:

class Tree(QtWidgets.QTreeWidget):

    def __init__(self, parent=None):
        super(Tree, self).__init__(parent)
        self.setHeaderLabels(('name', 'widget'))
        self.setSelectionMode(self.SingleSelection)
        self.setDragEnabled(True)
        self.setDropIndicatorShown(True)
        self.setDragDropMode(self.InternalMove)

    def dropEvent(self, event):
        index = self.indexAt(event.pos())
        parent = index.parent()
        self.model().dropMimeData(
            event.mimeData(),
            event.dropAction(),
            index.row(),
            index.column(),
            parent
        )
        event.accept()

It still has the problem of the associated widgets being lost, but I can post that as a separate issue.

Jesse
  • 143
  • 1
  • 1
  • 10
  • Just to know: movement of item/index widgets on drag&drop is quite a difficult task. The fact is, d&d, even for internal movement, ***always*** uses serialization of source indexes, meaning that there is almost *no* absolute certainty about the source of the drop event. The result is that even internal d&d acts like the operation comes from external sources, meaning that the index widgets will ***always*** be destroyed. I'm working on a related question about this, but be aware: there is absolutely ***no*** definite solution for this, and especially for tree models. – musicamante Jun 28 '22 at 04:21
  • Ah, thanks! Would QTreeView work any better for these types of issues? Or any other alternative? It seems like a pretty huge and glaring problem that I first encountered a year or more ago, and hadn't been able to work around. It seems like a solution would've come up by now. Thanks for your help on the issue! – Jesse Jun 28 '22 at 05:20
  • QTreeView is just the base class from which QTreeWidget inherits from, and, most importantly, the behavior comes from QAbstractItemView, which is the base class for all Qt item views. I realize that this behavior might seem unexpected and probably unwanted, but it's done for performance reasons. Item views can possibly have tens of thousands of indexes. It's quite obvious that the default behavior is to *automatically destroy* editors as soon as they're not needed anymore: consider the complexity of implementing a system that would allow *complete control* of widget removal whenever the -> – musicamante Jun 28 '22 at 05:36
  • -> model is completely cleared. The API aims for maximum performance considering a statistical usage. Item views can also provide visualization of SQL database, meaning thousands, or even millions of indexes. It must work as its best, all considering. And, since QTreeWidget is based on QTreeView, the behavior is (and *must* be) the same. So, no, there wouldn't be any change. The implementation has to be completely customized, and can only be achieved by accurate work arounds (which include possible unwanted behavior, but that's expected: tree models are complex by nature). – musicamante Jun 28 '22 at 05:36