How to update a QLayout and get the new dimensions before returning? - qt

This is driving me nuts. I have a custom menu class that, when set visible, shows a list of items located in a particular folder. When a hardware button is pressed, my application gets the latest list of items, populates the menu with them, and returns.
The menu displaying these items uses a QListWidget filled with custom widgets. Each of the widgets contains one or more QLabels in a horizontal layout, and is created at the time the menu is shown. In order to adjust the text displayed based on the menu width available, I need to get the size of the QLabel AFTER it has been resized according to the layout, but before the menu becomes visible to the user. The problem is, my layout does not get updated until all of the functions constructing my list return.
I have tried QApplication::ProcessEvents() and the layout update functions, but none of them have updated the values of my QLabels before returning. I can set a QTimer when the button is initially pressed, and have it show the menu, update the items, and stop itself, but that seems like a terrible solution.
Any help would really be appreciated! I've spent most of a day on this.
Marlon

I had this exact problem and could not find an answer anywhere on the Internet. Calling Layout.update(), Layout.activate(), or widget.adjustSize() (all suggested in various places) all did not work.
I had a widget with a vertical layout that I wanted to add a QLabel to and then immediately use the size of the QLabel.
The only thing that worked reliably was
layout->addWidget(myLabel);
myLabel->show();
size = myLabel->size();
It would seem that layouts will just not recalculate until you either return from a function and allow the Qt event loop to progress or manually call show() yourself.

How to update a QLayout and get the new dimensions before returning?
Don't. You're not meant to do that. It'll drive you "nuts" because you're doing it backwards. Layout updates are handled asynchronously from the event loop. Instead of getting layout dimensions right away, set yourself up to be part of the system. Some options are:
Implement a custom widget that will interact properly with the layout, growing to fill the available width of the layout. Perhaps all you need is a size policy and a way to elide text?
Make a custom layout that takes the special properties of your use case into account.

You want to call QWidget::adjustSize() on your parent widget. This will force the layout recalculations.

Have you tried using layout()->update(); ?

I've tried many but nothing works for me on Qt 5.15.
Only invented little patch - create timer and get size after 20 msec:
QTimer::singleShot(20, this, [this]
{
const auto height = myLayout->contentsRect().height();
// ...
});

Related

Check if QML Item is being drawn

I have a set of QML items distributed all over my UI. They display data from a remote device and their content needs to be updated regularly. The Items are spread on several tabs and hidden in nested ListView instances, so most of them won't be visible to the user all the time.
In order to keep the bandwidth low I want to update only those items that are currently visible to the user.
I am looking for the right hook to get the information which of these Items is currently displayed from within the Item, without relying on information from the parents. If they were all placed in ListView delegates I could use the delegate's Components onCompleted and onDestroyed signals. Since this is not the case I am stuck at finding out how to get this information.
Am I missing something here? Is there an onPaintFinished signal or something similar? My workaround would be to add that logic to the parent containers, but that would be tedious, since there are several kinds of container that can contains these display Items.
Instances that are on delegates of a ListView will not exist until they would be in the visible range or the cache range around the visual area of the list view. If the delegate moves outside of that range, it is destroyed. So, no need to worry about instances hidden there.
Furthermore, items are currently not visible are also not drawn. They are not entered into the scene graph, and hence, not rendered. So, instances of your items appearing on tabs that are currently not current will also not be drawn. However, these items do still exist of course.
Figuring out if an item is effectively visible or not is quite a hard problem though. QML delegates part of that to OpenGL (clipping for instance). There is not feedback on the result of that. You could in theory lift that information out of the renderer, but that would require customizing that and that is very hard. You could take a look at the heuristics that GammaRay uses to warn about items not being visible. Perhaps you can take some inspiration from that.

Qt ensureVisible() not working in QScrollArea

