4

This is my first entry here so apologies for any newbie mistakes. I've searched both here and Google with no luck before posting.

I want to be able to add images to a QListWidget, using drag and drop from a file browser. Dropping a valid file on the list widget also needs to trigger a function in the main class of my app, and pass it the image path.

I found this code that does exactly that, but for PyQt4. Importing QtCore and QtGui from PySide instead of PyQt4 produces a segmentation fault when triggering a drag and drop event. There are no error messages.

I think I've traced this to an old way of handling signals and have tried using the new, more pythonic way described here. I can't seem to get it working, while still passing a list of URLs between classes however.

Any help would be greatly appreciated!

import sys
import os
from PyQt4 import QtGui, QtCore

class TestListView(QtGui.QListWidget):
    def __init__(self, type, parent=None):
        super(TestListView, self).__init__(parent)
        self.setAcceptDrops(True)
        self.setIconSize(QtCore.QSize(72, 72))

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

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

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

class MainForm(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainForm, self).__init__(parent)

        self.view = TestListView(self)
        self.connect(self.view, QtCore.SIGNAL("dropped"), self.pictureDropped)
        self.setCentralWidget(self.view)

    def pictureDropped(self, l):
        for url in l:
            if os.path.exists(url):
                print(url)                
                icon = QtGui.QIcon(url)
                pixmap = icon.pixmap(72, 72)                
                icon = QtGui.QIcon(pixmap)
                item = QtGui.QListWidgetItem(url, self.view)
                item.setIcon(icon)        
                item.setStatusTip(url)        

def main():
    app = QtGui.QApplication(sys.argv)
    form = MainForm()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()
Community
  • 1
  • 1
thimic
  • 189
  • 1
  • 8

3 Answers3

13

Found out how PySide signals work in the end. Here's the PyQt4 code above, ported to PySide. I'd love to hear if there are better solutions out there.

import sys
import os
from PySide import QtGui, QtCore

class TestListView(QtGui.QListWidget):

    fileDropped = QtCore.Signal(list)

    def __init__(self, type, parent=None):
        super(TestListView, self).__init__(parent)
        self.setAcceptDrops(True)
        self.setIconSize(QtCore.QSize(72, 72))

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

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

    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.fileDropped.emit(links)
        else:
            event.ignore()

class MainForm(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainForm, self).__init__(parent)

        self.view = TestListView(self)
        self.view.fileDropped.connect(self.pictureDropped)
        self.setCentralWidget(self.view)

    def pictureDropped(self, l):
        for url in l:
            if os.path.exists(url):
                print(url)                
                icon = QtGui.QIcon(url)
                pixmap = icon.pixmap(72, 72)                
                icon = QtGui.QIcon(pixmap)
                item = QtGui.QListWidgetItem(url, self.view)
                item.setIcon(icon)        
                item.setStatusTip(url)        

def main():
    app = QtGui.QApplication(sys.argv)
    form = MainForm()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()
thimic
  • 189
  • 1
  • 8
  • Hi there @thimic I know this post is old, but this worked well for me but I dont quite understand how this works, do you know of a good explanation video or documentation that I could go through to understand it better ? – Jody Stocks Feb 10 '20 at 14:20
  • Hi @JodyStocks, There’s an example of PySide and signals and slots here: http://zetcode.com/gui/pysidetutorial/eventsandsignals/ Or if there are particular things you’re wondering about, I’ll try my best to help answer them. – thimic Feb 11 '20 at 09:12
  • Hi there @thimic So I have been trying to get a better understanding of this but I am struggling a little. I have the Drop event Firing correctly but when It Fires, the actually move of the items in my QListWidget are not moving. If I set my Table Variable to be just a normal QListWidget() then the items move and update position. Is there no way to just trigger an event like you do with button clicks ? – Jody Stocks Feb 17 '20 at 10:19
  • Hi @JodyStocks, I am not quite sure what you mean about the items in the QListWidget not moving. Are you wishing to be able to drag files onto the widget from outside and also re-arrange the items in the widget internally? – thimic Feb 22 '20 at 00:48
  • @JodyStocks, As for the other question, there are two concepts at play. Events is one and Signals and Slots is another. Interactions in Qt cause events to fire that can be handled by implementing certain methods, such as dropEvent(). Separately, it is possible to set up communication between widgets using signals and slots, such as “fileDropped” and “clicked”. This answer explains the difference in a bit more detail: https://stackoverflow.com/a/3794944/3996477 – thimic Feb 22 '20 at 01:03
0

For me this worked fine (tested on PySide2 & PyQt5). This is not a drop signal, but a signal when the rows of a QListWidget have been moved. In my case this is always happening after dropping.

self.window.my_list_widget.model().rowsMoved.connect(self.my_method)
vince
  • 47
  • 1
  • 6
-1

I made some changes of @thimic 's code, so that it can run in my computer:

import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *

class TestListView(QListWidget):


    def __init__(self, type, parent=None):
        super(TestListView, self).__init__(parent)
        self.setAcceptDrops(True)
        self.setIconSize(QtCore.QSize(72, 72))

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

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

    def dropEvent(self, event):
        if event.mimeData().hasUrls:
            event.setDropAction(Qt.CopyAction)
            event.accept()
            links = []
            for url in event.mimeData().urls():
                links.append(str(url.toLocalFile()))
            print('Has drop Event! ')
        else:
            event.ignore()

class MainForm(QMainWindow):
    def __init__(self, parent=None):
        super(MainForm, self).__init__(parent)

        self.view = TestListView(self)
        self.view.fileDropped.connect(self.pictureDropped)
        self.setCentralWidget(self.view)

    def pictureDropped(self, l):
        for url in l:
            if os.path.exists(url):
                print(url)
                icon = QtGui.QIcon(url)
                pixmap = icon.pixmap(72, 72)
                icon = QtGui.QIcon(pixmap)
                item = QtGui.QListWidgetItem(url, self.view)
                item.setIcon(icon)
                item.setStatusTip(url)

def main():
    app = QApplication(sys.argv)
    form = MainForm()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()
Roberto Caboni
  • 7,252
  • 10
  • 25
  • 39
  • 1
    It would be helpful to the community if you explained a bit about what you needed to change and why. – JonC Jul 22 '20 at 07:08