How can I respond to an internal drag and drop operation using a QListWidget?

I have a Qt4 application (using PyQt bindings) that contains a QListWidget initialized like this:

 class MyList(QtGui.QListWidget): def __init__(self): QtGui.QListWidget.__init__(self) self.setDragDropMode(self.InternalMove) 

I can add items, and this allows me to drag and drop to reorder the list. But how do I get notified when a user reorders a list? I tried adding the dropMimeData(self, index, data, action) method to the class, but it is never called.

+7
qt qt4 pyqt pyqt4
source share
6 answers

I just had to deal with this and itโ€™s a pain in the ass, but here's what to do:

You need to set eventFilter to a subclass of ListWidget and then watch the ChildRemoved event. This event covers movement as well as deletion, so it should work for reinstalling items with drag and drop inside the list.

I am writing my Qt in C ++, but here is the pythonification version:

 class MyList(QtGui.QListWidget): def __init__(self): QtGui.QListWidget.__init__(self) self.setDragDropMode(self.InternalMove) self.installEventFilter(self) def eventFilter(self, sender, event): if (event.type() == QEvent.ChildRemoved): self.on_order_changed() return False # don't actually interrupt anything def on_order_changed(self): # do magic things with our new-found knowledge 

If you have another class containing this list, you may need to move the event filter method. Hope this helps, I know that I had to deal with it for a day before I figure it out.

+7
source share

I have an easier way. :)

In fact, you can access the internal listwidget model using myList->model() - and from there many available signals will be available.

If you only care about drag & drop, connect to layoutChanged . If you have navigation buttons (which usually execute with delete + add), connect to rowsInserted too.

If you want to know what has changed, rowsMoved may be better than layoutChanged .

+12
source share

I found Trey Stout's answer , but I obviously got events when the list order didn't actually change. I turned to Chani's answer , which works as needed, but without code, I needed a little work on implementation in python.

I thought I would share a code snippet to help future visitors:

 class MyList(QListWidget): def __init__(self): QListWidget.__init__(self) self.setDragDropMode(self.InternalMove) list_model = self.model() list_model.layoutChanged.connect(self.on_layout_changed) def on_layout_changed(self): print "Layout Changed" 

This is tested in PySide, but sees no reason why it will not work in PyQt.

+5
source share

Not a solution, but some ideas:

You should probably check what is returned by the supportedDropActions method. You may need to overwrite this method by including Qt :: MoveAction or Qt :: CopyAction.

You have a QListView :: indexesMoved signal, but I'm not sure if it will exit if you use a QListWidget. It is worth checking out.

+1
source share

I know this is old, but I was able to get my code to work using Trey's answer and wanted to share my python solution. This is for a QListWidget inside a QDialog , not a subclass.

 class NotesDialog(QtGui.QDialog): def __init__(self, notes_list, notes_dir): QtGui.QDialog.__init__(self) self.ui=Ui_NotesDialog() # the notesList QListWidget is created here (from Qt Designer) self.ui.setupUi(self) # install an event filter to catch internal QListWidget drop events self.ui.notesList.installEventFilter(self) def eventFilter(self, sender, event): # this is the function that processes internal drop in notesList if event.type() == QtCore.QEvent.ChildRemoved: self.update_views() # do something return False # don't actually interrupt anything 
+1
source share

The QListWidget.model() approach seemed the most elegant of the proposed solutions, but it did not work for me in PyQt5. I donโ€™t know why, but maybe something has changed in the transition to Qt5. The eventFilter approach did work, but there is another alternative worth considering: overloading QDropEvent and checking if event.source is itself. See the code below, which is MVCE with all proposed solutions encoded for verification in PyQt5:

 import sys from PyQt5 import QtGui, QtWidgets, QtCore class MyList(QtWidgets.QListWidget): itemMoved = QtCore.pyqtSignal() def __init__(self): super(MyList, self).__init__() self.setDragDropMode(self.InternalMove) list_model = self.model() # list_model.layoutChanged.connect(self.onLayoutChanged) # doesn't work # self.installEventFilter(self) # works self.itemMoved.connect(self.onLayoutChanged) # works def onLayoutChanged(self): print("Layout Changed") def eventFilter(self, sender, event): """ Parameters ---------- sender : object event : QtCore.QEvent """ if event.type() == QtCore.QEvent.ChildRemoved: self.onLayoutChanged() return False def dropEvent(self, QDropEvent): """ Parameters ---------- QDropEvent : QtGui.QDropEvent """ mime = QDropEvent.mimeData() # type: QtCore.QMimeData source = QDropEvent.source() if source is self: super(MyList, self).dropEvent(QDropEvent) self.itemMoved.emit() app = QtWidgets.QApplication([]) form = MyList() for text in ("one", "two", "three"): item = QtWidgets.QListWidgetItem(text) item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable) item.setCheckState(QtCore.Qt.Checked) form.addItem(item) form.show() sys.exit(app.exec_()) 
0
source share

All Articles