Search code examples
pythonpython-2.7drag-and-droppyqt4qlistwidget

Drag'N'Drop custom widget items between QListWidgets


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?


Solution

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