Como faço para responder a uma operação de arrastar-e-soltar interna usando um QListWidget?

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

  •  11-07-2019
  •  | 
  •  

Pergunta

Eu tenho um aplicativo Qt4 (utilizando as ligações PyQt) que contém uma QListWidget, inicializada assim:

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

Eu posso adicionar itens, e isso me permite arrastar e soltar para reordenar a lista. Mas como faço para obter uma notificação quando a lista é reordenada pelo usuário? Eu tentei adicionar um método dropMimeData(self, index, data, action) para a classe, mas nunca é chamado.

Foi útil?

Solução

Eu apenas tive que lidar com isso e é um pé no saco, mas aqui está o que fazer:

Você tem que instalar um eventFilter na sua subclasse ListWidget e, em seguida, assistir para o evento ChildRemoved. Este evento cobre movimentos, bem como a remoção, assim que deve funcionar para os itens re-organizar com arrastar e soltar dentro de uma lista.

Eu escrevo meu Qt em C ++, mas aqui está uma versão 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

Se você tem alguma outra classe que contém essa lista, você pode querer mover o método de filtro de eventos lá. Espero que isso ajude, eu sei que eu tive que lutar com isso por um dia antes de descobrir isso.

Outras dicas

Eu tenho uma maneira mais fácil. :)

Você pode realmente acessar o modelo interno da listwidget com myList->model() -. E de lá há muitos sinais disponíveis

Se você só se preocupam com drag & drop, conecte-se layoutChanged. Se você tem botões de movimento (que normalmente são implementados com remove + add) conectar a rowsInserted também.

Se você quer saber o que mudou, rowsMoved pode ser melhor do que layoutChanged.

Nem uma solução, mas algumas idéias:

Você provavelmente deve verificar o que é devolvido pelo método supportedDropActions. Pode ser que você precisa para substituir esse método, para incluir Qt :: MoveAction ou Qt :: CopyAction.

Você tem sinal QListView :: indexesMoved, mas eu não tenho certeza se será emitido se você estiver usando QListWidget. Ele worths verificação.

Eu sei que isso é velho, mas eu era capaz de obter o meu código de trabalho usando a resposta de Trey e queria compartilhar minha solução python. Isto é para um QListWidget dentro de um QDialog, não um que é sub-classificada.

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

A abordagem QListWidget.model() parecia a mais elegante das soluções propostas, mas não funcionou para mim em PyQt5. Não sei porquê, mas talvez algo mudou na transição para Qt5. A abordagem eventFilter fez trabalho, mas não há outra alternativa que vale a pena considerar: excesso de equitação do QDropEvent e verificar se event.source é auto. Veja o código abaixo que é um MVCE com todas as soluções propostas codificadas in para o check-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_())
scroll top