QListWidgetItem 内的小部件在内部移动后消失

err*_*r69 3 drag-and-drop qlistwidget python-3.x qt5 pyqt5

我有一个由via和拖放模式QListWidget填充的,当我在列表中移动项目时,其标签会消失。QLabel.setItemWidget()InternalMove

我该如何解决这个问题?

在此输入图像描述

一个最小的重现示例

from PyQt5.QtWidgets import (
    QApplication, QLabel, QStyle,
    QListWidget, QListWidgetItem
)
from PyQt5.QtCore import QSize
import sys


if __name__ == '__main__':
    app = QApplication(sys.argv)

    list = QListWidget()
    list.setFixedHeight(400)
    list.setDragDropMode(QListWidget.DragDropMode.InternalMove)

    for _ in range(8):
        item = QListWidgetItem()
        item.setSizeHint(QSize(40, 40))
        list.addItem(item)

        label = QLabel()
        label.setPixmap(list.style().standardIcon(
            QStyle.StandardPixmap.SP_ArrowUp).pixmap(QSize(40,40)))
        list.setItemWidget(item, label)

    list.show()

    sys.exit(app.exec())
Run Code Online (Sandbox Code Playgroud)

编辑

阅读其说明文档后.setItemWidget()

此函数只能用于在列表小部件项目的位置显示静态内容。如果您想显示自定义动态内容或实现自定义编辑器小部件,请改用 QListView 和 QStyledItemDelegate 子类。

我想知道这是否与该问题有关,在这种情况下“静态内容”意味着什么,被QLabel视为“动态内容”?

编辑#2

问题是在 a 内部dropEvent()dropMimeData()调用,这又创建了一个完整的新项目?(rowsInserted被调用),我猜这对于 self 项目不应该发生,因为拖动项目中设置的小部件没有序列化并存储在内部mimedata,因此小部件是解耦的,dropMimeData()通常在您拖放项目时调用一个不同的列表。

所以我想解决这个问题的一个丑陋的方法是将手动序列化的小部件存储在 aQListWidget.mimeData()作为自定义mimetypevia中QMimeData.setData(),并在放入后重新创建小部件QListWidget.dropMimeData()

例如:

from PyQt5.QtWidgets import (
    QApplication, QLabel, QStyle,
    QListWidget, QListWidgetItem
)
from PyQt5.QtCore import QSize, QMimeData, QBuffer, QIODevice
from PyQt5.QtGui import QPixmap
import pickle
import sys

class ListWidget(QListWidget):
    def mimeData(self, items:list[QListWidgetItem]) -> QMimeData:
        mimedata = QListWidget.mimeData(self, items)
        #   e.g. serialize pixmap
        custommime = []
        for item in items:
            label:QLabel = self.itemWidget(item)
            buff = QBuffer()
            buff.open(QIODevice.OpenModeFlag.WriteOnly)
            label.pixmap().save(buff, 'PNG')
            buff.close()
            custommime.append(buff.data())
        mimedata.setData('application/custommime', pickle.dumps(custommime))
        #
        return mimedata 


    def dropMimeData(self, index:int, mimedata:QMimeData, action) -> bool:
        result = QListWidget.dropMimeData(self, index, mimedata, action)
        #   e.g. recreate pixmap
        if mimedata.hasFormat('application/custommime'):
            for i, data in enumerate(
                    pickle.loads(mimedata.data('application/custommime')), 
                    start=index):
                pixmap = QPixmap()
                pixmap.loadFromData(data, 'PNG')
                label = QLabel()
                label.setPixmap(pixmap)
                self.setItemWidget(self.item(i), label)
        #
        return result


if __name__ == '__main__':
    app = QApplication(sys.argv)
    list = ListWidget()
    list.setFixedHeight(400)
    list.setDragDropMode(QListWidget.DragDropMode.InternalMove)
    list.setSelectionMode(QListWidget.SelectionMode.ExtendedSelection)

    for i in range(8):
        item = QListWidgetItem()
        item.setSizeHint(QSize(40, 40))
        list.addItem(item)
        label = QLabel()
        label.setPixmap(list.style().standardIcon(
            QStyle.StandardPixmap.SP_DialogOkButton + i).pixmap(QSize(40,40)))
        list.setItemWidget(item, label)

    list.show()
    sys.exit(app.exec())
Run Code Online (Sandbox Code Playgroud)

ekh*_*oro 5

更新

该错误现已在最新版本的 Qt5 和 Qt6 中修复。


这是由 Qt 错误引起的,该错误仅影响相当新的版本。当使用 Qt-5.15.6 和 Qt-6.4.0 时,我可以一致地重现它 - 但不能例如 Qt-5.12.1。该问题似乎与QTBUG-100128密切相关。

PyQt5/6 的解决方法(基于PaddleStroke 的解决方案)如下:

class ListWidget(QListWidget):
    def dragMoveEvent(self, event):
        if ((target := self.row(self.itemAt(event.pos()))) ==
            (current := self.currentRow()) + 1 or
            (current == self.count() - 1 and target == -1)):
            event.ignore()
        else:
            super().dragMoveEvent(event)
Run Code Online (Sandbox Code Playgroud)

旧答案

不幸的是,经过今天的一些进一步实验,下面给出的建议解决方法似乎并不是一个有效的解决方案。我发现还可以通过拖放到非空区域来使项目小部件消失。

在测试了 Qt5 的其他一些版本后,我可以确认该错误在 5.12.x、5.13.x、5.14.x、5.15.0 和 5.15.1 中完全不存在。这与上面现有的 Qt 错误报告一致,该报告将 Qt-5.15.2 确定为引入该错误的版本。

与问题中建议的相反,没有任何理由不应该将标签用作项目小部件。术语“静态内容”仅意味着“不由用户定义的自定义绘图更新”。

这个错误似乎是QTBUG-87057的回归,QTBUG-87057 对拖放过程中列表视图行的移动方式进行了大量内部更改。这些变化的复杂性可能意味着消除其负面影响的简单解决方法是不可能的。这些更改会影响所有高于 5.15.1 的 Qt5 版本和高于 6.0 的 Qt6 版本。


AFAICS,这仅影响将视图中当前最后一项拖放到空白区域。其他项目和多项选择不受影响。这建议采用以下解决方法:
class ListWidget(QListWidget):
    def dropEvent(self, event):
        if (self.currentRow() < self.count() - 1 or
            self.itemAt(event.pos()) is not None):
            super().dropEvent(event)

list = ListWidget()
...
Run Code Online (Sandbox Code Playgroud)

或使用事件过滤器:

class Monitor(QObject):
    def eventFilter(self, source, event):
        if event.type() == QEvent.Drop:
            view = source.parent()
            if (view.currentRow() == view.count() - 1 and
                view.itemAt(event.pos()) is None):
                return True
        return super().eventFilter(source, event)

monitor = Monitor()
list = QListWidget()
list.viewport().installEventFilter(monitor)
...
Run Code Online (Sandbox Code Playgroud)