QScrollArea with only vertical scrolling - how do I make one and how does it work?
-
So, I'm dealing with an issue as old as time: I'm trying to make a
QScrollArea
which only scrolls vertically, and make the widget in it always take up exactly the full width available - no more, no less. The widget in question isQWidget
with aVBoxLayout
and all of its children areQLabel
s.Desired result:
------------------------------------------------ | QScrollArea (window, resize freely) |^|| | ----------------------------------------- | || | | QWidget | | || | | ------------------------------------- | | || | | | QVBoxLayout | | | || | | | --------------------------------- | | |o|| | | | | QLabel | | | | || | | | --------------------------------- | | | || | | | --------------------------------- | | | || | | | | QLabel with a bunch of text | | | | || | | | | that gets word-wrapped | | | | || | | | --------------------------------- | | | || | | | ... | | | || | | ------------------------------------- | | || | ----------------------------------------- |v|| ------------------------------------------------
Note that both the QWidget and QLabels should fill the whole width, even if they don't need it, but they must never exceed the width given to them by the scroll area. The horizontal scroll bar must never appear, not just hidden but because it's never needed.
If I search for this question online I get a few different answers. This is one of the top results, and to me it seems wrong: it installs an event filter to detect when the size of one thing changes and sets the width of something else so it fits. (I think it actually changes the width of the scroll area rather than the widget like I want, but that's besides the point for now). I've seen a similar solution discussed on StackOverflow, too.
But Qt already has a layout engine! Maybe I'm wrong about this, but I think I shouldn't have to tell it what size things should be, I should only have to tell it how I want things to fit together and have the layout engine take care of the rest. So, is there a way to achieve this without hacks?
Much more importantly... Can you explain the relevant bits of the layout engine? I tried reading the relevant part of the QScrollArea page as well as the entirety of the Layout Management page, but I just don't understand how the different bits affect each other. I've found that Size Policy would give me a great deal of control over the preferred behaviour of the vertical and horizontal sizes separately, but with a layout set (QVBoxLayout in my case) this option is nullified - the layout's size policy is used instead, but it doesn't have one, the only thing it seems to have is setSizeConstraint which has an extremely limited enum of options, and doesn't explain what they mean for the different axes in a VBoxLayout.
Sorry for the novel. TL;DR:
- What is the best-practice way to make a ScrollArea only scroll vertically, and make the child widget take up the full width?
- Can you explain how it works, so that I can learn something from it and perhaps apply it later on in my project?
Thanks.
Edit: I'm using PySide6 but this shouldn't matter, C++ answers would be just as good.
-
Hi,
If memory serves well, you have to set the layout QSizePolicy to fixed.
-
So, I'm dealing with an issue as old as time: I'm trying to make a
QScrollArea
which only scrolls vertically, and make the widget in it always take up exactly the full width available - no more, no less. The widget in question isQWidget
with aVBoxLayout
and all of its children areQLabel
s.Desired result:
------------------------------------------------ | QScrollArea (window, resize freely) |^|| | ----------------------------------------- | || | | QWidget | | || | | ------------------------------------- | | || | | | QVBoxLayout | | | || | | | --------------------------------- | | |o|| | | | | QLabel | | | | || | | | --------------------------------- | | | || | | | --------------------------------- | | | || | | | | QLabel with a bunch of text | | | | || | | | | that gets word-wrapped | | | | || | | | --------------------------------- | | | || | | | ... | | | || | | ------------------------------------- | | || | ----------------------------------------- |v|| ------------------------------------------------
Note that both the QWidget and QLabels should fill the whole width, even if they don't need it, but they must never exceed the width given to them by the scroll area. The horizontal scroll bar must never appear, not just hidden but because it's never needed.
If I search for this question online I get a few different answers. This is one of the top results, and to me it seems wrong: it installs an event filter to detect when the size of one thing changes and sets the width of something else so it fits. (I think it actually changes the width of the scroll area rather than the widget like I want, but that's besides the point for now). I've seen a similar solution discussed on StackOverflow, too.
But Qt already has a layout engine! Maybe I'm wrong about this, but I think I shouldn't have to tell it what size things should be, I should only have to tell it how I want things to fit together and have the layout engine take care of the rest. So, is there a way to achieve this without hacks?
Much more importantly... Can you explain the relevant bits of the layout engine? I tried reading the relevant part of the QScrollArea page as well as the entirety of the Layout Management page, but I just don't understand how the different bits affect each other. I've found that Size Policy would give me a great deal of control over the preferred behaviour of the vertical and horizontal sizes separately, but with a layout set (QVBoxLayout in my case) this option is nullified - the layout's size policy is used instead, but it doesn't have one, the only thing it seems to have is setSizeConstraint which has an extremely limited enum of options, and doesn't explain what they mean for the different axes in a VBoxLayout.
Sorry for the novel. TL;DR:
- What is the best-practice way to make a ScrollArea only scroll vertically, and make the child widget take up the full width?
- Can you explain how it works, so that I can learn something from it and perhaps apply it later on in my project?
Thanks.
Edit: I'm using PySide6 but this shouldn't matter, C++ answers would be just as good.
@NeatNit
Look at:
setHorizontalScrollBarPolicy
and
setSizeAdjustPolicyYou may have to set
setWidgetResizable(bool resizable)
to true. -
@SGaist and @mpergand
Please take a look at this minimal example:import sys from PySide6.QtWidgets import QApplication, QPlainTextEdit, QScrollArea, QWidget, QVBoxLayout, QLabel full_text = """First line Second line This is the third line, with a lot of words that make this line very long and causes word wrap to come into effect aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb bbb cccccccccccccccccccccccccccccc ccc dddddddddddddddddddddddddddddd ddd eeeeeeeeeeeeeeeeeeeeeeeeeeeeee eee ffffffffffffffffffffffffffffff fff gggggggggggggggggggggggggggggg ggg hhhhhhhhhhhhhhhhhhhhhhhhhhhhhh hhh iiiiiiiiiiiiiiiiiiiiiiiiiiiiii iii jjjjjjjjjjjjjjjjjjjjjjjjjjjjjj jjj kkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkk llllllllllllllllllllllllllllll lll mmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmm nnnnnnnnnnnnnnnnnnnnnnnnnnnnnn nnn oooooooooooooooooooooooooooooo ooo pppppppppppppppppppppppppppppp ppp qqqqqqqqqqqqqqqqqqqqqqqqqqqqqq qqq rrrrrrrrrrrrrrrrrrrrrrrrrrrrrr rrr ssssssssssssssssssssssssssssss sss tttttttttttttttttttttttttttttt ttt uuuuuuuuuuuuuuuuuuuuuuuuuuuuuu uuu vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv vvv wwwwwwwwwwwwwwwwwwwwwwwwwwwwww www xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyy zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz zzz""" app = QApplication(sys.argv) # QTextEdit = desired behaviour: text_edit = QPlainTextEdit() text_edit.setReadOnly(True) text_edit.setPlainText(full_text.replace('\n', "\n\n")) text_edit.setWindowTitle("Desired behaviour") text_edit.show() # QScrollArea + QWidget + QVBoxLayout + QLabels = desired layout structure, bad behaviour: view = QWidget() layout = QVBoxLayout(view) layout.setSizeConstraint(QVBoxLayout.SetMinimumSize) for txt in full_text.split('\n'): lbl = QLabel(txt) lbl.setWordWrap(True) layout.addWidget(lbl) scroll = QScrollArea() scroll.setWidget(view) scroll.setWindowTitle("Bad behaviour, desired layout structure") scroll.show() sys.exit(app.exec())
I tried various combinations of the settings you mentioned and couldn't get it working. Can you run this code on your end and do it?
For now you can assume that the window is always wider than the longest word - but spoiler alert, getting word-wrap working mid-word on QLabels will be my next question ;)
-
@SGaist and @mpergand
Please take a look at this minimal example:import sys from PySide6.QtWidgets import QApplication, QPlainTextEdit, QScrollArea, QWidget, QVBoxLayout, QLabel full_text = """First line Second line This is the third line, with a lot of words that make this line very long and causes word wrap to come into effect aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb bbb cccccccccccccccccccccccccccccc ccc dddddddddddddddddddddddddddddd ddd eeeeeeeeeeeeeeeeeeeeeeeeeeeeee eee ffffffffffffffffffffffffffffff fff gggggggggggggggggggggggggggggg ggg hhhhhhhhhhhhhhhhhhhhhhhhhhhhhh hhh iiiiiiiiiiiiiiiiiiiiiiiiiiiiii iii jjjjjjjjjjjjjjjjjjjjjjjjjjjjjj jjj kkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkk llllllllllllllllllllllllllllll lll mmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmm nnnnnnnnnnnnnnnnnnnnnnnnnnnnnn nnn oooooooooooooooooooooooooooooo ooo pppppppppppppppppppppppppppppp ppp qqqqqqqqqqqqqqqqqqqqqqqqqqqqqq qqq rrrrrrrrrrrrrrrrrrrrrrrrrrrrrr rrr ssssssssssssssssssssssssssssss sss tttttttttttttttttttttttttttttt ttt uuuuuuuuuuuuuuuuuuuuuuuuuuuuuu uuu vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv vvv wwwwwwwwwwwwwwwwwwwwwwwwwwwwww www xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyy zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz zzz""" app = QApplication(sys.argv) # QTextEdit = desired behaviour: text_edit = QPlainTextEdit() text_edit.setReadOnly(True) text_edit.setPlainText(full_text.replace('\n', "\n\n")) text_edit.setWindowTitle("Desired behaviour") text_edit.show() # QScrollArea + QWidget + QVBoxLayout + QLabels = desired layout structure, bad behaviour: view = QWidget() layout = QVBoxLayout(view) layout.setSizeConstraint(QVBoxLayout.SetMinimumSize) for txt in full_text.split('\n'): lbl = QLabel(txt) lbl.setWordWrap(True) layout.addWidget(lbl) scroll = QScrollArea() scroll.setWidget(view) scroll.setWindowTitle("Bad behaviour, desired layout structure") scroll.show() sys.exit(app.exec())
I tried various combinations of the settings you mentioned and couldn't get it working. Can you run this code on your end and do it?
For now you can assume that the window is always wider than the longest word - but spoiler alert, getting word-wrap working mid-word on QLabels will be my next question ;)
-
@NeatNit
try:
scroll.setWidgetResizable(true)if it doesn't work add:
scroll.setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents) -
@mpergand This causes the vertical layout to stretch when the window is taller than the contents.
Edit: See the screenshot:
-
For the benefit of future readers, who are frustrated to find that (despite the indications to the contrary) the code does not actually work, even after adding the modifications from comment #5:
What you actually need to do here is set the horizontal size policy on the inner widget (i.e. "view") to Ignored. This causes it to use the size of its container. You do not actually need setSizeConstraint() on the layout nor setSizeAdjustPolicy() on the scroll area.
Also, wrt the problem mentioned in comment 6, instead of adding stretch you can (and probably should) instead set the vertical size policy on the view to Maximum.