heightForWidth label - qt

4-5 years I needed a widget with the following properties
Display text incl. HTML
Text should be wrapped on several lines
When the widget is put into a layout, the height of the widget should be adjusted in such a way that the text exactly fits the widget geometry
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_())

I found this extremely problematic. I think the core of the problem is as follows:
Qt layouts are typically assigned to a widget that we can call the parent widget (with parentwidget->setLayout(layout)).
Layouts ask their child widgets (or child layouts) for their size preferences (minimum size, size hint [preferred size], and maximum size). This is done in a slightly complex way via a QLayoutItem-derived class (e.g. QWidgetItem).
Height-for-width (HFW) widgets -- those having 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.
So, the layout asks its children how big they want to be without much HFW thought, and thus sets its own minimum/preferred/maximum size (determining, potentially in conjunction with other constraints, the size of the parent widget).
After the layout has told its parent (and its parent, etc.) how much space it needs, Qt works out how big it thinks everything should be. The layout is called via its setGeometry(const QRect& layout_rect). Now the layout knows how big it is. It assigns space to its children with child->setGeometry().
But only at this point has the layout discovered its final width. So up to this point, it cannot offer a final width to its children, and thus HFW widgets can't know their final width until they're being laid out finally. By this time, the layout and its parent may already have been set to the wrong height (can be too big; can be too small).
An excellent description of the widget/layout interaction is at http://kdemonkey.blogspot.co.uk/2013/11/understanding-qwidget-layout-flow.html; beyond this, you're best off looking at the Qt source code itself.
So you see two categories of solution, as you've outlined above, where size needs to be "properly" constrained to that required.
The first:
HFW widgets, like 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).
Then, when they're laid out, they (1) intercept 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().
This tends to work reasonably, except that any parent widget that wants to match its size to such an HFW widget has to do the same thing, e.g. intercepting resizeEvent, and if the parent widget has a layout with hasHeightForWidth() == true, doing something like setFixedHeight(layout->heightForWidth(width())); updateGeometry();.
That leads to faff as you have to modify potentially arbitrary widgets in a long line of parenthood.
It can also lead to quite a lot of redrawing, which can be slow and cause visual flickering.
The second:
Rewrite the layouts.
The best approach I've found is to have layouts calculate their children's geometry assuming some sort of standard rectangle (Qt itself often starts with a 640x480 one when drafting a layout); offering height information based on this draft; then, when setGeometry() is called, if the resulting height (based on a potentially new width) doesn't match what the layout previously advertised, re-constraining by calling parent->setFixedHeight().
This allows you to use arbitrary widgets, and HFW widgets only need to support hasHeightForWidth() and heightForWidth() to have the new layouts (and their parent widgets, and any ancestor layouts using this mechanism) adjust their height.
It can lead to some redrawing, but often not too much, as it happens on a per-layout not per-widget basis.
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 (#defines 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).

Size hint will not dictate child widget's size except Fixed policy. If autoresize-like policies will surplus by parent.

Related

Does Qt's QLayout by default squezze/overlap all widgets on layout area?

If you implement for example a QHBoxLayout with a fixed width (determined by a parent layout) and add more widgets (with a given minimum size) than fit into the fixed layout area, the widgets are getting smaller than the minSize and at a certain point even overlap.
Is this (ignoring minimumSize, spacing) the default implementation?
If yes, how would you achieve to keep the minimumSize and "push" other widgets out of the layout area or only partially draw the widgets (clip to layout area)?
I came across what seems to be the c++ source code for the layout kernel. Layouts are calculated within the qLayoutengine. For GridLayouts, BoxLayouts that is done via internal qGeomCalc method. There it is stated: "It portions out available space to the chain's [chain: struct of layoutItems and its geometry] children". So minimumSize will get ignored in order to "pack" all items into the given space.

Grid Layout Widget Sizing Problems

I use a Grid Layout inside my app. The grid layout I set to some fixed sizes.
myBootGridLayout->setContentsMargins(3,0,0,0);
myBootGridLayout->setRowMinimumHeight(0,25);
myBootGridLayout->setRowMinimumHeight(1,25);
myBootGridLayout->setRowMinimumHeight(2,25);
wdgBootFeatues->setFixedHeight(80);
For the QPushButton I use a size rule:
btnSelBootImagePath->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
But as you can see on the image, the ComboBox and Buttons have the same size but the LineEdit field is smaller. What I do wrong? Is there a trick to bring them on the common same size (Height) like in the QT documentation?
In case your question is to understand how to make sure that elements will have the same height, we should consider the following:
Layout might not necessarily ensure that some elements will have same height you would like, since it also relies on the size (horizontal and vertical) policy of the element in layout itself. In case you want to have QLineEdit and QPushButton instances to have the same height, you should set minimum height for each of them. Probably, it would even make sense to make the height fixed (i.e. set both minimum and maximum height to be the same values) for such elements to fit your needs, since both of these elements by default have fixed vertical size policy. This is for the reason, since most apps treat buttons and one line text fields in the same way.
In most cases, combining QVBoxLayout, QHBoxLayout and then QGridLayout is not necessary at all, since QGridLayout is much more flexible, and combines QVBoxLayout and QHBoxLayout features in a single layout under the hood at the first place, this it will probably satisfy all your needs (i.e. to represent you elements in a grid manner). Also, construction of UI elements will be slightly faster if less elements will be used.
Qt documentation might have such an effect because of the following reason - elements were tested on a different device. Qt does not try to make identical style sheets for widgets' elements across all platforms, thus visual differences will be everywhere. On some operating systems, button height is smaller than text field height by default, and this is completely normal.
One approach to make sure that size will get bigger than by default is to change size policy (vertical in your case). Code snippet changing size policy is correct basically. However, size policy is different thing than fixed height across elements. However, if your button and line edit would be in the same row, and both would have minimum expanding vertical policy, probably these elements would have the same height in that row.
Thus, probably to make sure the height of your elements remains the same is to set some minimum (and maximum as well in case vertical size policy is fixed) height through code or Qt Creator. This would be the easiest and least painful from thinking perspective approach. I am not sure if I have answered the question (it looks like that you answered yourself in your own way), but I am sure that I have introduced some thoughts that might come in handy when understanding Qt layouts.

Resizing Layout equal to MainWindow

When I run my program it will display all content properly, and when I resizing the main window, the layout along with all associated widgets remain fixed, rather than resizing with the main window. I used to increase my all widget and listWidget respect to window computer resolution size but still this not one work properly.
I used this one code finding the system height and width.
QWidget widget;
widget.resize(widget.width(), widget.minimumHeight());
QRect rec = QApplication::desktop()->screenGeometry();
int h = rec.height();
int w = rec.width();
// Increasing the listwidget size
ui->listWidget->setFixedHeight(h);
ui->listWidget->setFixedWidth(w);
//increasing the button size
ui->pushButton->setFixedHeight(h0.2);
ui->pushButton->setFixedWidth(w0.2);
At this link you will find two screenshots that illustrate my problem.
Please resolve to solve my problem. Thanks very much in advance.
When defining the layout of your windows and forms in Qt Designer you have to define each element of your form in advance, in order to have a working layout.
This solution is based on the screenshots provided in the comments to the question. Follow these steps:
Add an empty widget to the central area of your form, if there is nothing there. It will be used as a placeholder for the controls you will add later, and of course you can replace it with whatever widget you want. But you need it there to define a proper layout.
In the property panel, set the horizontal QSizePolicy of this widget to MinimumExpanding.
Add an horizontal spacer to the left side of your progress bar.
Define a minimum/maximum width for the white widget on the left (I guess it's a text area). As an example set the maximum width to 200
pixels.
Make the same for the QTabWidget on the right.
Give a minimum height to the Groupbox on top.
Then give the grid layout to the MainWindow.
You should get something similar in the designer view (I use a dark theme, yours will have different colors of course):
If you complete all steps you should have a nicely resizing window.
For the future: remember to integrally define your layouts, also using placeholder widgets when needed, read carefully the documentation about the widgets size policies (there are several, you need to play with them to fully understand each one) and keep in mind that Qt uses a container based approach which is different, as an example from those used by the .Net framework that relies on the concept of anchors.
EDIT : to answer questions in the comments
You will need to add a layout to any widget that contains other widgets, e.g. adding controls to your groupbox will require to give it a grid, horizontal or vertical layout in order to scale nicely on resize. Again use spacers and size policies to make it look the way you want. If you need to add or remove controls, or change their positions, you may need to brake the layout, rearrange and then set it again.
You can also select groups of widgets and give them a layout e.g. vertical, than another group and set them horizontal and so on... then give a grid layout to the container widget to build a compound layout.
There are endless possibilities, you just need to practice and go through trial and error as for everything else...
You can also do it all programmatically, check the Qt widgets documentation for this. But for complex layouts I would not go that way: it's a lot of code... and you have to compile and run to test every modification.
Using the QtCreator, within the designer you can simply right-click on the parent-widget and add a Grid-Layout.
This one resizes it's children to it's dimensions.

QSizePolicy true meaning, documentation and thoughts

I have been now two years working deeply with Qt layout system. After this time i encountered thosand of problems with SizePolicies and Layouts. I normally found out solutions, but never really understood what i was doing.
Now i was taking some time to try to understand correctly and build a 101 GUIDE for them and never fail. I found out this piece of information in the documentation about QSizePolicy of a QWidget:
This property holds the default layout behavior of the widget
If there is a QLayout that manages this widget's children, the size
policy specified by that layout is used. If there is no such QLayout,
the result of this function is used.
I thought that if you had a QLabel, for example, and you set the policies to Horizontal Expanding, Vertical Fixed, THE LABEL itself changed that way.
But it doesn't, at all.
When reading that i see that it talks about ITS CHILDREN inside the LAYOUT. So what does it mean, then?. Nothing is inside the label, is that why it does not work?.
WHat about inserting a label inside a QFrame, and telling the frame to be Expanding... Will the QFrame expand or stretch (depending on the rest of brother widgets in the same Layout) or will the label expand or stretch, not the QFrame?
What a mess...
What about Stretching?. If you set stretching 10 when you add the widget:
layout->addwidget(label, 10, Qt::AlignHCenter);
It doesn't work either.
Stretch 0 when you add the widget means : Take the policies i told you. Default, depends on the type of widget. Button-like widgets have expanding-fixed. Box-like Expanding-Expanding...
Stretch 10 means: grow maximum.
Am i right? Well. When having a QFrame and a QLabel inside, setting Expanding, and 10 to strech to the label DOES NOT WORK.
I don't understand all of your questions, so I will only answer to those that I think i do.
I thought that if you had a QLabel, for example, and you set the policies to Horizontal Expanding, Vertical Fixed, THE LABEL itself
changed that way. But it doesn't, at all.
I don't know what that means. What is the exact behavior you're expecting and what is actually happening? Is this label in a layout? Are there any other widgets in this layout?
WHat about inserting a label inside a QFrame, and telling the frame to be Expanding...
If you set a QSizePolicy to your QFrame object and set a layout to it, this size policy might be ignored. Docs: If there is a QLayout that manages this widget's children, the size policy specified by that layout is used. If there is no such QLayout, the result of this function is used. This means that the widget's layout should manage the size of the widget. Note that it will still respect the minimum/maximum width/height values.
Stretch 10 means: grow maximum.
What makes you think that?. The stretch factor is dependent on other widgets inside the layout. Docs: Stretch factors are used to change how much space widgets are given in proportion to one another.
When having a QFrame and a QLabel inside, setting Expanding, and 10 to strech to the label DOES NOT WORK.
Does not work how? If you set stretch for QFrame to 1 and for QLabel to 10, your QLabel object should always be 10 times as wide/high(depending of your layout type) as your QFrame object. If your QFrame has a layout and it contains children, then this might not work as it would depend on the childrens size policies.

Qt Layout, resize to minimum after widget size changes

Basically I've got a QGridLayout with a few widgets in it. The important ones are 2 labels, which I use for drawing images to the screen. Well, if the user wants, he can change the resolution of the incoming images, thus, forcing the Labels to resize.
Let's assume the initial size of the label is 320x240. The user changes the VideoMode to 640x480, the label and the entire GUI resizes perfectly. But when the user switches back to 320x240, the label shrinks, but the Layout/Window does NOT.
I've played around with sizePolicies and sizeHints, and resize(0,0), but nothing did the trick. Could somebody help me with this?
Here some screenshots to clarify the problem:
You need to set the size constraint of the layout holding all your widgets to "SetFixedSize". Although the name doesn't sound like it will work, it ensures that your layout will only use the space it needs. You will not have the problem like you do in your second screenshot.
Example:
mainLayout.setSizeConstraint(QLayout::SetFixedSize);
QLayout::setSizeConstraint(QLayout::SetFixedSize) solves this problem well when you prefer keeping your widget's size fixed at all times--that is, if you'd like it to always be fixed to its "packed" size (which may still vary as the child widgets change size). That is what the "fixed" means there: "fixed" to the correct size, even as the latter varies. (In Qt terms, what I'm calling the "packed" size is simply the widget's sizeHint.)
But a constraint may be too strong a solution in some instances. In particular, if you apply it to a top-level window, then the user will not be free to resize the window. If you don't like that, you can instead perform the "set size to sizeHint" operation instantaneously each time it's needed, rather than imposing it as an unrelenting constraint. The way to do that is to call QWidget::adjustSize().
http://doc.qt.io/qt-5/qwidget.html#adjustSize
Note that if the container whose children are changing size is not the top-level window, then adjustSize() may have to be called recursively on the container and its parents. (In my case I had to do that, anyway. I also tried the size-constraint scheme, and found that applying the constraint at only the topmost level was successful in compacting all levels. I haven't enough knowledge of Qt to comment usefully on these observations, so I merely share them.)
You need to store the original size of your widget parent window before applying any changes to the layout and restore it when the user switches back to the original.
Notice that you need to work with the widget parent window size and not the widget parent size.
in your widget before applying the layout changes:
minimumWindowSize = this->window().size();
when you finished reorganizing the widget to the compact size
this->window().resize(minimumWindowSize);
So that is exactly what i'm doing in mu project.
Resolution os doesn't matter. I have only to have a widget for rendering video, or image in your case.
void MainWindow::resizeEvent(QResizeEvent* event)
{
QMainWindow::resizeEvent(event);
if ((player != 0) && ((player->isPlaying()) || player->isLoaded() || player>isLoaded())){
renderer->resize(ui->mainVideoWidget->width(),ui->mainVideoWidget->height());
resizeFilter();
}
}

Resources