带有 setWordWrap(True) 的 QLabel 在放置在 QListWidget 中时不会垂直调整大小,即使 resizeMode 已设置为 Adjust

Posted

技术标签:

【中文标题】带有 setWordWrap(True) 的 QLabel 在放置在 QListWidget 中时不会垂直调整大小,即使 resizeMode 已设置为 Adjust【英文标题】:QLabels with setWordWrap(True) are not resizing vertically when placed in a QListWidget, even though resizeMode has been set to Adjust 【发布时间】:2021-04-07 21:50:13 【问题描述】:

如何将QLabels 和setWordWrap(True) 放在QListWidget/QListView 内,以便在调整父窗口小部件大小时正确调整QLabels 的大小?

当我尝试这样做时,我遇到了 QLabels 没有得到正确高度的问题(参见下面的示例 2)

我正在使用

Python:3.8.5 PyQt5:5.15.2

示例 1:使用 QVBoxLayout

为了展示我想要完成的事情,我想先展示一个示例,我只是将QLabels 添加到 QVBoxLayout

这如我所愿:如果我水平调整(主)窗口的大小,带有环绕文本的QLabel 将在垂直方向占用更多空间

import sys
from PyQt5.QtWidgets import *


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setMinimumHeight(150)
        self.main_widget = QWidget()
        self.setCentralWidget(self.main_widget)
        vbox = QVBoxLayout()
        self.main_widget.setLayout(vbox)

        label_1 = QLabel("label_1")
        vbox.addWidget(label_1)
        label_1.setStyleSheet("*background-color: #f0f000;")

        label_2 = QLabel("[wrapped] label_2 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
        vbox.addWidget(label_2)
        label_2.setStyleSheet("*background-color: #00f0f0;")
        label_2.setWordWrap(True)


app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec_()

示例 2:使用QListWidget

这是我遇到问题的代码:运行此代码不会为带有包装文本的 Qlabels 提供任何额外空间

(在示例中使用QListWidget 而不是QListView,但据我所见 - 以及我对这些类的理解 - 应该没有区别)

请注意resizeMode property 已设置为QListView.Adjust

import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.list_widget = QListWidget()
        self.setCentralWidget(self.list_widget)
        self.list_widget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.list_widget.setResizeMode(QListView.Adjust)  # <-----
        self.list_widget.setUniformItemSizes(False)  # -should already be false, but just in case
        self.list_widget.setWordWrap(True)  # -AFAIK this should only effect text that is put directly into QListWidgetItems, but just in case

        item_1_text_str = f"Item number 1"
        lwi_item_1 = QListWidgetItem(self.list_widget)
        self.list_widget.addItem(lwi_item_1)
        item_1_widget_qlabel = QLabel(item_1_text_str)
        item_1_widget_qlabel.setStyleSheet("*background-color: #f0f000;")
        self.list_widget.setItemWidget(lwi_item_1, item_1_widget_qlabel)

        item_2_text_str = "[wrapped] label_2 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
        lwi_item_2 = QListWidgetItem(self.list_widget)
        self.list_widget.addItem(lwi_item_2)
        item_widget_2_qlabel = QLabel(item_2_text_str)
        item_widget_2_qlabel.setWordWrap(True)  # <-------
        item_widget_2_qlabel.setStyleSheet("*background-color: #00f0f0;")
        self.list_widget.setItemWidget(lwi_item_2, item_widget_2_qlabel)


app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec_()

我试过的

将大小策略设置为Expanding

通常策略设置为MinimumExpanding,所以我认为Expanding 会更好,因为Expanding 包含Shrink 标志

在上面的代码示例 2 中,它看起来像这样:

    item_widget_2_qlabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

这并没有帮助

使用QLabel 中的sizeHint 设置QListWidgetItem 的大小提示

如果在__init__完成:

lwi_item_2.setSizeHint(item_widget_2_qlabel.sizeHint())

这样做的问题是它只会设置一次大小提示,然后sizeHint会卡住,所以带有包裹文本的QLabel不能垂直扩展或收缩

或者使用此代码,以便在调整主窗口大小时更新 sizeHint:

class MainWindow(QMainWindow):
    [...]

def resizeEvent(self, a0: QResizeEvent) -> None:
    super().resizeEvent(a0)
    row = 0
    while row < self.list_widget.count():
        lwi_item = self.list_widget.item(row)
        item_widget = self.list_widget.itemWidget(lwi_item)
        widget_size_hint = item_widget.sizeHint()
        lwi_item.setSizeHint(widget_size_hint)  # <--------
        row += 1

令人惊讶的是,使用此代码时的结果是相同的:QLabel 的垂直大小没有改变

【问题讨论】:

为什么需要使用 QListWidget QLabels? @musicamante 嗨,谢谢你的问题。我在我正在处理的几个项目中使用QListWidget/QListView,我想放QLabel的一个典型原因是因为我有一个自定义行/项目小部件(使用setItemWidget添加)持有两个或三个其他小部件,其中一个是 QLabel,带有我想要包装的文本 --- 我愿意使用其他方法,所以如果你知道好的替代方法,请分享! 好吧,QLabel 有点奇怪,因为它是唯一一个拥有自己处理尺寸方式的小部件,因为它能够根据可用空间调整其内容(并且,部分,反之亦然)。鉴于您所描述的,我可以理解这种方法的必要性,并且您的回答中提出的解决方案对我来说似乎可以接受:问题的 resizeEvent 实现不起作用的原因是标签返回的 sizeHint 是基于水平列表视图在映射后立即提供的可用空间。 @musicamante 好的,这是有道理的,感谢您抽出宝贵时间发表评论和解释。我已经使用 PyQt 几年了,但现在我开始真正学习它是第一次。让我感到困惑的一件事是setResizeMode(QListView.Adjust) 似乎没有做任何事情,那么这是否仅对其他类型的小部件有效? doc.qt.io/qt-5/qlistview.html#resizeMode-prop 【参考方案1】:

在写这个问题时,我能够在 QListWidget 子类的覆盖 resizeEvent 中使用 QLabel.heightForWidth 找到解决方案

这不是我所希望的(我曾希望我错过了一些简单的东西,以便解决方案更加优雅)。 如果有人有比这更好的解决方案,请分享!

这里是我用来让它工作的代码

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *


class ListWidget(QListWidget):
    def __init__(self):
        super().__init__()
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        """
        self.list_widget.setResizeMode(QListView.Adjust)
        -this is not needed with this solution. (Maybe because it only effects QListWidgetItems?)
        """

    def resizeEvent(self, a0: QResizeEvent) -> None:
        super().resizeEvent(a0)
        row = 0
        while row < self.count():
            lwi_item = self.item(row)
            item_widget = self.itemWidget(lwi_item)
            if isinstance(item_widget, QLabel) and hasattr(item_widget, "wordWrap") and item_widget.wordWrap():
                width_int = self.width() - self.contentsMargins().left() - self.contentsMargins().right()
                height_hfw_int = item_widget.heightForWidth(width_int)  # <--------
                widget_size_hint = QSize(width_int, height_hfw_int)
                lwi_item.setSizeHint(widget_size_hint)
            row += 1


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.list_widget = ListWidget()
        self.setCentralWidget(self.list_widget)

        lwi_item_1 = QListWidgetItem(self.list_widget)
        self.list_widget.addItem(lwi_item_1)
        item_1_widget_qlabel = QLabel("Item number 1")
        item_1_widget_qlabel.setStyleSheet("*background-color: #f0f000;")
        self.list_widget.setItemWidget(lwi_item_1, item_1_widget_qlabel)

        lwi_item_2 = QListWidgetItem(self.list_widget)
        self.list_widget.addItem(lwi_item_2)
        item_2_html_rich_str = "<span style='font-size:12pt'>Listen, strange women lyin in ponds distributin swords is no basis for a system of government.</span><br><span>Supreme executive power derives from a mandate from the masses, not from some farcical aquatic ceremony.</span>"
        item_2_markdown_str = "## Listen, strange women lyin in ponds distributin swords is no basis for a system of government.\n\nSupreme executive power derives from a mandate from the masses, not from some farcical aquatic ceremony."
        item_2_text_str = "Listen, strange women lyin in ponds distributin swords is no basis for a system of government.\n\nSupreme executive power derives from a mandate from the masses, not from some farcical aquatic ceremony."
        item_widget_2_qlabel = QLabel(item_2_html_rich_str)
        item_widget_2_qlabel.setTextFormat(Qt.RichText)  # Qt.RichText, Qt.MarkdownText, Qt.PlainText
        item_widget_2_qlabel.setWordWrap(True)  # <-------
        item_widget_2_qlabel.setStyleSheet("*background-color: #00f0f0;")
        self.list_widget.setItemWidget(lwi_item_2, item_widget_2_qlabel)


app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec_()

【讨论】:

以上是关于带有 setWordWrap(True) 的 QLabel 在放置在 QListWidget 中时不会垂直调整大小,即使 resizeMode 已设置为 Adjust的主要内容,如果未能解决你的问题,请参考以下文章

QCheckBox 自动换行

如何自动换行 QTreeWidgetItem

Qt-label显示的汉字自动换行

如何插入带有镶木地板格式和SNAPPY压缩的蜂巢表?

PYQT5(二十五) QLabel自适应

如何使标签文本轮廓化并适合标签的大小