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

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

  •  11-07-2019
  •  | 
  •  

Вопрос

У меня есть приложение 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_())
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top