I'm using Qt to create a sortable bar graph-like widget. The widget is laid out with a QHBoxLayout for each bar in the graph (which are also widgets).
When the user changes the sorting parameters, my controller clears the layout, sorts it, and calls addWidget on every item in order. The problem I'm facing is that this list is quite large (1000+ widgets), and Qt is crawling when attempting to layout that many elements.
I've found out that each time addWidget is called, Qt will recalculate the location of every item in the QHBoxLayout and then update. This is a problem, as it performs a lot of unnecessary calculations on each LayoutItem before the one I am adding. For 1000 test items that I'm putting in, resorting takes around 30 seconds, which is obviously far too long.
Is there a better Layout type to use, or is there a faster way to add a collection of sorted widgets to a layout?
Edit:
Apparently, the issue I've been having isn't with Qt laying out each item again, but the internals of all the parenting and shenanigans that go on behind the scenes I set up a test project to add 1000 buttons to a layout, printing out a counter each time. The numbers printed out almost instantaneously, though it took that same ~30 seconds for Qt to straighten itself out. I think the problem doesn't exist only with QHBoxLayout, but with Qt's layout system in general. Looks like I'll be manually positioning widgets and bypassing a QLayout altogether. Thanks to everyone who gave their input.
From documenation of void QLayout::setEnabled(bool enable)
Enables this layout if enable is true, otherwise disables it.
An enabled layout adjusts dynamically to changes; a disabled layout acts as if it did
not exist.
By default all layouts are enabled.
What about making a Graph widget (composed of Bar (or whatever name you choose) widgets) which would contain your own QLayout subclass optimized for this task (since you have to reimplement QLayout::addItem() which is pure virtual in QLayout).
Also, be sure to check out Qwt, since it already might have implemented what you want.
Perhaps you can create a new QHBoxLayout without parent, add your widgets to this layout, and then delete the old QHBoxLayout and insert your new layout. WARNING: I did not try this.
Related
There are hundreds of widgets in QVBoxLayout. I am hiding/showing them based on option menus. If I hide some of widgets, some blank space remains in QVBoxLayout and I dont want this unnecessary space. Adding spacer at bottom is not solving the issue. Same for setting margin spacing. Its like hidden widgets consume some space. Is there any way to fix this?
Thanks.
Layouts have some default spacing between each child widget, defined by setSpacing(), setHorizontalSpacing(), setVerticalSpacing(). Even if you hide the child widget, the spacing around it remains visible. (Note: I think this is a bad design decision made by Qt developers, but we need to live with it.) You have basically these options:
a) Remove the child widget from the layout instead of hiding it. Remember its original position and if it should be shown again, insert it at that position. This is complicated, the original position may be invalidated if you removed some other widgets meanhile, so this would require some clever algorithm for maintaining the correct visual positions of the hidden child widgets in the layout etc. I would not do this, feels too complicated for me.
b) Use zero-sized default spacing in the layout and add spacings manually by https://doc.qt.io/qt-5/qboxlayout.html#addSpacing and then when hiding the child widgets, set the size of the QSpacerItem next to it to zero. And set it back to non-zero when showing the child widget again.
c) Alternative to a) but do not keep original indexes but have a container of pointers to the child widgets and when a change in the visibility of the child widgets is required, then remove all the items from the layout and put all the child widgets which should be visible to the layout. This means to re-create the content of the layout in each change. Actually this is how I am doing it in my code. I have about 20 widgets and hiding/showing is fast enough. I believe it will be fast enough even for hundreds of widgets.
d) And alternative to c) ... if you have really large number of widgets, then you should consider deleting those which are not supposed to be visible and re-creating them when they are shown. I.e. in c) we keep the widget alive but hidden in a certain container but in d) we delete this widget and create it later again. It depends on your use case whether c) is better than d) or vice versa. My gut feeling is that c) should be fine performance-wise and is simpler.
Note: My reasonging is based on showing and hiding of widgets in grid layout, but I guess that VBox layout has the same principles regarding preserving default spacing even around hidden items.
When writing grid-like dialogs I often happen to need multiple columns having the same structure, with the number of columns known only at runtime (think configuration parameters for multiple instances of the same kind of object).
In this situation, the regular Qt widgets designer falls short, because, although I can comfortably design the various headers and a "prototype column", there is no easy way to replicate the creation of such column at runtime (all the widgets are created together in setupUi, and there's no easy way to clone QWidgets).
An alternative may be to split up the "prototype column" in a separate custom widget (that aligns the child widgets using a QVBoxLayout) and instantiate such custom widget at runtime in the parent dialog, putting those instances in a QHBoxLayout.
The problem with this approach is that the widgets of different columns may end up horizontally unaligned, as each QVBoxLayout performs its calculations on its own without considering the other columns, so the desired grid layout is easily messed up.
The only other possibility that comes to mind would be to split up the prototype column in a separate widget as described but, instead of putting the custom widget in a QHBoxLayout, "stealing" its children and putting them correctly aligned in the parent QGridLayout. This results in the desired graphical effect, but reparenting the children and hiding the custom widget feels a bit like a hack.
Now, my question is twofold:
are there potential issues I may be unaware of in happily reparenting such children widgets, given that their "fake" container isn't going to do anything particular with them?
are there better approaches to this issue? Ideally I would think about some method to merge QGridLayout of children widgets with a "main" grid, but there seems not to be such a thing.
I'm currently building a program in QT in which I have several custom widgets in a frame, and upon a button press more will enter the frame. I've implemented my own functionality to drag and move them around the frame, and that all works fine, but here's the problem:
When I already have some of these widgets on the screen that have moved from their initial location and I go to add another widget to the layout, the entire layout resets the locations of every widget beneath it.
I've been using .move(x,y) to move them around, and .addWidget(my widget) as the way to add the widgets to the layout.
Is there any way to prevent this, or some sort of work around? Or if nothing else, any other way to add them to the frame in which they wont automatically move around when a new one is added?
I solved my problem to some degree, but it is a bit of a work around.
Since when new widgets were added to the layout the layout would repaint them all so that they were spaced apart/aligned with the parameters it was set with, I attempted to compensate for this function by saving to each widget its x,y coordinates and move them back immediately once a new widget was added to the layout.
Initially the effort was in vain since SOMEHOW the layout detected this or something, and immediately moved them back.
However, this is how I solved it - I just used a stackedWidget. It would still try to automatically orientate each with some spacing when a new widget was added, but my little for loop to re-move every sub widget back (which I haven't changed whatsoever) now works.
this is a question for programming with Qt/C++. I have a combo box with two items. If current index for selection is 0, then no QLineEdit should be displayed in layout below the combo box. If it is 1, a QLineEdit should appear. It should disappear again if index is 0 again.
Notably, other elements in the layout should not be affected by the change. Values already entered by user in other QineEdit should remain in place.
Is it possible to dynamically modify widget? How did you procede?
Kind regards.
All QWidget objects have a function called hide().
You can attach a signal to the currentIndexChanged signal of the combo box, and in that function you implement whatever logic you have in mind and invoke the method hide of your QLineEdit.
The only problem with this approach is that a Qt Widget, when hidden, doesn't occupy any space on the screen, and this can lead to layout changes (depending on how you've programmed your layout, some other widgets can move a bit, for example). To prevent that you can make another Widget appear where the QLineEdit were (perhaps invoking the show() function, and placing the 'placeholder' on the same container that the LineEdit was), only to occupy its space and keep it there, or you can use a QStackedWidget add the two Widgets there and change its index.
I would recommend that you read the following example, it has some useful insight on dynamically changing things: Qt Extension Example.
Also, when in doubt, take a look in the other examples, they are really well documented and cover a lot of important topics on Qt.
Good luck with your code :)
How do you change the behavior of a QListWidget so that it resizes its height instead of choosing a (seemingly arbitrary) height and adding scrollbars? See screenshot:
The QListView's should fill up as much space horizontally as they can (creating as many "columns," if you will.) Then they wrap and make as many rows as necessary to fit all the items. These calculations should be adjusted as the window is resized. This is all working fine.
However, what I want to happen is that instead of the height staying the same, the QListView should grow or shrink vertically and never need any scrollbars. The scrolling, if necessary, will be handled on the parent QWidget that hosts all of the labels and lists. It seems like once the height of the QListWidget is established (not sure where its default is coming from), it never changes. It is too big in some cases (see second "Test" list above) and too small in others (see first "blank maps" list above.)
The layout above is nothing surprising: two QLabel's and two QListWidget's in a QVBoxLayout. Here are the properties I have set on the QListWidget's:
setMovement(QListView::Static);
setResizeMode(QListView::Adjust);
setViewMode(QListView::IconMode);
setIconSize(QSize(128, 128));
(I already tried setting the horizontal and vertical scrollbar policies, but that just turns the scrollbars off, clipping the content. Not what I want.)
Maybe you could this without using QListWidget. The Qt's examples contain a new layout class, QFlowLayout, which could be useful. With the following kind of widget hierarchy you could get multiple groups with labels and they all would be inside one QScrollArea.
QScrollBox
QVBoxLayout
QLabel "Blank maps"
QWidget
QFlowLayout
your own widgets showing map images and labels
QLabel "Text"
QWidget
QFlowLayout
your own widgets
The problem is that this kind of solution would create much more widgets than QListWidget based solution. So if you have hundreds of items in your list, this might not be the best solution.
There is a protected member function called contentsSize() in QListView. It is used to calculate the required minimum(), maximum(), and pageStep() for the scrollbars (as mentioned here).
Can you subclass the QListView class and make use of that information? I suggest you recalculate the size of your widget in the same function where you add contents to it. While somewhat lacking elegance, this appears to be a pretty reliable solution.