3

i am currently working on a Python program, where i want to move items from one QListWidget to another with Drag and Drop.

i read this thread, and implemented the Drag and Drop as described. It works with standard QListWidgetItems. The Problem is, i wanna do this custom widgets inside the QListWidgetItems, as described here, to store an icon and several lines into 1 QListWidgetItem.

So when i now drag and drop an item into another list, it is empty, because it seems just to move/copy the QListWidgetItem without its ItemWidget.

Heres an example code:

    from PyQt4 import QtGui, QtCore
    import sys, os

    class ThumbListWidget(QtGui.QListWidget):
        def __init__(self, type, parent=None):
            super(ThumbListWidget, self).__init__(parent)
            self.setIconSize(QtCore.QSize(124, 124))
            self.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
            self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
            self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()
        else:
            super(ThumbListWidget, self).dragEnterEvent(event)

    def dragMoveEvent(self, event):
        if event.mimeData().hasUrls():
            event.setDropAction(QtCore.Qt.CopyAction)
            event.accept()
        else:
            super(ThumbListWidget, self).dragMoveEvent(event)

    def dropEvent(self, event):
        if event.mimeData().hasUrls():
            event.setDropAction(QtCore.Qt.CopyAction)
            event.accept()
            links = []
            for url in event.mimeData().urls():
                links.append(str(url.toLocalFile()))
            self.emit(QtCore.SIGNAL("dropped"), links)
        else:
            event.setDropAction(QtCore.Qt.MoveAction)
            super(ThumbListWidget, self).dropEvent(event)


    class Dialog_01(QtGui.QMainWindow):
        def __init__(self):
            super(QtGui.QMainWindow,self).__init__()
            self.listItems={}

            myQWidget = QtGui.QWidget()
            myBoxLayout = QtGui.QVBoxLayout()
            myQWidget.setLayout(myBoxLayout)
            self.setCentralWidget(myQWidget)

            self.myQListWidget = ThumbListWidget(self)

            myBoxLayout.addWidget(self.myQListWidget)

            for index, name, icon in [
                ('No.1', 'Meyoko',  'icon.png'),
                ('No.2', 'Nyaruko', 'icon.png'),
                ('No.3', 'Louise',  'icon.png')]:
                # Create QCustomQWidget
                myQCustomQWidget = QCustomQWidget()
                myQCustomQWidget.setTextUp(index)
                myQCustomQWidget.setTextDown(name)
                myQCustomQWidget.setIcon(icon)
                # Create QListWidgetItem
            myQListWidgetItem = QtGui.QListWidgetItem(self.myQListWidget)
            # Set size hint
            myQListWidgetItem.setSizeHint(myQCustomQWidget.sizeHint())
            # Add QListWidgetItem into QListWidget
            self.myQListWidget.addItem(myQListWidgetItem)
            self.myQListWidget.setItemWidget(myQListWidgetItem, myQCustomQWidget)

        self.listWidgetB = ThumbListWidget(self)
        myBoxLayout.addWidget(self.listWidgetB)   


class QCustomQWidget (QtGui.QWidget):
    def __init__ (self, parent = None):
        super(QCustomQWidget, self).__init__(parent)
        self.textQVBoxLayout = QtGui.QVBoxLayout()
        self.textUpQLabel    = QtGui.QLabel()
        self.textDownQLabel  = QtGui.QLabel()
        self.textQVBoxLayout.addWidget(self.textUpQLabel)
        self.textQVBoxLayout.addWidget(self.textDownQLabel)
        self.allQHBoxLayout  = QtGui.QHBoxLayout()
        self.iconQLabel      = QtGui.QLabel()
        self.allQHBoxLayout.addWidget(self.iconQLabel, 0)
        self.allQHBoxLayout.addLayout(self.textQVBoxLayout, 1)
        self.setLayout(self.allQHBoxLayout)
        # setStyleSheet
        self.textUpQLabel.setStyleSheet('''
            color: rgb(0, 0, 255);
        ''')
        self.textDownQLabel.setStyleSheet('''
            color: rgb(255, 0, 0);
        ''')

    def setTextUp (self, text):
        self.textUpQLabel.setText(text)

    def setTextDown (self, text):
        self.textDownQLabel.setText(text)

    def setIcon (self, imagePath):
        self.iconQLabel.setPixmap(QtGui.QPixmap(imagePath))


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    dialog_1 = Dialog_01()
    dialog_1.show()
    dialog_1.resize(480,320)
    sys.exit(app.exec_())

So the basic question is: How can i transfer the items ItemWidget while using drag and drop.

