Como faço para responder a uma operação de arrastar-e-soltar interna usando um QListWidget?
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.
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
.
Eu encontrei a resposta de Trey Stout fez trabalho no entanto eu estava, obviamente, recebendo eventos quando a ordem da lista não tinha realmente mudado. Virei-me para resposta de Chani, que funciona como necessário, mas sem código levou-me um pouco de trabalho para implementar em python.
Eu pensei que eu iria partilhar o trecho de código para ajudar os 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"
Esta é testado em PySide mas não vejo nenhuma razão que não iria funcionar em PyQt.
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_())