I have 2 column. One of the columns is filled with lots of QWidgets.
When I drop a widget in a column I call this method:
void MainWindow::scrollToItem(Product_View *item) {
QPoint point = item->mapToParent(QPoint());
ui->scrollArea->ensureVisible(point.x(), point.y());
}
When I am at the bottom of the first column and I drag a widget in the next one. The scrollarea scrolls properly to the bottom so that I can see where I dropped it.
But when I drop the widget back in the first column, it scrolls but not entirely to the bottom. Its of about 150px (EDIT: 150px is not true. I only see about 5px of the Widget)
Can anybody help me?
EDIT:
maybe good to know my layout.
I have a mainwindow with a QScrollArea.
The scrollArea has a QWidget called scrollAreaWidgetContents and it has a QHBoxLayout.
The widget has 5 columns. And these have QVBoxLayout for my drag and drop widgets.
JEEZ ANOTHER EDIT:
I notice it only goes wrong with the last item.
I JUST KEEP ON EDITING
It is now clear to me that the scrollbar just isn't going all the way down.
QScrollBar *bar = ui->scrollArea->verticalScrollBar();
bar->setValue(bar->maximum());
This code also shows the same behaviour. What should I do with this thread? And should I create a new one?
The ensureVisible function only takes a point, so using it will only guarantee that one corner of your widget is visible (the top left, I believe?). Try using ensureWidgetVisible instead - this should make sure the entire widget makes it on-screen.
Hope that helps!
Although this is an older post I encountered the same problem and it gave me a lot of trouble finding a solution.
My problem:
Had to add a new line to a widget and then make sure the scroll bar scrolls down to it in order for people to view it. OP describes the issue well in his answer.
The things I tried are:
1. (Best way) To call processEvents() on the app object. I tried it after I saw this post and ratzian's answer.
2. Implement your custom ScrollArea,that extends QScrollArea and override the resize handler to be able to call ensureWidgetVisible on the added widget. Of course it somehow needs to know about that widget object.
3. Add a QTimer and start the timer upon adding a new widget. The method that the timer calls will need to call ensureWidgetVisible() on the new widget object and afterwards stop the timer.
I am aware that 3 is a bit hacky since it doesnt know when the resize event took place and so the timer will need to be set to a suboptimal value. (e.g resize is called in 33 ms and you set timer to 500 ms, you get the idea).
I hope this manages to help people who struggle with the same problem.
I found my problem. Not the solution.
If I drag the widget back to the first column, my code calls ensureWidgetVisible.
After that, my scrollArea resizes because of the new item. So thats why my widget isn't entirely visible.

Qt hide QLayout (switch between two layouts)

I didn't find solution for my problem with two QLayouts. I need app with QHBoxLayout with possible expandind when I will add new widgets, push buttons, ....
So what I have: One QDialog and two layouts. Now I know that I can't hide the layout.
So I tray just :
layout()->removeItem(firstlayout);
layout()->addLayout(secondLayout);
But when I did this, I saw all items in first layout on possition [0,0].
So next step I try:
for (all items in first layout) if (widget) widget->hide();
But this is working only with QWidget and I have many different items in layouts.
Simply way is use the widget, because there is possible to use hide/show, but I need auto expanding window when I add new items.
Just rebuild the layout, there is no need to keep the two layouts in existence at the same time. Probably clearest is to have two (or more) methods, which first delete current layout, then create new layout, add widgets to it, hide all widgets you want hidden, and set it as current layout. Note that you don't even need to keep a member variable for the layout, since QWidget has that anyway and provides you with setter and getter.
Or, if you have different widgets in different layouts, and actually want to be able "switch pages" so to say, simply use QStackedWidget. Or if you have a fixed part (buttons etc), and then part with "pages", then put the "pages" into QStackedWidget, and keep fixed part out of it.
'addLayout(secondLayout/firstLayout) ' will remove the other layout automatically, you do not have to remove it. If you keep a pointer to the layout(which has addWidget() before), you can simply use the layout and widgets in it later. :)

QToolbar force shrink

Qt toolbars shrink and display an 'extend' button when there isn't enough space.
Is there anyway to create a toolbar pre-shrunk? I want to display just the first label so I can use a large number of toolbars as floatable popup-menus (or a linear dialogboxs ).
I specifically want to use toolbars (rather than dialogs) because they are already created for another app.
Internally a class called QToolbarLayout makes this decision about the "extension" button, based on the size of the space the toolbar is given:
http://qt.gitorious.org/qt/qt/blobs/4.7/src/gui/widgets/qtoolbarlayout.cpp#line398
So there's no method or slot for it, beyond setting its size. However, you could make your application persist the toolbar information between sessions via QSettings:
http://doc.qt.nokia.com/latest/qsettings.html#details
http://doc.qt.nokia.com/latest/qsettings.html#restoring-the-state-of-a-gui-application
That would allow your users who wished to work in this style the option of setting it up like that, and let those who didn't like it save their preferred layout as well.

Reducing flicker with QBoxLayout

Whenever a displayed QBoxLayout is populated, there's some flicker on the screen as widgets get added to the layout. How do I stop this flicker?
setUpdatesEnabled did not do the trick.
show() the widget only after you've finished populating it/laying it out.
Or don't attach your layout to it's widget before you're done adding things to it. (i.e. only call setLayout(your_layout) when you've finished adding things to your_layout).
Alternatively, look into the updatesEnabled QWidget property. You can use that to temporarily disable the widget's updates to prevent flicker. (This is most useful on the more complex widgets like QTableWidget and similar when you are making "massive" changes to the underlying data.)
Quote from the doc above:
setUpdatesEnabled() is normally used to disable updates for a short period of time, for instance to avoid screen flicker during large changes. In Qt, widgets normally do not generate screen flicker, but on X11 the server might erase regions on the screen when widgets get hidden before they can be replaced by other widgets. Disabling updates solves this.

Resources