Search code examples
pythonpyqtpyqt5qmainwindowqlistview

PyQt5 draggable icons from a listview to another


I struggle to drag and drop icons from a listview (ViewMode set to IconMode) to another. It's similar to what I've found in the docs.

listviews example

Scenario: A user drags the QIcon 1 from ListView 1 and drops it to ListView 2. ListView 2 should add QIcon 1 to its model. Moreover I want to do some background work when a specific QIcon is added to ListView 2. How do I know that QIcon 1 was dropped to ListView 2 and not QIcon 2?

mainwindow (sets up the layout, loads the images into listview 1):

class Ui_MainWindow(object):
...
    def loadImages(self):
        model = QStandardItemModel()
        images = Path("images").glob("*.*")
        for image in images:
            item = QStandardItem()
            item.setIcon(QIcon(str(image)))
            model.appendRow(item)

        self.listView1.setModel(model)

listview 1:

class ListView1(QListView):
    def __init__(self):
        super().__init__()
        self.setAcceptDrops(False)
        self.setViewMode(QtWidgets.QListView.IconMode)
        self.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.setIconSize(QSize(50, 50))
        self.setResizeMode(QtWidgets.QListView.Adjust)
        self.setDragDropMode(QAbstractItemView.DragOnly)

listview 2:

class ListView2(QListView):
    def __init__(self):
        super().__init__()
        self.setViewMode(QtWidgets.QListView.IconMode)
        self.setDragDropMode(QAbstractItemView.DropOnly)
        self.setIconSize(QSize(50, 50))
        self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        event.accept()

    def dragMoveEvent(self, event):
        event.accept()

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

How can I drag and drop an icon from listview 1 to listview 2 and access its properties?


Solution

  • It is not necessary to overwrite dragEnterEvent, dragMoveEvent or dropEvent since those implementations already exist and work correctly, the example you point out is for other types of widgets that do not have those events implemented.

    from pathlib import Path
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class LListView(QtWidgets.QListView):
        def __init__(self, parent=None):
            super(LListView, self).__init__(parent)
            self.model = QtGui.QStandardItemModel(self)
            self.setModel(self.model)
    
            self.setAcceptDrops(False)
            self.setViewMode(QtWidgets.QListView.IconMode)
            self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
            self.setIconSize(QtCore.QSize(50, 50))
            self.setResizeMode(QtWidgets.QListView.Adjust)
            self.setDragDropMode(QtWidgets.QAbstractItemView.DragOnly)
    
    class RListView(QtWidgets.QListView):
        def __init__(self, parent=None):
            super(RListView, self).__init__(parent)
            self.model = QtGui.QStandardItemModel(self)
            self.setModel(self.model)
    
            self.setAcceptDrops(True)
            self.setViewMode(QtWidgets.QListView.IconMode)
            self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
            self.setIconSize(QtCore.QSize(50, 50))
            self.setResizeMode(QtWidgets.QListView.Adjust)
            self.setDragDropMode(QtWidgets.QAbstractItemView.DropOnly)
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
    
            central_widget = QtWidgets.QWidget()
            self.l_view = LListView()
            self.r_view = RListView()
    
            self.setCentralWidget(central_widget)
            lay = QtWidgets.QHBoxLayout(central_widget)
            lay.addWidget(self.l_view)
            lay.addWidget(self.r_view)
            self.loadImages()
    
        def loadImages(self):
            images = Path("images").glob("*.*")
            for image in images:
                item = QtGui.QStandardItem()
                item.setIcon(QtGui.QIcon(str(image)))
                self.l_view.model.appendRow(item)
    
    
    if __name__ == '__main__':
        import sys
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.show()
        sys.exit(app.exec_())
    

    Update:

    If you want to add an identifier this can be done through a role that you pass when creating the item, and then in the dropEvent() get all the roles, then get the role you want and through it the identifier:

    from pathlib import Path
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class LListView(QtWidgets.QListView):
        def __init__(self, parent=None):
            super(LListView, self).__init__(parent)
            self.m_model = QtGui.QStandardItemModel(self)
            self.setModel(self.m_model)
    
            self.setAcceptDrops(False)
            self.setViewMode(QtWidgets.QListView.IconMode)
            self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
            self.setIconSize(QtCore.QSize(50, 50))
            self.setResizeMode(QtWidgets.QListView.Adjust)
            self.setDragDropMode(QtWidgets.QAbstractItemView.DragOnly)
    
    class RListView(QtWidgets.QListView):
        def __init__(self, parent=None):
            super(RListView, self).__init__(parent)
            self.m_model = QtGui.QStandardItemModel(self)
            self.setModel(self.m_model)
    
            self.setAcceptDrops(True)
            self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
            self.setIconSize(QtCore.QSize(50, 50))
            self.setResizeMode(QtWidgets.QListView.Adjust)
            self.setDragDropMode(QtWidgets.QAbstractItemView.DropOnly)
    
        def dropEvent(self, event):
            last_row_count = self.model().rowCount()
            super(RListView, self).dropEvent(event)
            # check if an item is added
            if self.model().rowCount() > last_row_count:
                md = event.mimeData()
                fmt = "application/x-qabstractitemmodeldatalist"
                if md.hasFormat(fmt):
                    encoded = md.data(fmt)
                    stream = QtCore.QDataStream(encoded, QtCore.QIODevice.ReadOnly)
                    datas = []
                    item = {}
                    while not stream.atEnd():
                        row = stream.readInt32()
                        column = stream.readInt32()
                        map_items = stream.readInt32()
                        for i in range(map_items):
                            key = stream.readInt32()
                            value = QtCore.QVariant()
                            stream >> value
                            item[QtCore.Qt.ItemDataRole(key)] = value
                        datas.append(item)
                    for data in datas:
                        identifier = data[QtCore.Qt.UserRole+1].value()
                        print("identifier: ", identifier)
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
    
            central_widget = QtWidgets.QWidget()
            self.l_view = LListView()
            self.r_view = RListView()
    
            self.setCentralWidget(central_widget)
            lay = QtWidgets.QHBoxLayout(central_widget)
            lay.addWidget(self.l_view)
            lay.addWidget(self.r_view)
            self.loadImages()
    
        def loadImages(self):
            images = Path("images").glob("*.*")
            for i, image in enumerate(images):
                item = QtGui.QStandardItem()
                identifier = "img_{:06d}".format(i+1)
                item.setData(identifier, QtCore.Qt.UserRole+1)
                item.setIcon(QtGui.QIcon(str(image)))
                self.l_view.m_model.appendRow(item)
    
    
    if __name__ == '__main__':
        import sys
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.show()
        sys.exit(app.exec_())