¿Cómo respondo a una operación interna de arrastrar y soltar usando un QListWidget?

StackOverflow https://stackoverflow.com/questions/1224432

  •  11-07-2019
  •  | 
  •  

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.

¿Fue útil?

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_())
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top