Как реагировать на внутреннюю операцию перетаскивания с помощью QListWidget?

у меня есть приложение Qt4 (используя привязки PyQt), которое содержит QListWidget инициализируется вот так:

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

Я могу добавлять элементы, и это позволяет мне перетаскивать, чтобы изменить порядок списка. Но как получить уведомление, когда список будет переупорядочен пользователем? Я попытался добавить dropMimeData(self, index, data, action) метод для класса, но он никогда не вызывается.

6 ответов


Я просто имел дело с этим и это боль в заднице, но вот что нужно делать:

вы должны установить 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->model() - и оттуда есть много сигналов.

если вы заботитесь только о drag & drop, подключитесь к layoutChanged. Если у вас есть кнопки перемещения (которые обычно реализуются с помощью remove+add), подключитесь к rowsInserted тоже.

если вы хотите знать, что переехала, rowsMoved может быть лучше, чем layoutChanged.


нашел ответ Трея Стаута работал, однако я, очевидно, получал события, когда порядок списка фактически не изменился. Я повернулся к Чани!--3--> который работает по мере необходимости, но без кода мне потребовалось немного работы для реализации в 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.


Не решение, но некоторые идеи:

вероятно, вы должны проверить, что возвращается методом supportedDropActions. Возможно, вам нужно перезаписать этот метод, чтобы включить 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. The eventFilter подход действительно работал, но есть еще одна альтернатива, которую стоит рассмотреть: превышение qdropevent и проверка события if.источник-это Я. См. код ниже, который является 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_())