I've had a real challenge getting QScrollArea to take the minimum space possible up to a maximum height.
My GUI model is as follows: A QScrollArea contains a vertical layout which is populated with a widget of class TableRow. I want this class TableRow to take up the minimum height possible. It has a widget at the top which is always visible, and a QScrollArea below which has a label inside it whose visibility can be toggled. The label is for notes which may be 0 characters or may be infinite in length (hardware limitations aside).
I've found that for a label in class TableRow setting the vertical sizePolicy to Fixed will actually take up exactly how much it needs to fit all the contents (see: Qt Layout, resize to minimum after widget size changes). However this doesn't appear to work with QScrollArea. In fact every sizePolicy I've tried keeps the QScrollArea at a fixed height; except for Ignore, but then the QScrollArea goes to a height of 0, regardless of its contents.
I've created a git branch producing a simplified version of this problem.
Here is the result of applying a fixed vertical sizePolicy:
What I'm expecting from this test case:
The first widget's height should be almost 30px (the height of the upper widget) only showing the borders for the QLabel and QScrollArea
The second widget's height should be shorter than 130px (the maximum height of the QScrollArea being 100px) but large enough to show the label without scrolling
The third widget's height should be 130px, and the scrollbar should appear (this part is correct in every case I've tried except for when the vertical sizePolicy is set to Ignored )
I understand I may need to override some things to make this work, as by itself it's not obvious why a QScrollArea's height might be dependent on its child widgets (which is probably why it was not designed to make this easy, or at least it seems like it wasn't).
However, I think the case I'm trying to make is common enough, and my current approach is justifiable. If there's another/better way to make an individual widget scroll after it reaches a maximum height I'm open to that as an answer, provided it meets the three conditions I'm expecting.
This feels more like a hack than a solution, but it does work for me, at least in the short term. Because the text for lblNotes does not change runtime, I was able to add the following code in the constructor of my TableRow widget:
// Hack to resize QScrollAreas
ui->lblNotes->adjustSize(); // Otherwise lblNotes will think its height is still ~0px
int height = ui->lblNotes->height() + 12; // Borders and margins add up to 12px
if (height > 100) { height = 100; } // Cap the height at desired maximum value
ui->scrollArea->setFixedHeight(height);
Should I have to deal with the case of dynamically set text, I could wrap this in a function to be called anytime the text of lblNotes is set.
I'm still open to solutions that involve using the layout features Qt has natively as I believe that would be preferred if a solution exists. Some QScrollArea contents may not be as straight-forward to determine the height from in the future.
Related
I have the following, quite simple setup:
In a QWidget that is displayed as a window (no parent), there is a single QVBoxLayout.
In that QVBoxLayout, there is a single custom QGraphicsView.
The size policy of that custom QGraphicsView is set to Preferred/Preferred and setHeightForWidth set to true (and overwritten in the custom class) - I want to preserve aspect ratio.
The whole constructor of the widget here:
graphicsView = new CustomGraphicsView();
QVBoxLayout *layout = new QVBoxLayout();
layout->setMargin(0);
QSizePolicy sp(QSizePolicy::Preferred, QSizePolicy::Preferred);
sp.setHeightForWidth(true);
graphicsView->setSizePolicy(sp);
layout->addWidget(graphicsView);
setLayout(layout);
The setup works, and the aspect ratio is maintained when dragging the width of the window bigger (the height grows with it).
But as soon as I drag the window width smaller, the aspect ratio of the graphics view is maintained, but the height of the window itself won't shrink. The result being a small graphics view with lots of space above and below it that shouldn't be there.
Investigating it further I was trying to find out where things break, so I overloaded all the sizeHint(), minimumSize(), minimumHeight(), etc. functions of my custom graphics view.
Just to discover that not a single one of them is ever being called while I am manually resizing the window. The only thing called as expected is heightForWidth - which returns my calculated value - but its return value is not applied when the window is shrinking.
So, not only do I not know why the layout won't shrink on its own despite having the Preferred vertical policy (which explicity says "The widget can be expanded, but there is no advantage to it being larger than sizeHint()").
I also don't know how the layout gets the size from the widget to begin with. I assumed sizeHint() since all of the documentation permanently refers to it, but that is obviously wrong in this case.
What I already tried is to set the vertical policy to every possible value. None of them would cause the window to grow and shrink as it is supposed to.
My current workaround:
I have added the resizeEvent(...) function to the widget and inside that, I manually resize() the whole widget if its height exceeds the value returned by the heightForWidth() function of the custom graphics widget.
Okay as far as workarounds go, but it leads to heavy flickering (as usual when resizing inside a resizeEvent).
Any ideas on either problem?
I have a window structured in the following manner:
Window>VBox>Scrolled Window>Tree View>Columns
My issue arises when I label the last column (it must be a dynamic assignment). If the label winds up being too long, the containing window gets stretched horizontally. Instead, I would like a scroll bar to appear at the bottom of the Scrolled Window to deal with it, leaving the window at its original width.
However, it looks like the closest I can come is fixing the height of the Tree View. Surely there's a way to fix the width?
Do you have some code to look at? I have done this many times in python and never had any trouble. Also, you link to the fixed height property of the Tree View rows, you actually need the requested width property of the scrolled window. That link also has a link to set the size of the window.
I need to create a scrollable, owner-drawn widget that behaves a lot like QPlainTextEdit with word-wrapped text, in the sense that the height depends on the width - as the content width decreases, the content height increases.
What is the best approach to do it? I was thinking about putting my QWidget-derived class inside a QScrollArea, but QPlainTextEdit is derived from QScrollArea instead, should I go that route?
Also, I want to paint only the visible area in paintEvent(), it would be wasteful otherwise.
Right now I'm examining the code of QPlainTextEdit, but it is rather complex and not easy to read, so if anyone knows of a code example that's simpler on the web, you can give me a link, it would help a lot.
I'll post the solution I came up with. It's not the best, but it mostly works.
I did not derive from QAbstractScrollArea in the end, instead I simply embedded my widget in a QScrollArea with a vertical layout, which worked well-enough.
I implemented resizeEvent() (I saw this from QPlainTextEdit implementation), and each time the width changes, I recalculate the height, and I set the widget's minimum height to that. I set the minimum height because of how the layout works.
void MyWidget::resizeEvent(QResizeEvent *e)
{
// If the widget's width has changed, we recalculate the new height
// of our widget.
if (e->size().width() == e->oldSize().width()) {
return;
}
setMinimumHeight(calculateHeightFromWidth(e->size().width()));
}
For drawing only the visible area see Get visible area of QPainter
I have a QVBoxLayout which has a QSCrollArea and with addStretch() added to its end.
QScrollArea here gets to use only half of the available space eventhough there is free space below,, currently occupied by addStretch.
If contents of the QScrollArea are large enough, I want QScrollArea to expand to all the available space. When QScrollArea's contents are smaller, I want QScrollArea to shrink to that size, with rest of the available space used by addStetch.
However, currently, for small QScrollArea this works but when QScrollArea's contents are larger, it only expands to half of the available space.
I tried setting various Size Policies for the QScrollArea and the underlying scrolling widget but nothing works.
What's the trick to get this sorted?
Here's a snipped of my code [in Python but answers in C++ are very welcome]:
# My setup
self.layout = QVBoxLayout()
self.setLayout(self.layout)
scrollArea = QScrollArea()
self.layout.addWidget(scrollArea)
scrollArea.setWidgetResizable(True)
scrollArea.setEnabled(True)
self.scrollWidget = QWidget()
self.scrollLayout = QVBoxLayout()
self.scrollWidget.setLayout(self.scrollLayout)
scrollArea.setWidget(self.scrollWidget)
# Now what I do is, I add bunch of widgets inside the self.scrollLayout
# and in the end, I perform a self.layout.addStretch() to push them up (verticle Alignment does same)
# Maximum height QScrollArea (scrollArea) gets is stuck at around 50% although I want to take all the space if necessary
I added ALL possible setSizePolicy(..) options
scrollArea.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
# and ALL others. None works. addStretch() still takes 50% minimum space and gets even more if QScrollArea's contents are smaller.
I just had the very same problem and I think I found a solution. Do not add a stretch after the QScrollArea, but as last element inside the QScrollArea layout. So in your code, use self.scrollLayout.addStretch() instead of self.layout.addStretch().
I found the solution.
It's necessary to override sizeHint to holding scrollarea's scrolling widget.
It's necessary to set maximum height to scrolling widget.
And it's necessary to place this widget inside a QVBoxLayout with addStretch() which pushes it to the top.
When QScrollArea's contents are smaller, I want QScrollArea to shrink
to that size, with rest of the available space used by addStetch.
If contents of QScrollArea are small, you can align them to the top and hide vertical scrollbar. In this case you won't need to add stretch to the main vertical layout.
If contents are larger than QScrollArea, it will take all available space.
I think, this is what you need.
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.