内部移动后QlistWidgetItem内部的小部件消失

发布于 2025-02-05 04:21:24 字数 3874 浏览 1 评论 0原文

我有一个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())

要复制 edit

在阅读.setItemWidget()的文档后,

此功能仅应用于显示列表小部件项目的静态内容。如果要显示自定义动态内容或实现自定义编辑器窗口小部件,请改用QListView和子类QStyleDitemdelegate。

我想知道这是否与问题有关?在这种情况下,“静态内容”的意思是Qlabel被考虑到“动态内容”?

编辑#2

问题在dropevent() a dropmimedata()中被调用,而这又创建了一个完整的新项目? (rowsinserted被调用),我猜是不应该发生的自我项目,因为拖动项目中设置的窗口小部件未序列化并存储在mimedata中小部件是分离的,当您从其他列表中拖动项目时,通常调用dropmimedata()

因此,我想解决此问题的一种丑陋方法是将手动序列化的小部件存储在qlistwidget.mimedata()作为自定义mimeType通过qmimimedata.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())

I have a QListWidget which is populated by QLabel via .setItemWidget() and a drag and drop mode InternalMove, when I move an item inside the list its label disappears.

How can I solve this issue?

enter image description here

A minimal example to reproduce

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())

edit

After reading the documentation for the .setItemWidget() which states:

This function should only be used to display static content in the place of a list widget item. If you want to display custom dynamic content or implement a custom editor widget, use QListView and subclass QStyledItemDelegate instead.

I wonder if this is related to the issue and what does "static content" mean in this context, is QLabel considered "dynamic content"?

edit #2

The problem is inside a dropEvent() a dropMimeData() is called which in turn creates a complete new item? (rowsInserted is called), which isn't supposed to happen for self items I guess, because a widget set in the dragged item isn't serialized and stored inside mimedata so the widget is decoupled, The dropMimeData() is usually called when you drag and drop items from a different list.

So I guess an ugly way to solve this is to store a manually serialized widget inside a QListWidget.mimeData() as a custom mimetype via QMimeData.setData() and recreate the widget after a drop inside QListWidget.dropMimeData().

for example:

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())

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

只有影子陪我不离不弃 2025-02-12 04:21:24

更新

该错误现已在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)

/strong>:

不幸的是,经过今天进一步的实验,下面建议的工作似乎不是一个有效的解决方案。我发现,也有可能通过拖放到非空地区而消失。

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

与问题所建议的相反,没有理由为什么不应将标签用作项目网络。术语“静态内容”,只是指“未通过用户定义的自定义图形更新”。

此错误似乎是从 qtbug-87057 的回归。内部更改列表视图行在拖放过程中移动的方式。这些变化的复杂性可能意味着一个简单的工作,无法消除其负面副作用。这些变化会影响大于5.15.1和QT6版本大于6.0的所有QT5版本。



AFAICS, this only affects dragging and dropping the current last item in the view onto a blank area. Other items and multiple selections aren't affected. This suggests the following work-around:

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()
...

或使用事件过滤器:

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)
...

UPDATE

The bug has now been fixed in the latest versions of Qt5 and Qt6.


This is caused by a Qt bug which only affects fairly recent versions. I can consistently reproduce it when using Qt-5.15.6 and Qt-6.4.0 - but not e.g. Qt-5.12.1. The issue seems to be closely related to QTBUG-100128.

A work-around for PyQt5/6 (based on the solution by PaddleStroke) is as follows:

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)

OLD ANSWER:

Unfortunately, after some further experimentation today, it seems the suggested work-around given below isn't an effective solution. I have found it's also possible to make item-widgets disappear by drag and drop onto non-empty areas.

After testing some other versions of Qt5, I can confirm that the bug is completely absent in 5.12.x, 5.13.x, 5.14.x, 5.15.0 and 5.15.1. This agrees with the existing Qt bug report above which identified Qt-5.15.2 as the version where the bug was introduced.

Contrary to what is suggested in the question, there's no reason whatsoever why a label should not be used as an item-widget. The term "static content", just means "not updated by user-defined custom drawing".

This bug seems to be a regression from QTBUG-87057, which made quite a large number of internal changes to how list-view rows are moved during drag and drop. The complexity of those changes may mean a simple work-around that undoes its negative side-effects isn't possible. The changes affect all Qt5 versions greater than 5.15.1 and Qt6 versions greater than 6.0.



AFAICS, this only affects dragging and dropping the current last item in the view onto a blank area. Other items and multiple selections aren't affected. This suggests the following work-around:

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()
...

or using an event-filter:

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)
...

七秒鱼° 2025-02-12 04:21:24

在这里,防止错误发生的方法:

class QListWidgetDragBugFix : public QListWidget
{
    Q_OBJECT

public:
    QListWidgetDragBugFix(QWidget *parent);
    ~QListWidgetDragBugFix() override;

protected:
    void dragMoveEvent(QDragMoveEvent *e) override;
};


QListWidgetDragBugFix::QListWidgetDragBugFix(QWidget * parent)
  : QListWidget(parent)
{
}

QListWidgetDragBugFix::~QListWidgetDragBugFix()
{
}

/* Qt has a recent bug (2023, https://bugreports.qt.io/browse/QTBUG-100128) 
* where the items disappears in certain conditions when drag and dropping.
* Here we prevent the situation where this happens.
* 1 - If the item is dropped on the item below such that the item doesn't move (ie superior half of the below item)
* 2 - The item is the last one and user drop it on the empty space below.
* In both those cases the item widget was lost.
 */
void QListWidgetCustom::dragMoveEvent(QDragMoveEvent *e)
{
    if ((row(itemAt(e->pos())) == currentRow() + 1) 
        || (currentRow() == count() - 1 && row(itemAt(e->pos())) == -1)) {
        e->ignore();
    }
    else {
        QListWidget::dragMoveEvent(e);
    }
}

Here' s a way to prevent the bug from happening :

class QListWidgetDragBugFix : public QListWidget
{
    Q_OBJECT

public:
    QListWidgetDragBugFix(QWidget *parent);
    ~QListWidgetDragBugFix() override;

protected:
    void dragMoveEvent(QDragMoveEvent *e) override;
};


QListWidgetDragBugFix::QListWidgetDragBugFix(QWidget * parent)
  : QListWidget(parent)
{
}

QListWidgetDragBugFix::~QListWidgetDragBugFix()
{
}

/* Qt has a recent bug (2023, https://bugreports.qt.io/browse/QTBUG-100128) 
* where the items disappears in certain conditions when drag and dropping.
* Here we prevent the situation where this happens.
* 1 - If the item is dropped on the item below such that the item doesn't move (ie superior half of the below item)
* 2 - The item is the last one and user drop it on the empty space below.
* In both those cases the item widget was lost.
 */
void QListWidgetCustom::dragMoveEvent(QDragMoveEvent *e)
{
    if ((row(itemAt(e->pos())) == currentRow() + 1) 
        || (currentRow() == count() - 1 && row(itemAt(e->pos())) == -1)) {
        e->ignore();
    }
    else {
        QListWidget::dragMoveEvent(e);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文