6

4-5 年我需要一个具有以下属性的小部件

  • 显示文本包括。HTML
  • 文本应分多行
  • 将小部件放入布局时,应调整小部件的高度以使文本完全适合小部件几何形状

此子小部件应在布局中使用,以提供有关布局中其他 GUI 元素如何工作的一些详细信息,但仅占用最小空间来显示其内容。

我认为这很容易——但每次我回到挑战中,我总是以放弃告终。

主要问题是在实现 heightForWidth() 并使用带有 setHeightForWidth(True) 的 QSizePolicy 时布局会崩溃。它可以缩小到无限小。显然这是 Qt 错误。

另一种方法是在发生 resizeEvent() 时调用 updateGeometry() 并使用与宽度相关的高度调用 setFixedHeight(h)。但这也引起了一些奇怪的布局行为。

如果有人对如何解决这个问题有任何好的建议,请告诉我。

下面我包含一个片段,它重现了布局调整大小的行为。

此致,

麦兹

import sys
from PyQt4 import QtCore, QtGui

class Square(QtGui.QLabel):
    def __init__(self, parent=None):
        QtGui.QLabel.__init__(self, parent)
        self.setAutoFillBackground(True)

        palette = QtGui.QPalette()
        palette.setColor(QtGui.QPalette.Window, QtGui.QColor('red'))
        self.setPalette(palette)

        policy = self.sizePolicy()
        policy.setHeightForWidth(True)
        self.setSizePolicy(policy)

    def sizeHint(self):
        return QtCore.QSize(128, 128)

    def heightForWidth(self, width):
        return width

class Widget(QtGui.QWidget):
    def __init__(self, parent=None):
        # Call base class constructor
        QtGui.QWidget.__init__(self, parent)

        # Add a layout
        layout = QtGui.QVBoxLayout()
        self.setLayout(layout)

        # Add Square
        label = Square()
        layout.addWidget(label)

        spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        layout.addItem(spacerItem)

        # Some dummy button
        self._push_button = QtGui.QPushButton('Press me')
        layout.addWidget(self._push_button)

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

    widget = Widget()
    widget.show()

    sys.exit(app.exec_())
4

2 回答 2

5

我发现这非常有问题。我认为问题的核心如下:

  • Qt 布局通常分配给一个小部件,我们可以称之为父小部件(带有parentwidget->setLayout(layout))。
  • 布局向其子小部件(或子布局)询问其尺寸偏好(最小尺寸、尺寸提示 [首选尺寸] 和最大尺寸)。这是通过QLayoutItem派生类(例如QWidgetItem)以稍微复杂的方式完成的。
  • Height-for-width (HFW) 小部件——那些拥有hasHeightForWidth() == true和提供的小部件——int heightForWidth(int width)描述它们对布局的约束的能力有限,因为它们可以提供 a minimumSizeHint()、 asizeHint()和 a heightForWidth(width)。如有必要,它们还可以调用 和 之类setMinimumSize()的函数setMaximumSize()。但是大多数 Qt 布局,例如QVBoxLayout,QHBoxLayout和,在向其父级提供自己的最小/首选/最大尺寸时QGridLayout并没有特别注意结果,因为它们是通过,和-- 没有一个被调用的信息关于布局的目标大小(在类似 Catch-22 的情况下),所以他们不能轻易地为他们的孩子提供价值。heightForWidth(width)QLayout::minimumSize()QLayout::sizeHint()QLayout::maximumSize()width
  • 因此,布局在没有太多 HFW 想法的情况下询问其子级他们想要多大,从而设置其自己的最小/首选/最大尺寸(可能与其他约束一起确定父小部件的尺寸)。
  • 在布局告诉它的父级(及其父级等)它需要多少空间之后,Qt 计算出它认为一切都应该有多大。布局通过其setGeometry(const QRect& layout_rect). 现在布局知道它有多大。它用 为其子代分配空间child->setGeometry()
  • 只有在这一点上,布局才发现了它的最终宽度。所以到目前为止,它无法为其子级提供最终宽度,因此 HFW 小部件在最终布局之前无法知道它们的最终宽度。此时,布局及其父级可能已经设置为错误的高度(可能太大;可能太小)。
  • 小部件/布局交互的出色描述位于http://kdemonkey.blogspot.co.uk/2013/11/understanding-qwidget-layout-flow.html;除此之外,您最好查看 Qt 源代码本身。

因此,您会看到两类解决方案,正如您在上面所概述的,其中大小需要“适当地”限制在所需的范围内。

首先:

  • HFW 小部件,如QLabel使用自动换行的派生类,或想要固定其纵横比的图像,可以为其最小和最大尺寸提供合理的值,以及合理的sizeHint()(即他们想要的尺寸
  • 然后,当它们被布置时,它们 (1) 拦截QWidget::resizeEvent(QResizeEvent* event)以找出它们的新宽度(例如从event->size());(2)通过自己的heightForWidth()函数计算他们的首选高度;和 (3) 强制它们的高度,例如,setFixedHeight(height)后跟updateGeometry().
  • 这往往是合理的,除了任何想要将其大小匹配到这样的 HFW 小部件的父小部件必须做同样的事情,例如拦截resizeEvent,并且如果父小部件的布局具有hasHeightForWidth() == true,则执行类似的操作setFixedHeight(layout->heightForWidth(width())); updateGeometry();
  • 这会导致 faff,因为您必须在很长的父母身份中修改潜在的任意小部件。
  • 它还可能导致大量重绘,这可能会很慢并导致视觉闪烁。

第二:

  • 重写布局。
  • 我发现最好的方法是让布局计算他们孩子的几何形状,假设某种标准矩形(Qt 本身在起草布局时通常以 640x480 开头);根据该草案提供高度信息;然后,当调用 setGeometry() 时,如果结果高度(基于可能的新宽度)与之前宣传的布局不匹配,则通过调用parent->setFixedHeight().
  • 这允许您使用任意小部件,并且 HFW 小部件只需要支持hasHeightForWidth()heightForWidth()让新布局(及其父小部件,以及使用此机制的任何祖先布局)调整其高度。
  • 它可能会导致一些重绘,但通常不会太多,因为它发生在每个布局而不是每个小部件的基础上。

我已将 C++ 代码放在http://egret.psychol.cam.ac.uk/code/2017_01_16_qt_height_for_width/用于以下布局:

和以下小部件:

  • AspectRatioPixmapLabel-- 图像保持其纵横比;
  • LabelWordWrapWide-- 自动换行标签,尝试在自动换行之前使用尽可能多的水平空间。
  • VerticalScrollArea-- 顾名思义,一个垂直滚动区域,但完全支持宽度换高。

...加上一些基础设施代码(#defines 等),应该使布局恢复到其 Qt 等效行为,以及一些支持文件(包括gui_defines.h并使layouts.h布局和基础小部件的选择取决于您在这方面的偏好)。

我没有成功解决的一个遗留问题是,我认为在某些情况下,样式表QLabel的 'sheightForWidth()似乎返回了稍微错误的值(稍微高估了它的空间需求)。我怀疑问题出在哪里,QLabelPrivate::sizeForWidth但我只是通过计算一些样式表边界来不完美地解决它;它仍然不太正确,但高估(导致空白)比低估(导致剪辑)好。

于 2017-01-16T12:06:49.407 回答
0

除固定策略外,大小提示不会指示子小部件的大小。如果类似自动调整大小的策略将被父级过剩。

于 2013-05-14T12:01:25.003 回答