QStyledItemDelegate 的选项没有更新 [英] QStyledItemDelegate's option is not updating

查看:67
本文介绍了QStyledItemDelegate 的选项没有更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在使用 PyQt5 时遇到了问题.我有一个带有 QStyledItemDelegate 类绘制其项目的列表.这是最小的可重现示例:

I ran into a problem while using PyQt5. I have a list with QStyledItemDelegate class painting its items. Here is the minimal reproducible example:

import sys
from PyQt5.QtCore import (
    QAbstractListModel,
    Qt,
    QSize,
    QRect,
    QRectF,
)
from PyQt5.QtGui import (
    QPainter,
    QFontMetrics,
    QFont,
    QTextDocument,
    QTextOption,
    QPen,
)
from PyQt5.QtWidgets import (
    QApplication,
    QListView,
    QMainWindow,
    QStyledItemDelegate,
)

window_width = 0

class MessageDelegate(QStyledItemDelegate):
    WINDOW_PADDING = 30
    font = QFont("Times", 14)

    def __init__(self, *args, **kwargs):
        super(MessageDelegate, self).__init__(*args, **kwargs)

    def paint(self, painter, option, index):
        msg = index.model().data(index, Qt.DisplayRole)
        print("paint " + str(index.row()) + " " + str(option.rect.top()))
        field = QRect(option.rect)
        doc = QTextDocument(msg)
        doc.setDocumentMargin(0)
        opt = QTextOption()
        opt.setWrapMode(opt.WrapAtWordBoundaryOrAnywhere)
        doc.setDefaultTextOption(opt)
        doc.setDefaultFont(self.font)
        doc.setTextWidth(field.size().width())
        field.setHeight(int(doc.size().height()))
        field.setWidth(int(doc.idealWidth()))
        painter.setPen(Qt.gray)
        painter.setFont(self.font)
        painter.translate(field.x(), field.y())
        textrectf = QRectF(field)
        textrectf.moveTo(0, 0)
        doc.drawContents(painter, textrectf)
        painter.translate(-field.x(), -field.y())

    def sizeHint(self, option, index):
        global window_width
        msg = index.model().data(index, Qt.DisplayRole)
        doc = QTextDocument(msg)
        doc.setDocumentMargin(0)
        opt = QTextOption()
        opt.setWrapMode(opt.WrapAtWordBoundaryOrAnywhere)
        doc.setDefaultTextOption(opt)
        doc.setDefaultFont(self.font)
        doc.setTextWidth(window_width - self.WINDOW_PADDING)
        print("sizeHint " + str(index.row()) + " " + str(int(doc.size().height())))
        return QSize(0, int(doc.size().height()))


class MessageModel(QAbstractListModel):
    def __init__(self, *args, **kwargs):
        super(MessageModel, self).__init__(*args, **kwargs)
        self.messages = []

    def data(self, index, role):
        if role == Qt.DisplayRole:
            return self.messages[index.row()]

    def rowCount(self, index):
        return len(self.messages)

    def add_message(self, text):
        if text:
            self.messages.append(text)
            self.layoutChanged.emit()


class Dialog(QMainWindow):

    def __init__(self):
        global window_width
        super(Dialog, self).__init__()
        self.setMinimumSize(int(QApplication.primaryScreen().size().width() * 0.1), int(QApplication.primaryScreen().size().height() * 0.2))
        self.resize(int(QApplication.primaryScreen().size().width() * 0.3), int(QApplication.primaryScreen().size().height() * 0.5))
        window_width = int(QApplication.primaryScreen().size().width() * 0.3)
        self.messages = QListView()
        self.messages.setItemDelegate(MessageDelegate())
        self.model = MessageModel()
        self.messages.setModel(self.model)
        self.model.add_message("qwerty qwerty qwerty qwerty qwerty qwerty qwerty qwerty qwerty qwerty qwerty qwerty qwerty qwerty")
        self.model.add_message("abcdef")
        self.setCentralWidget(self.messages)

    def resizeEvent(self, event):
        global window_width
        super(Dialog, self).resizeEvent(event)
        window_width = self.size().width()

app = QApplication(sys.argv)
window = Dialog()
window.show()
app.exec_()

如您所见,我在 sizeHint 中返回每个项目之前打印了它的高度.我还打印了在油漆中收到的 option.rect 的 Y 坐标.由于我只有两个项目,我希望 item1 的坐标等于 item0 的高度.起初它似乎奏效了:

As you can see I am printing the height of each item before returning it in sizeHint. I also print the Y coordinate of option.rect received in paint. As I have only two items, I expect the coordinate of the item1 to be equal to the height of item0. And at first it seems to be working out:

sizeHint 0 23
paint 0 0
sizeHint 1 23
paint 1 23

然而,当我缩小窗口时,sizeHint 中的高度开始增长(因为窄窗口无法容纳所有内容)但 option.rect<的 Y 坐标/code> 保持不变:

However, as I narrow down the window the height in sizeHint starts to grow (because the narrow window can't fit all the contents) but the Y coordinate of option.rect stays the same:

sizeHint 0 46
paint 0 0
sizeHint 1 23
paint 1 23

即使到了第三行,option.rect 的位置也没有更新:

Even when I get to the third line the position of option.rect is not updating:

sizeHint 0 69
paint 0 0
sizeHint 1 23
paint 1 23

由于 item1 与 item0 重叠而不是向下移动.

As a result of that item1 overlaps item0 instead of moving down.

有没有办法在先前项目之一的大小发生变化时立即更新 option.rect 位置?

Is there a way to update option.rect position as soon as the size of one of previous items changes?

推荐答案

当索引的大小提示发生变化时,您需要发出 MessageDelegate.sizeHintChanged 让项目的布局管理器知道它需要重新分配项目.在这种情况下,项目的高度仅在调整窗口大小时发生变化,因此您可以做的是在 Dialog.resizeEvent 中发出(自定义)信号并将其连接到 MessageDelegate.sizeHintChanged.对于这个 Dialog 需要根据这样的东西进行修改

When the size hint of an index changes, you need to emit MessageDelegate.sizeHintChanged to let the layout manager of the items know that it needs to redistribute the items. In this case the height of the items only changes when the window is resized, so what you could do is to emit a (custom) signal in Dialog.resizeEvent and connect it to MessageDelegate.sizeHintChanged. For this Dialog would need to be modified according to something like this

from PyQt5.QtCore import pyqtSignal, QModelIndex

class Dialog(QMainWindow):

    # custom signal for signalling when window is resized
    width_changed = pyqtSignal()

    def __init__(self):
        ... as before ...
        self.messages = QListView()
        delegate = MessageDelegate()
        self.messages.setItemDelegate(delegate)
        # Connect custom signal to delegate.sizeHintChanged. 
        # This signal expects a ModelIndex for which we take the root, i.e. QModelIndex()  
        self.width_changed.connect(lambda : delegate.sizeHintChanged.emit(QModelIndex()))
        .... rest as before ....

    def resizeEvent(self, event):
        global window_width
        super(Dialog, self).resizeEvent(event)
        window_width = self.size().width()
        # emit the custom signal
        self.width_changed.emit()

在上面的代码中,我没有修改代码中的任何其他内容,但是您可以选择通过修改自定义信号以发出新的宽度并创建一个来摆脱全局 window_width 变量插入 MessageDelegate 以将新宽度分配给实例变量.

In the code above I didn't modify anything else in the code, but you could opt to get rid of the global window_width variable by modifying the custom signal to emit the new width and create a slot in MessageDelegate to assign the new width to an instance variable.

这篇关于QStyledItemDelegate 的选项没有更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