How can I know when a widget and layout has their final geometries?
-
Hi all!
I'm working on a program using quite complex layouts. What I want to do now is to automatically tweak the sizes of a
QSplitterwith two widgets, so that the widget on top (which itself has a quite complex layout) is fully shown if possible (sometimes, it's too big. Thus, it contains aQScrollArea, just like the bottom widget. But I want to avoid scroll bars in the top one if possible).This should happen both when the top widget is set up (it can be changed during runtime, so that widgets are deleted and re-added with different sizes) and when the window is resized.
For resizing, it's quite simple: I only have to override the
resizeEventand call my size-setting function after passing the event to the base class.But all in all, this is a more general question. How can I know when some widget and
layout actually does have it's final geoemetry?I played around with
QApplication::processEvents()and put my stuff into aQTimer::singleShot(0, [this] { … })lambda. This helps, but I don't always get the real final geometry, depending on if e.g. a database is loaded on startup (which causes the top widget to be set up immediately at startup), or one is created when the program already runs (in this case, the top widget is in an immediate state, some child widgets are already setup, others are added depending on user settings). Even, having the exact same situation, the reported sizes differ sometimes between two program starts.What always worked is to not use a 0 ms single shot, but e.g. a 100 ms one. But this can't be the right way, some older system might take slightly longer to render all the stuff.
So … what's a best practise approach to do this?
Thanks for all help!
-
Hi all!
I'm working on a program using quite complex layouts. What I want to do now is to automatically tweak the sizes of a
QSplitterwith two widgets, so that the widget on top (which itself has a quite complex layout) is fully shown if possible (sometimes, it's too big. Thus, it contains aQScrollArea, just like the bottom widget. But I want to avoid scroll bars in the top one if possible).This should happen both when the top widget is set up (it can be changed during runtime, so that widgets are deleted and re-added with different sizes) and when the window is resized.
For resizing, it's quite simple: I only have to override the
resizeEventand call my size-setting function after passing the event to the base class.But all in all, this is a more general question. How can I know when some widget and
layout actually does have it's final geoemetry?I played around with
QApplication::processEvents()and put my stuff into aQTimer::singleShot(0, [this] { … })lambda. This helps, but I don't always get the real final geometry, depending on if e.g. a database is loaded on startup (which causes the top widget to be set up immediately at startup), or one is created when the program already runs (in this case, the top widget is in an immediate state, some child widgets are already setup, others are added depending on user settings). Even, having the exact same situation, the reported sizes differ sometimes between two program starts.What always worked is to not use a 0 ms single shot, but e.g. a 100 ms one. But this can't be the right way, some older system might take slightly longer to render all the stuff.
So … what's a best practise approach to do this?
Thanks for all help!
-
Hi all!
I'm working on a program using quite complex layouts. What I want to do now is to automatically tweak the sizes of a
QSplitterwith two widgets, so that the widget on top (which itself has a quite complex layout) is fully shown if possible (sometimes, it's too big. Thus, it contains aQScrollArea, just like the bottom widget. But I want to avoid scroll bars in the top one if possible).This should happen both when the top widget is set up (it can be changed during runtime, so that widgets are deleted and re-added with different sizes) and when the window is resized.
For resizing, it's quite simple: I only have to override the
resizeEventand call my size-setting function after passing the event to the base class.But all in all, this is a more general question. How can I know when some widget and
layout actually does have it's final geoemetry?I played around with
QApplication::processEvents()and put my stuff into aQTimer::singleShot(0, [this] { … })lambda. This helps, but I don't always get the real final geometry, depending on if e.g. a database is loaded on startup (which causes the top widget to be set up immediately at startup), or one is created when the program already runs (in this case, the top widget is in an immediate state, some child widgets are already setup, others are added depending on user settings). Even, having the exact same situation, the reported sizes differ sometimes between two program starts.What always worked is to not use a 0 ms single shot, but e.g. a 100 ms one. But this can't be the right way, some older system might take slightly longer to render all the stuff.
So … what's a best practise approach to do this?
Thanks for all help!
@l3u_
@JoeCFD recommendsshowEvent(). That would be best if it solves your "timing" situation, but I'm not sure it will.singleShot(0)would be next choice. But, as you say, I have found occasions where I have to specify some non-0 delay, like 100ms, to allow the UI to fully "settle down". And 100ms is indeed a "guess". You could emit your own signals after e.g. a long database operation, but it's a lot of work and won't cover every case. See if anyone says otherwise, but I think these edge cases are a bit of a "black art" in Qt, you pays your money and you takes your chances.... -
@l3u_ override showEvent( QShowEvent * show_event ) of your widget?
https://doc.qt.io/qt-5/qwidget.html#showEvent -
I agree with @JonB that this is a "black art." (I came here to essentially express that very sentiment, although I didn't have as catchy of a phrase to describe it as "black art." That captures it well.)
I work almost entirely in QML, so my widgets knowledge it not fluent, but a general concept/idea that also might apply here could be:
watch for every QResizeEvent and/or QPaintEvent, and at those points keep track of how long the size has remained stable (either in wall clock time or in number-or-events quantity). After the "right amount" of time in a stable size, take whatever action you wanted take at the point of "final geometry reached".
-
I agree with @JonB that this is a "black art." (I came here to essentially express that very sentiment, although I didn't have as catchy of a phrase to describe it as "black art." That captures it well.)
I work almost entirely in QML, so my widgets knowledge it not fluent, but a general concept/idea that also might apply here could be:
watch for every QResizeEvent and/or QPaintEvent, and at those points keep track of how long the size has remained stable (either in wall clock time or in number-or-events quantity). After the "right amount" of time in a stable size, take whatever action you wanted take at the point of "final geometry reached".
@KH-219Design In other words: The answer to the title is "you can't" ;-)
-
@KH-219Design In other words: The answer to the title is "you can't" ;-)
-
@JoeCFD No. In this case, it's a normal classical
QWidgetbased desktop application without any touch screen involved.But as said: This was a more general question, I hit this problem several times in the past. Only that most of the time, I could work around it simply using
QTimer::singleShot(0,lambda calls. -
@JoeCFD No. In this case, it's a normal classical
QWidgetbased desktop application without any touch screen involved.But as said: This was a more general question, I hit this problem several times in the past. Only that most of the time, I could work around it simply using
QTimer::singleShot(0,lambda calls.@l3u_
Let's think about this :)From what you describe something/whatever is going on over time which adds or changes things causing the UI layout to change. You have said that even with a 100ms delay it (mostly?) seems to work for you, but you are worried that it might take longer.
Let's say there were some signal for "widget and layout has their final geometries" (whatever that might mean). You would not know how long to wait for this, would you? So (presumably) you would write code to act on either that signal or some timeout eventually expiring with nothing more happening.
So it still comes down to timing. Hence whether there is a signal or not you probably want to:
- Set off a single shot. Suggest your 100ms since you say that covers most/many cases. You could also afford to make it a bit longer, do you really think the user notices if there is somewhat more than a 10th of a second delay on whatever you want to do? Or, if you want, you could make it 0/small and then follow the next point.
- Whatever it is you claim in your situation the geometries might change later still. So you ought cater for that. Which presumably means your code allows for the layout changing and you can redo/correct whatever you did when you thought the geometries were "final". Likely you already have to allow for resizing. So either/both of a further timer which looks at whether geometry has changed or
resizeEventcan adjust for further change.
-
@l3u_
Let's think about this :)From what you describe something/whatever is going on over time which adds or changes things causing the UI layout to change. You have said that even with a 100ms delay it (mostly?) seems to work for you, but you are worried that it might take longer.
Let's say there were some signal for "widget and layout has their final geometries" (whatever that might mean). You would not know how long to wait for this, would you? So (presumably) you would write code to act on either that signal or some timeout eventually expiring with nothing more happening.
So it still comes down to timing. Hence whether there is a signal or not you probably want to:
- Set off a single shot. Suggest your 100ms since you say that covers most/many cases. You could also afford to make it a bit longer, do you really think the user notices if there is somewhat more than a 10th of a second delay on whatever you want to do? Or, if you want, you could make it 0/small and then follow the next point.
- Whatever it is you claim in your situation the geometries might change later still. So you ought cater for that. Which presumably means your code allows for the layout changing and you can redo/correct whatever you did when you thought the geometries were "final". Likely you already have to allow for resizing. So either/both of a further timer which looks at whether geometry has changed or
resizeEventcan adjust for further change.
@JonB For this special case, I solved it with two approaches.
-
The
QSplitter's handle now can't be positioned too low, because I set appropriate size policies and constraints: AsetSizeConstraint(QLayout::SetMaximumSize)for the layout of said "top" widget along with asetSizePolicy(QSizePolicy::Ignored, QSizePolicy::Maximum)for the scroll area. Using this, the handle won't travel lower as it has to to show the whole top widget when resizing the window and also can't be placed there manually. Resizing problem solved. -
The widget inside the scroll area is only changed (deleted and set up again) on user interaction or when starting up the problem. So I (pragmatically) decided to fire up a 100 ms single shot lambda to adjust the splitter's sizes so that the full top widget is shown (if possible).
There's no noticable delay in the adjustment, and everything looks good – both when starting up, and when changing the widget.
I would say this is a passable solution. Worst what could happen is that the window and all the widgets won't have their final geometry within those 100 ms. In this case, the splitter handle could be positioned at a "wrong" position and the user would have to drag it where appropriate. I think one can live with this.
-
@JonB For this special case, I solved it with two approaches.
-
The
QSplitter's handle now can't be positioned too low, because I set appropriate size policies and constraints: AsetSizeConstraint(QLayout::SetMaximumSize)for the layout of said "top" widget along with asetSizePolicy(QSizePolicy::Ignored, QSizePolicy::Maximum)for the scroll area. Using this, the handle won't travel lower as it has to to show the whole top widget when resizing the window and also can't be placed there manually. Resizing problem solved. -
The widget inside the scroll area is only changed (deleted and set up again) on user interaction or when starting up the problem. So I (pragmatically) decided to fire up a 100 ms single shot lambda to adjust the splitter's sizes so that the full top widget is shown (if possible).
There's no noticable delay in the adjustment, and everything looks good – both when starting up, and when changing the widget.
I would say this is a passable solution. Worst what could happen is that the window and all the widgets won't have their final geometry within those 100 ms. In this case, the splitter handle could be positioned at a "wrong" position and the user would have to drag it where appropriate. I think one can live with this.
@l3u_
OK, this is for two specific cases of your own. I thought you were looking for a general solution, allowing for anything going on in your widgets/layouts, even perhaps with third-party libraries/other code. Yours may well be fine for your case, just don't change/add any widgets/layouts ;-) -
-
@l3u_
OK, this is for two specific cases of your own. I thought you were looking for a general solution, allowing for anything going on in your widgets/layouts, even perhaps with third-party libraries/other code. Yours may well be fine for your case, just don't change/add any widgets/layouts ;-)