4-5 years I needed a widget with the following properties
This subwidget should be used in a layout to provide some detail on how the other GUI elements in the layout work but only consume a minimum space to display its content.
I thought this was an easy one - but each time I return to the challenge I always end by giving up.
The main problem is that the layout breaks down when heightForWidth() is implemented and a QSizePolicy with setHeightForWidth(True) is used. It can shrink to infinitely small. Apparently this is Qt bug.
Another approach is to call updateGeometry() when a resizeEvent() occurs and call setFixedHeight(h) using a width dependent height. But this also gives rise to some weird layout behavior.
If anybody has any good suggestions on how to approach this, please let me know.
Below I include a snippet that reproduces the layout resizing behavior.
Best regards,
Mads
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_())
To set the height and width of the label widget, we should declare the Label widget with a variable. Instantiating the label widget with a variable allows the users to add/edit the properties of the Label widget.
In this tutorial, we’ll demonstrate some examples of controlling the width of the <label> tag by using the display property set to “block” in combination with the width property. Usa a <form> element. Place the <label> tag with the for attribute and the <input> tag with the id, name, and type attributes inside the <form> element.
It's the same width whether the css code exists or not. The label is an inline element, meaning it is only as big as it needs to be. Set the display property to either inline-block or block in order for the width property to take effect.
This function was introduced in Qt 5.0. Returns the preferred height for this widget, given the width w. If this widget has a layout, the default implementation returns the layout's preferred height. if there is no layout, the default implementation returns -1 indicating that the preferred height does not depend on the width.
I found this extremely problematic. I think the core of the problem is as follows:
parentwidget->setLayout(layout)
).QLayoutItem
-derived class (e.g. QWidgetItem
).hasHeightForWidth() == true
and providing int heightForWidth(int width)
-- have limited ability to describe their constraints to layouts, in that they can offer a minimumSizeHint()
, a sizeHint()
, and a heightForWidth(width)
. They can, if necessary, also call functions like setMinimumSize()
and setMaximumSize()
. But most Qt layouts, like QVBoxLayout
, QHBoxLayout
, and QGridLayout
, don't pay particular attention to the heightForWidth(width)
results when offering their own min/preferred/max size to their parent, because they do so via QLayout::minimumSize()
, QLayout::sizeHint()
, and QLayout::maximumSize()
-- none of which are called with information about the layout's target size (in a Catch-22-like situation), so they can't easily provide the width
value to their children.setGeometry(const QRect& layout_rect)
. Now the layout knows how big it is. It assigns space to its children with child->setGeometry()
.So you see two categories of solution, as you've outlined above, where size needs to be "properly" constrained to that required.
The first:
QLabel
-derived classes using word wrap, or images wanting to fix their aspect ratio, can provide sensible values for their minimum and maximum size, and a sensible sizeHint()
(being the size they'd like to be).QWidget::resizeEvent(QResizeEvent* event)
to find out their new width (e.g. from event->size()
); (2) calculate their preferred height via their own heightForWidth()
function; and (3) force their height via, for example, setFixedHeight(height)
followed by updateGeometry()
.resizeEvent
, and if the parent widget has a layout with hasHeightForWidth() == true
, doing something like setFixedHeight(layout->heightForWidth(width())); updateGeometry();
.The second:
parent->setFixedHeight()
.hasHeightForWidth()
and heightForWidth()
to have the new layouts (and their parent widgets, and any ancestor layouts using this mechanism) adjust their height.I've put C++ code at http://egret.psychol.cam.ac.uk/code/2017_01_16_qt_height_for_width/ for the following layouts:
BoxLayoutHfw
, VBoxLayoutHfw
, HBoxLayoutHfw
-- replacements for QBoxLayout
etc.GridLayoutHfw
-- replacements for QGridLayout
.FlowLayoutHfw
-- replacement for Qt's FlowLayout
(http://doc.qt.io/qt-5/qtwidgets-layouts-flowlayout-example.html).and the following widgets:
AspectRatioPixmapLabel
-- image maintaining its aspect ratio;LabelWordWrapWide
-- word-wrapping label that tries to use as much horizontal space before it word-wraps.VerticalScrollArea
-- as its name suggests, a vertical scroll area, but one that supports height-for-width cleanly.... plus some infrastructure code (#define
s etc.) that should make the layouts revert to their Qt equivalent's behaviour, and some support files (including gui_defines.h
and layouts.h
that make the choice of layout and base widget conditional on your preference in this regard).
One residual problem that I've not successfully addressed is that I think QLabel
's heightForWidth()
seems to return slightly wrong values (slightly overestimating its space requirements) with stylesheets under some circumstances. I suspect the problem is in QLabelPrivate::sizeForWidth
but I've just worked around it imperfectly by calculating some stylesheet borders; it's still not quite right, but overestimating (leading to whitespace) is better than underestimating (leading to clipping).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With