Qt Layout, resize to minimum after widget size changes - qt

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();
}
}

Related

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.

Is there a way to force updating the size of the non-active child of QStackedWidget when it's resized?

I have a QStackedWidget (QSW) with 2 child components, both are configured to expand to the size of their parents.
One of the child components (CC) resizes its content based on CC's size. These
calculated sizes are also used to determine the size of components which are always visible in a sibling of QSW. In order for these sizes to be always correct, CC needs to be resized even when it's not the active component in QSW.
What's the most straightforward way to accomplish this?
Edit: CC's sizePolicy is MinimumExpanding/MinimumExpanding, so the requirement is that CC's size is set to expand to the maximum size QSW allows it to, even when CC is not the active component.
This is a fine matter of whether or not the size hint works and what size policy is. In such cases the call of QWdiget::adjustSize is usually helping but to answer with confidence more context of your code is needed. Anyway, the docs say when it helps. I would trap the moment when the size is not correct and apply adjustSize. Mind that the scope where you apply adjustSize matters too.
Using a QStackedLayout with the StackAll stacking mode solves the issue. When the stacking mode is set to StackAll, all child widgets are showing, they just cover each other.

Displaying an image and automatically re-size it

I can't quite figure out what the best way of displaying an image is in my particular case, so hopefully someone on here has a few tips.
I want to display an image that gets re-sized automatically to fit inside the space that is available. I currently do this by creating a class derived from QLabel that implements void resizeEvent(QResizeEvent*) where I do a QPixmap::scaled to re-size the image. The problem is that this only works when the widget is enlarged because the widget doesn't get a resizeEvent when I try to make the widget smaller. I guess that because I set the image to the same size as the widget, it isn't allowed to be sized smaller again? I guess I could try to create a smaller image therefor introducing a sort of "border" around the image which would perhaps allow re-size events to occur when making the area smaller. Any thoughts?
resizeEvent is sent whenever size is changed. It doesn't matter whether it is enlarged or not.
But you can set Policy and Max/Min size to constraint widget in shrinking/enlarging. So if you have your widget not getting resizeEvent AND it doesn't shrink either, then look at your size policy and min width/height. If it shrinks but you doesn't have resizeEvent then you have some error in you logic, I believe.
Alternatively you can use paintEvent for image painting and use QWidget::rect() for your widget width/height.
Try changing the size policy of the label to QSizePolicy::Preferred.
Have a look at size policies in general.

heightForWidth label

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.

Resizing QT's QTextEdit to Match Text Height: maximumViewportSize()

I am trying to use a QTextEdit widget inside of a form containing several QT widgets. The form itself sits inside a QScrollArea that is the central widget for a window. My intent is that any necessary scrolling will take place in the main QScrollArea (rather than inside any widgets), and any widgets inside will automatically resize their height to hold their contents.
I have tried to implement the automatic resizing of height with a QTextEdit, but have run into an odd issue. I created a sub-class of QTextEdit and reimplemented sizeHint() like this:
QSize OperationEditor::sizeHint() const {
QSize sizehint = QTextBrowser::sizeHint();
sizehint.setHeight(this->fitted_height);
return sizehint;
}
this->fitted_height is kept up-to-date via this slot that is wired to the QTextEdit's "contentsChanged()" signal:
void OperationEditor::fitHeightToDocument() {
this->document()->setTextWidth(this->viewport()->width());
QSize document_size(this->document()->size().toSize());
this->fitted_height = document_size.height();
this->updateGeometry();
}
The size policy of the QTextEdit sub-class is:
this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
I took this approach after reading this post.
Here is my problem:
As the QTextEdit gradually resizes to fill the window, it stops getting larger and starts scrolling within the QTextEdit, no matter what height is returned from sizeHint(). If I initially have sizeHint() return some large constant number, then the QTextEdit is very big and is contained nicely within the outer QScrollArea, as one would expect. However, if sizeHint gradually adjusts the size of the QTextEdit rather than just making it really big to start, then it tops out when it fills the current window and starts scrolling instead of growing.
I have traced this problem to be that, no matter what my sizeHint() returns, it will never resize the QTextEdit larger than the value returned from maximumViewportSize(), which is inherited from QAbstractScrollArea. Note that this is not the same number as viewport()->maximumSize(). I am unable to figure out how to set that value.
Looking at QT's source code, maximumViewportSize() is returning "the size of the viewport as if the scroll bars had no valid scrolling range." This value is basically computed as the current size of the widget minus (2 * frameWidth + margins) plus any scrollbar widths/heights. This does not make a lot of sense to me, and it's not clear to me why that number would be used anywhere in a way that supercede's the sub-class's sizeHint() implementation. Also, it does seem odd that the single "frameWidth" integer is used in computing both the width and the height.
Can anyone please shed some light on this? I suspect that my poor understanding of QT's layout engine is to blame here.
Edit: after initially posting this, I had the idea to reimplement maximumViewportSize() to return the same thing as sizeHint(). Unfortunately, this did not work as I still have the same problem.
I have solved this issue. There were 2 things that I had to do to get it to work:
Walk up the widget hierarchy and make sure all the size policies made sense to ensure that if any child widget wanted to be big/small, then the parent widget would want to be the same thing.
This is the main source of the fix. It turns out that since the QTextEdit is inside a QFrame that is the main widget in a QScrollArea, the QScrollArea has a constraint that it will not resize the internal widget unless the "widgetResizable" property is true. The documentation for that is here: http://doc.qt.io/qt-4.8/qscrollarea.html#widgetResizable-prop. The documentation was not clear to me until I played around with this setting and got it to work. From the docs, it seems that this property only deals with times where the main scroll area wants to resize a widget (i.e. from parent to child). It actually means that if the main widget in the scroll area wants to ever resize (i.e. child to parent), then this setting has to be set to true.
So, the moral of the story is that the QTextEdit code was correct in overriding sizeHint, but the QScrollArea was ignoring the value returned from the main frame's sizeHint.
Yay! It Works!
You may try setting minimumSize property of the QTextEdit to see if that force the layout to grow.
I don't understand most of Qt's layout scheme but setting minimum and maximum size pretty much does what I want it to do. Well, most of the time anyways.

Resources