Как мне ответить на внутреннюю операцию перетаскивания, используя QListWidget?
Вопрос
У меня есть приложение Qt4 (использующее привязки PyQt), которое содержит QListWidget
, инициализированный так:
class MyList(QtGui.QListWidget):
def __init__(self):
QtGui.QListWidget.__init__(self)
self.setDragDropMode(self.InternalMove)
Я могу добавлять элементы, и это позволяет мне перетаскивать и изменять порядок списка. Но как мне получить уведомление, когда список переупорядочивается пользователем? Я попытался добавить метод dropMimeData (self, index, data, action)
в класс, но он никогда не вызывается.
Решение
Мне просто пришлось иметь дело с этим, и это задница, но вот что нужно сделать:
Вы должны установить eventFilter
в своем подклассе ListWidget
, а затем отслеживать событие ChildRemoved
. Это событие охватывает как перемещения, так и удаление, поэтому оно должно работать для переупорядочения элементов с помощью перетаскивания внутри списка.
Я пишу свой Qt на C ++, но вот версия 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
Если у вас есть другой класс, содержащий этот список, вы можете переместить туда метод фильтра событий. Надеюсь, это поможет, я знаю, что мне пришлось бороться с этим в течение дня, прежде чем понять это.
Другие советы
У меня есть более простой способ. :) Р>
На самом деле вы можете получить доступ к внутренней модели listwidget с помощью myList-> gt; model ()
- и оттуда доступно много сигналов.
Если вас интересует только перетаскивание и вставка, подключитесь к layoutChanged
.
Если у вас есть кнопки перемещения (которые обычно реализуются с помощью функции удаления + добавления), подключитесь и к rowInserted
.
Если вы хотите узнать, что было перемещено, rowMoved
может быть лучше, чем layoutChanged
.
Я обнаружил, что ответ Трея Стаута сработал, однако я, очевидно, получал события, когда порядок в списке фактически не изменился. Я обратился к ответу Чани , который работает как требуется, но без кода мне потребовалось немного усилий для реализации на python. р>
Я думал, что поделюсь фрагментом кода, чтобы помочь будущим посетителям.
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"
Это протестировано в PySide, но не вижу причин, по которым оно не будет работать в PyQt.
Не решение, а несколько идей.
Вероятно, вам следует проверить, что возвращается методом supportDropActions. Возможно, вам нужно перезаписать этот метод, чтобы включить Qt :: MoveAction или Qt :: CopyAction.
У вас есть сигнал QListView :: indexesMoved, но я не уверен, будет ли он излучаться, если вы используете QListWidget. Это стоит проверить.
Я знаю, что это старая версия, но я смог заставить свой код работать, используя ответ Трея, и хотел поделиться своим решением на python. Это для QListWidget
внутри QDialog
, а не для подкласса.
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
Подход QListWidget.model ()
казался наиболее элегантным из предложенных решений, но у меня не работал в PyQt5. Я не знаю почему, но, возможно, что-то изменилось в переходе на Qt5. Подход eventFilter
сработал, но есть и другая альтернатива, которую стоит рассмотреть: переопределение QDropEvent и проверка, является ли event.source self. Посмотрите код ниже, который является MVCE со всеми предложенными решениями, закодированными для проверки в 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_())