Would it maybe be better to use a QListView for something like this?

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
KevKosDev
  • 811
  • 2
  • 9
  • 31
  • 1
    You cannot do this. The Item's data and flags have to be serialized during a drag and drop, and there is no way to serialize a `QWidget` (see: [Serializing Qt Data Types](https://doc.qt.io/qt-4.8/datastreamformat.html)). Also see [this relevant question](http://stackoverflow.com/q/40413221/984421). – ekhumoro Jan 11 '17 at 19:24
  • Not the Answer i was hoping for, but the one i expected .:) I couldnt even read the QWidget back out from the List. For now i went with recreating the dragged and dropped Entries. Tomorrow I will give QListView a try. – KevKosDev Jan 11 '17 at 21:17
  • 1
    The `QListWidget` class **is** a `QListView`, so it won't make any difference. The drag and drop mechanism is the same throughout Qt, so the same limitations apply. Why do you need to store the widgets as data in the items? Why not just store a key to a dictionary that holds references to the widgets? – ekhumoro Jan 11 '17 at 21:28
  • Not quite getting this. How would i identify the dragged Item and assign its widgets in the new ListWidget? – KevKosDev Jan 11 '17 at 21:42

1 Answers1

2

As stated in the comments to the question, there is no way to serialize a widget, so it cannot be transferred via drag and drop. However, you can use an item's setData method to store the data that is needed to re-create the item's widget. This will be far simpler to implement, because you can then use the rowsInserted signal of the list-widget's model to automatically add a new widget every time an item is inserted.

The script shown below implements that, and can handle dragging and dropping multiple items to and from each list-widget. Note that the drag/drop *event methods are no longer needed, and you no longer need to explicitly set the custom widgets.

from PyQt4 import QtGui, QtCore
import sys, os

class ThumbListWidget(QtGui.QListWidget):
    def __init__(self, type, parent=None):
        super(ThumbListWidget, self).__init__(parent)
        self.setIconSize(QtCore.QSize(124, 124))
        self.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
        self.setDefaultDropAction(QtCore.Qt.MoveAction)
        self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        self.setAcceptDrops(True)
        self.model().rowsInserted.connect(
            self.handleRowsInserted, QtCore.Qt.QueuedConnection)

    def handleRowsInserted(self, parent, first, last):
        for index in range(first, last + 1):
            item = self.item(index)
            if item is not None and self.itemWidget(item) is None:
                index, name, icon = item.data(QtCore.Qt.UserRole)
                widget = QCustomQWidget()
                widget.setTextUp(index)
                widget.setTextDown(name)
                widget.setIcon(icon)
                item.setSizeHint(widget.sizeHint())
                self.setItemWidget(item, widget)

class Dialog_01(QtGui.QMainWindow):
    def __init__(self):
        super(QtGui.QMainWindow,self).__init__()
        self.listItems = {}

        myQWidget = QtGui.QWidget()
        myBoxLayout = QtGui.QVBoxLayout()
        myQWidget.setLayout(myBoxLayout)
        self.setCentralWidget(myQWidget)

        self.myQListWidget = ThumbListWidget(self)

        myBoxLayout.addWidget(self.myQListWidget)

        for data in [
            ('No.1', 'Meyoko',  'icon.png'),
            ('No.2', 'Nyaruko', 'icon.png'),
            ('No.3', 'Louise',  'icon.png')]:
            myQListWidgetItem = QtGui.QListWidgetItem(self.myQListWidget)
            # store the data needed to create/re-create the custom widget
            myQListWidgetItem.setData(QtCore.Qt.UserRole, data)
            self.myQListWidget.addItem(myQListWidgetItem)

        self.listWidgetB = ThumbListWidget(self)
        myBoxLayout.addWidget(self.listWidgetB)

class QCustomQWidget (QtGui.QWidget):
    def __init__ (self, parent = None):
        super(QCustomQWidget, self).__init__(parent)
        self.textQVBoxLayout = QtGui.QVBoxLayout()
        self.textUpQLabel    = QtGui.QLabel()
        self.textDownQLabel  = QtGui.QLabel()
        self.textQVBoxLayout.addWidget(self.textUpQLabel)
        self.textQVBoxLayout.addWidget(self.textDownQLabel)
        self.allQHBoxLayout  = QtGui.QHBoxLayout()
        self.iconQLabel      = QtGui.QLabel()
        self.allQHBoxLayout.addWidget(self.iconQLabel, 0)
        self.allQHBoxLayout.addLayout(self.textQVBoxLayout, 1)
        self.setLayout(self.allQHBoxLayout)
        # setStyleSheet
        self.textUpQLabel.setStyleSheet('''
            color: rgb(0, 0, 255);
        ''')
        self.textDownQLabel.setStyleSheet('''
            color: rgb(255, 0, 0);
        ''')

    def setTextUp (self, text):
        self.textUpQLabel.setText(text)

    def setTextDown (self, text):
        self.textDownQLabel.setText(text)

    def setIcon (self, imagePath):
        self.iconQLabel.setPixmap(QtGui.QPixmap(imagePath))

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    dialog_1 = Dialog_01()
    dialog_1.show()
    dialog_1.resize(480,320)
    app.exec_()
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Thank you very much for your Example! It works like charm ! However, a quick note: It seems that in Python 2, you have to convert the QVariant in the "handleRowsInserted" function to a PyObject, with .toPyObject(), to get the original data out of it. Otherwise it throws the exception "QVariant not iterable" – KevKosDev Jan 12 '17 at 16:00