QListWidgetItem 对内容的调整

QListWidgetItem's adjust to contents

当用户调整对话框大小时,我怎样才能使列表项的比例适合他们。目前他们似乎没有这样做......他们保持与工具启动时相同的大小。我启用了自动换行。我该如何解决这个问题?

当我缩放宽度时,它看起来不错,如下所示...

但放大后看起来不对

import os
import sys
import re
import random

from PySide2 import QtGui, QtWidgets, QtCore

class ListWidgetItem(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(ListWidgetItem, self).__init__(parent)
        self.setStyleSheet('background: green;')

        self.uiTitle = QtWidgets.QLabel()
        self.uiTitle.setWordWrap(True)
        self.uiTitle.setText('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc porta ornare odio sit amet suscipit. Curabitur orci urna, commodo quis urna ut, consequat sodales urna.')

        self.subtitle = QtWidgets.QLabel()
        self.subtitle.setText('Title')

        undoicon = self.style().standardIcon(QtWidgets.QStyle.SP_BrowserReload)
        self.button = QtWidgets.QPushButton('Click Me')
        self.button.setIcon(undoicon)

        self.mainLayout = QtWidgets.QVBoxLayout()
        self.mainLayout.setAlignment(QtCore.Qt.AlignTop)
        self.mainLayout.setSpacing(0)
        self.mainLayout.addWidget(self.subtitle)
        self.mainLayout.addWidget(self.uiTitle)
        self.mainLayout.addWidget(self.button)
        self.setLayout(self.mainLayout)

class ListWidget(QtWidgets.QListWidget):
    def __init__(self, parent=None):
        super(ListWidget, self).__init__(parent)
        self.resize(400,400)
        self.setSpacing(1)
        self.setSelectionMode(QtWidgets.QListView.ExtendedSelection)
        self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
        self.verticalScrollBar().setSingleStep(10)
        self.setWordWrap(True)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.setSizeAdjustPolicy(QtWidgets.QListWidget.AdjustToContents)
        self.populateItems()

    def appendListWidget(self, widget):
        lwi = QtWidgets.QListWidgetItem()
        lwi.setSizeHint(widget.sizeHint())
        self.addItem(lwi)
        self.setItemWidget(lwi, widget)

    def populateItems(self):
        for x in range(10):
            widget = ListWidgetItem()
            self.appendListWidget(widget)

def main():
    app = QtWidgets.QApplication(sys.argv)
    ex = ListWidget()
    ex.show()
    app.exec_()


if __name__ == '__main__':
    pass
    main()

为列表项设置的大小提示是静态的,并且基于小部件的初始大小提示,因此它不会完全适应,尤其是当内容可以改变其纵横比时。它会在需要时增加其尺寸,但不会缩小。

问题来自文字换行,这是 Qt 布局的一个已知限制。来自布局管理的 Layout Issues 部分:

The use of rich text in a label widget can introduce some problems to the layout of its parent widget. Problems occur due to the way rich text is handled by Qt's layout managers when the label is word wrapped.

一个可能的解决方案是覆盖列表小部件的 resizeEvent,并根据内容进行一些计算,同时考虑 heightForWidth().

这需要两个 for 循环(它可以只用一个循环稍微优化,但改进很小,因为您不太可能在当前视口中看到这么多小部件)。
第一个循环是在不显示滚动条的情况下检查可见项目是否适合当前视图,第二个是根据可用宽度实际设置小部件的新尺寸提示。

很明显,如果使用ScrollBarAlwaysOn策略,只需要做单循环,考虑滚动条hint width from start (do 不要使用实际的滚动条大小,因为当视图还没有显示时它会return一个不同的值。

class ListWidget(QtWidgets.QListWidget):
    # ...
    def resizeEvent(self, event):
        super().resizeEvent(event)
        width = self.viewport().width() - self.frameWidth() * 2
        if width > self.minimumSizeHint().width():
            # ensure that we have enough horizontal space to display the 
            # contents by avoiding recursion of the resize event
            maxHeight = self.viewport().height()
            height = 0
            spacing = self.spacing()
            for i in range(self.count()):
                item = self.item(i)
                widget = self.itemWidget(item)
                if not widget:
                    continue
                height += widget.heightForWidth(width)
                if height > maxHeight:
                    # we can't fit all items in the current visible rect
                    width -= self.verticalScrollBar().sizeHint().width()
                    break
                height += spacing
        else:
            width = self.minimumSizeHint().width()
        for i in range(self.count()):
            item = self.item(i)
            widget = self.itemWidget(item)
            if widget:
                item.setSizeHint(
                    QtCore.QSize(width, widget.heightForWidth(width))
                )