¿Cómo respondo a una operación interna de arrastrar y soltar usando un QListWidget?
Pregunta
Tengo una aplicación Qt4 (usando los enlaces PyQt) que contiene un QListWidget
, inicializado así:
class MyList(QtGui.QListWidget):
def __init__(self):
QtGui.QListWidget.__init__(self)
self.setDragDropMode(self.InternalMove)
Puedo agregar elementos, y esto me permite arrastrar y soltar para reordenar la lista. Pero, ¿cómo obtengo una notificación cuando el usuario reordena la lista? Intenté agregar un método dropMimeData (self, index, data, action)
a la clase, pero nunca se llama.
Solución
Solo tuve que lidiar con esto y es un fastidio, pero esto es lo que hay que hacer:
Debe instalar un eventFilter
en su subclase ListWidget
y luego observar el evento ChildRemoved
. Este evento cubre los movimientos y la eliminación, por lo que debería funcionar para reorganizar elementos con arrastrar y soltar dentro de una lista.
Escribo mi Qt en C ++, pero aquí hay una versión de pythonification:
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
Si tiene alguna otra clase que contiene esta lista, es posible que desee mover el método de filtro de eventos allí. Espero que esto ayude, sé que tuve que luchar con esto por un día antes de resolverlo.
Otros consejos
Tengo una manera más fácil. :)
En realidad puede acceder al modelo interno del widget de lista con myList- > model ()
, y desde allí hay muchas señales disponibles.
Si solo le importa arrastrar y soltar, conéctese a layoutChanged
.
Si tiene botones de movimiento (que generalmente se implementan con eliminar + agregar), conéctese también a rowsInserted
.
Si desea saber qué se movió, rowsMoved
podría ser mejor que layoutChanged
.
Encontré La respuesta de Trey Stout funcionó, sin embargo, obviamente recibía eventos cuando el orden de la lista no había cambiado realmente. Me dirigí a la respuesta de Chani que funciona según lo requerido, pero sin código me llevó un poco de trabajo implementarlo en Python.
Pensé que compartiría el fragmento de código para ayudar a futuros visitantes:
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"
Esto se prueba en PySide pero no veo ninguna razón por la que no funcionaría en PyQt.
No es una solución, pero algunas ideas:
Probablemente deberías comprobar lo que devuelve el método soportadoDropActions. Puede ser que necesite sobrescribir ese método, para incluir Qt :: MoveAction o Qt :: CopyAction.
Tiene la señal QListView :: indexesMoved, pero no estoy seguro de si se emitirá si está utilizando QListWidget. Vale la pena revisarlo.
Sé que esto es antiguo, pero pude hacer que mi código funcionara con la respuesta de Trey y quería compartir mi solución de Python. Esto es para un QListWidget
dentro de un QDialog
, no uno que esté subclasificado.
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
El enfoque QListWidget.model ()
parecía la más elegante de las soluciones propuestas, pero no me funcionó en PyQt5. No sé por qué, pero tal vez algo cambió en el cambio a Qt5. El enfoque eventFilter
funcionó, pero hay otra alternativa que vale la pena considerar: anular el QDropEvent y verificar si event.source es propio. Vea el código a continuación, que es un MVCE con todas las soluciones propuestas codificadas para registrar 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_())