I have a label:
self.label = QtLabel(self)
that is inside of a VBoxLayout.
That I want to set in the center of a QWizardPage, no matter what size the window becomes. I managed to get it centered horizontally with:
self.label.setAlignment(QtCore.Qt.AlignCenter)
but I cannot seem to get it to center vertically too. I've tried
self.label.setAlignment(QtCore.Qt.AlignVCenter)
and:
self.label.setAlignment(QtCore.Qt.AlignCenter | AlignVCenter)
and a couple of other things that I cannot remember at this moment (I'll edit if I do). After reading this answer it seemed the problem had something to do with setting a min and max size. I tried that, setting MinimumHeight and MaximumHeight to 200. That roughly centered the label but it doesn't adapt to changes in the window's height, only its width.
How can I center this label directly in the middle of my page?
Add your label in between two spacer items. Your vertical layout should also be laid out in its parent widget so it takes full size of the parent.
QSpacerItem* verticalSpacer1 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
label = new QLabel(Form);
label->setAlignment(Qt::AlignCenter);
QSpacerItem* verticalSpacer2 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
verticalLayout->addItem(verticalSpacer1);
verticalLayout->addWidget(label);
verticalLayout->addItem(verticalSpacer2);
If you don't want to set a minimum size policy you could use QWidgets to do something like this:
QWizardPage.__init__(self)
intro_text = "Some text that needs to be centered..."
self.introVBox = QVBoxLayout(self)
self.sizer_top = QWidget()
self.sizer_top.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.sizer_bottom = QWidget()
self.sizer_bottom.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.label = QLabel(self)
self.label.setText(intro_text)
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.introVBox.addWidget(self.sizer_top)
self.introVBox.addWidget(self.label)
self.introVBox.addWidget(self.sizer_bottom)
self.setLayout(self.introVBox);
Related
I am currently developing a chat application in order to be familiar with the pyqt5 framework. Still I am not that familiar with layouts. I tried to stack up recieved messages in a scrollable area after putting them into a label. But if there is one message, it occupies all the space from the scroll area. This is how it looks like.
I want it to take the minimum size it can take irrespective of number of labels in scroll area. Like this.
]
How can I do that? This is the code.
def __init__(self): #constructor of main application
###
####
self.scrollwid = QtWidgets.QWidget()
self.scrollareavbox = QtWidgets.QVBoxLayout()
self.scrollwid.setLayout(self.scrollareavbox)
self.screen3.s3.scrollArea.setWidget(self.scrollwid) #THE SCROLL AREA IS IN SCREEN3 NAMED scrollArea
self.screen3.s3.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.screen3.s3.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
###
def messageUpdateSlot(self, message): #WHERE I STACK LABELS
#create a new Qlabel and put that in the stacked widget.
lbl_instance = self.createLabel(message)
lbl_instance.setStyleSheet("border: 2px solid black; background-color: yellow")
self.scrollareavbox.addWidget(lbl_instance)
def createLabel(self, text): #WHERE I CREATE LABEL FOR EACH MESSAGE RECIEVED
label = QtWidgets.QLabel()
label.setText(text)
#label.setObjectName(_fromUtf8(objName))
return label
I am trying to use PyQt5 for one of my GUI application. I could be able to add widgets as I want, but couldn't align them properly. I want to align my widgets as below:
But, My code is working something like this,
Following is my code, can anyone help me please?
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QRect
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QDesktopWidget, QLabel
class GroupBox(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.setGeometry(QtCore.QRect(20, 20, 900, 700))
self.setWindowTitle("InvoiceMee - Split Documents")
layout = QtWidgets.QGridLayout(self)
groupbox = QtWidgets.QGroupBox("Files to Convert", checkable=False)
layout.addWidget(groupbox)
hbox = QtWidgets.QHBoxLayout()
groupbox.setLayout(hbox)
label = QLabel()
pixmap = QPixmap('images.jpg')
label.setPixmap(pixmap)
label.resize(pixmap.width(), pixmap.height())
pathBox = QtWidgets.QLineEdit(self)
pathBox.setPlaceholderText("Enter the Path Here")
pathBox.setGeometry(QRect(160, 150, 201, 20))
selectFileBtn = QtWidgets.QPushButton("Select")
convertButton = QtWidgets.QPushButton("Convert")
good_radiobutton = QtWidgets.QRadioButton("Invoices")
naive_radiobutton = QtWidgets.QRadioButton("Credit Notes")
hbox.addWidget(pathBox, alignment=QtCore.Qt.AlignCenter)
hbox.addWidget(selectFileBtn, alignment=QtCore.Qt.AlignCenter)
hbox.addWidget(convertButton, alignment=QtCore.Qt.AlignCenter)
hbox.addWidget(good_radiobutton, alignment=QtCore.Qt.AlignCenter)
hbox.addWidget(naive_radiobutton, alignment=QtCore.Qt.AlignCenter)
hbox.addWidget(label,alignment=QtCore.Qt.AlignCenter)
hbox.addStretch()
self.center()
def center(self):
# geometry of the main window
qr = self.frameGeometry()
# center point of screen
cp = QDesktopWidget().availableGeometry().center()
# move rectangle's center point to screen's center point
qr.moveCenter(cp)
# top left of rectangle becomes top left of window centering it
self.move(qr.topLeft())
Use QGridLayout instead of QHBoxLayout. Grid Layout gives you the option to layout your widgets in a grid like struture. Here's the official documentation for QGridLayout.
You can change your layout like this:
grid = QtWidgets.QGridLayout()
groupbox.setLayout(grid)
grid.addWidget(label,0,0,1,0,QtCore.Qt.AlignCenter)
grid.addWidget(pathBox,1,0,QtCore.Qt.AlignRight)
grid.addWidget(selectFileBtn,1,1,QtCore.Qt.AlignLeft)
grid.addWidget(good_radiobutton,2,0,QtCore.Qt.AlignRight)
grid.addWidget(naive_radiobutton,2,1,QtCore.Qt.AlignLeft)
grid.addWidget(convertButton,3,0,1,0,QtCore.Qt.AlignCenter)
Add a vertical spacer item if you want to remove space between your widgets:
verticalSpacer = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
grid.addItem(verticalSpacer, 6, 0, QtCore.Qt.AlignTop)
You are using a QHBoxLayout (which stands for horizontal box layout). This means that all widgets that you added will be always displayed side by side, horizontally, according to the order of insertion.
You should use a layout that allows vertical orientation, instead.
You're using more than a widget per row, so you could use a QGridLayout, but, since some of those widgets have different horizontal sizes, the result might not be what you showed us.
The solution is to use nested layouts, with a main grid layout with stretch sets for first/third row and column and a "central" layout added to the second row/column of the grid. Then, whenever you need more than one widget in a row, add a nested QHBoxLayout.
class GroupBox(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.setGeometry(QtCore.QRect(20, 20, 900, 700))
self.setWindowTitle("InvoiceMee - Split Documents")
layout = QtWidgets.QGridLayout(self)
groupbox = QtWidgets.QGroupBox("Files to Convert", checkable=False)
layout.addWidget(groupbox)
# the "main" layout, used to ensure that the actual layout containing
# all widgets stays in the center
groupLayout = QtWidgets.QGridLayout()
groupbox.setLayout(groupLayout)
groupLayout.setColumnStretch(0, 1)
groupLayout.setColumnStretch(2, 1)
groupLayout.setRowStretch(0, 1)
groupLayout.setRowStretch(2, 1)
# this is the actual layout used to add widgets
centerLayout = QtWidgets.QVBoxLayout()
groupLayout.addLayout(centerLayout, 1, 1)
label = QLabel()
pixmap = QPixmap('images.jpg')
label.setPixmap(pixmap)
# this won't work
# label.resize(pixmap.width(), pixmap.height())
pathBox = QtWidgets.QLineEdit(self)
pathBox.setPlaceholderText("Enter the Path Here")
# this won't work either, the layout will try to move and resize it anyway
# pathBox.setGeometry(QRect(160, 150, 201, 20))
# use minimum width instead
pathBox.setMinimumWidth(200)
selectFileBtn = QtWidgets.QPushButton("Select")
convertButton = QtWidgets.QPushButton("Convert")
good_radiobutton = QtWidgets.QRadioButton("Invoices")
naive_radiobutton = QtWidgets.QRadioButton("Credit Notes")
centerLayout.addWidget(label, alignment=QtCore.Qt.AlignCenter)
# the second row has more than one widget, use a nested horizontal layout
inputLayout = QtWidgets.QHBoxLayout()
centerLayout.addLayout(inputLayout)
inputLayout.addWidget(pathBox)
inputLayout.addWidget(selectFileBtn)
# the same for the radio buttons
radioLayout = QtWidgets.QHBoxLayout()
centerLayout.addLayout(radioLayout)
# use horizontal alignment to keep buttons closer, otherwise the layout
# will try to expand them as much as possible (depending on the other
# widgets in the centerLayout)
radioLayout.addWidget(good_radiobutton, alignment=QtCore.Qt.AlignRight)
radioLayout.addWidget(naive_radiobutton, alignment=QtCore.Qt.AlignLeft)
# use center alignment so that the button doesn't expand
centerLayout.addWidget(convertButton, alignment=QtCore.Qt.AlignCenter)
I'd suggest you to carefully study how layout work and behave, make some experiments and also use Qt Designer to easily see how nested layout can work.
Also, consider that in some cases it might be necessary to set a specific size policy to avoid widgets expanding too much, and use a QWidget "container" can make things easier.
For example, instead of using the horizontal alignment when you add the radio buttons, you can use a QWidget container:
# ...
radioContainer = QtWidgets.QWidget()
centerLayout.addWidget(radioContainer, alignment=QtCore.Qt.AlignCenter)
radioContainer.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
QtWidgets.QSizePolicy.Preferred)
radioLayout = QtWidgets.QHBoxLayout(radioContainer)
radioLayout.addWidget(good_radiobutton)
radioLayout.addWidget(naive_radiobutton)
# ...
I'm trying to make a widget which contains many other widgets and I keep having problems with resizing the window: the widget keeps expanding even if I "tell" it not to. Here is my minimal example:
from PyQt5 import QtWidgets, QtGui, QtCore
class CustomWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.layout = QtWidgets.QGridLayout()
self.button1 = QtWidgets.QPushButton("Button A")
self.button2 = QtWidgets.QPushButton("Button B")
self.label1 = QtWidgets.QLabel("Long label that can span multiple columns")
self.layout.addWidget(self.button1, 0, 0)
self.layout.addWidget(self.button2, 0, 1)
self.layout.addWidget(self.label1, 1, 0, 1, 2)
self.setLayout(self.layout)
class App(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.cw = CustomWidget()
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.cw)
self.setLayout(self.layout)
self.show()
QtWidgets.QApplication.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
app = QtWidgets.QApplication([])
win = App()
status = app.exec_()
This code does work however if I resize the window then all the buttons and labels get spread out over the screen which is not what I want.
I've tried:
Setting a fixed size: doesn't work because the label text can be different lengths and I want the widget to resize accordingly (but stay as small as possible)
self.setFixedSize(self.size()) doesn't do anything and sometimes makes it worse
self.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) or any other size policy seems to do nothing
TL;DR I want my widget to shrink even if there's empty space but I don't want to set a fixed size.
EDIT:
I have partially solved the problem by passing in alignment=QtCore.Qt.AlignLeft to all the self.layout.addWidget calls. It doesn't totally get rid of the problem however it may be a step in the right direction.
If you want to keep those widgets as small as possible, you can add a row and column stretch to the layout, set for a row/column index greater than the most bottom-right layout coordinate used.
In your case, you have two rows and two columns, so it's enough to set the stretch for the third row and column:
self.layout.setColumnStretch(2, 1)
self.layout.setRowStretch(2, 1)
Obviously, you can set it for a very high index, so that if you have to add more widgets you don't have to care about it.
If you want to keep those widgets in the center, just add them starting from the second row and column and set the row/column stretch for the first row/column too:
self.layout.setColumnStretch(0, 1)
self.layout.setRowStretch(0, 1)
self.layout.addWidget(self.button1, 1, 1)
self.layout.addWidget(self.button2, 1, 2)
self.layout.addWidget(self.label1, 2, 1, 1, 2)
self.layout.setColumnStretch(3, 1)
self.layout.setRowStretch(3, 1)
Note that you can also use the size policy, but you have to use Maximum, not Minimum, and you also need to add widgets with the correct alignment.
I got same issue. I resolved the issue by setting the values of minimum and maximum size to equal like both to 100
If you want to shrink the widget set minimum to 0 and set maximum value to the size you want start the display. It will not expand but shrink
From what you are describing it seems you just don't want to use a layout.
Make a widget with a layout to hold the buttons and label. Place this widget in the main window without using a layout, instead place with setGeometry.
Something like this,
from PyQt5 import QtWidgets, QtGui, QtCore
class CustomWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.resize(400, 100)
# Create a widget to hold your buttons
self.fixwidget = QtWidgets.QWidget(self)
self.layout = QtWidgets.QGridLayout()
self.button1 = QtWidgets.QPushButton("Button A")
self.button2 = QtWidgets.QPushButton("Button B")
self.label1 = QtWidgets.QLabel("Long label that can span multiple columns")
self.layout.addWidget(self.button1, 0, 0)
self.layout.addWidget(self.button2, 0, 1)
self.layout.addWidget(self.label1, 1, 0, 1, 2)
# set the layout inside the widget
self.fixwidget.setLayout(self.layout)
# place the widget with setGeometry
height = self.fixwidget.sizeHint().height()
width = self.fixwidget.sizeHint().width()
xpos, ypos = 5, 5
self.fixwidget.setGeometry(QtCore.QRect(xpos, ypos, width, height))
QtWidgets.QApplication.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
app = QtWidgets.QApplication([])
win = CustomWidget()
win.show()
status = app.exec_()
Notice that not using a layout is, in most cases, not advised as it makes managing the window harder.
I am creating a form with some QTextEdit widgets.
The default height of the QTextEdit exceeds a single line of text and as the contents' height exceeds the QTextEdit's height, it creates a scroll-bar to scroll the content.
I would like to override this behaviour to create a QTextEdit that would rather wrap its height to its contents. This means that the default height would be one line and that on wrapping or entering a new line, the QTextEdit would increase its height automatically. Whenever the contents height exceeds the QTextEdit's height, the latter should not create a scroll bar but simply increase in height.
How can I go about doing this? Thanks.
This is almost exactly like a question I answer the other day about making a QTextEdit adjust its height in reponse to content changes: PySide Qt: Auto vertical growth for TextEdit Widget
I am answering instead of marking a duplicate as I suspect its possible you want a variation on this. Let me know if you want me to expand this answer:
The other question had multiple parts. Here is the excerpt of the growing height widget:
class Window(QtGui.QDialog):
def __init__(self):
super(Window, self).__init__()
self.resize(600,400)
self.mainLayout = QtGui.QVBoxLayout(self)
self.mainLayout.setMargin(10)
self.scroll = QtGui.QScrollArea()
self.scroll.setWidgetResizable(True)
self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.mainLayout.addWidget(self.scroll)
scrollContents = QtGui.QWidget()
self.scroll.setWidget(scrollContents)
self.textLayout = QtGui.QVBoxLayout(scrollContents)
self.textLayout.setMargin(10)
for _ in xrange(5):
text = GrowingTextEdit()
text.setMinimumHeight(50)
self.textLayout.addWidget(text)
class GrowingTextEdit(QtGui.QTextEdit):
def __init__(self, *args, **kwargs):
super(GrowingTextEdit, self).__init__(*args, **kwargs)
self.document().contentsChanged.connect(self.sizeChange)
self.heightMin = 0
self.heightMax = 65000
def sizeChange(self):
docHeight = self.document().size().height()
if self.heightMin <= docHeight <= self.heightMax:
self.setMinimumHeight(docHeight)
the following code sets a QTextEdit widget to the height of the content:
# using QVBoxLayout in this example
grid = QVBoxLayout()
text_edit = QTextEdit('Some content. I make this a little bit longer as I want to see the effect on a widget with more than one line.')
# read-only
text_edit.setReadOnly(True)
# no scroll bars in this example
text_edit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
text_edit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
text_edit.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
# you can set the width to a specific value
# text_edit.setFixedWidth(400)
# this is the trick, we nee to show the widget without making it visible.
# only then the document is created and the size calculated.
# Qt.WA_DontShowOnScreen = 103, PyQt does not have this mapping?!
text_edit.setAttribute(103)
text_edit.show()
# now that we have a document we can use it's size to set the QTextEdit's size
# also we add the margins
text_edit.setFixedHeight(text_edit.document().size().height() + text_edit.contentsMargins().top()*2)
# finally we add the QTextEdit to our layout
grid.addWidget(text_edit)
I hope this helps.
I currently have a QScrollArea defined by:
self.results_grid_scrollarea = QScrollArea()
self.results_grid_widget = QWidget()
self.results_grid_layout = QGridLayout()
self.results_grid_layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
self.results_grid_widget.setLayout(self.results_grid_layout)
self.results_grid_scrollarea.setWidgetResizable(True)
self.results_grid_scrollarea.setWidget(self.results_grid_widget)
self.results_grid_scrollarea.setViewportMargins(0,20,0,0)
which sits quite happily nested within other layouts/widgets, resizes as expected, etc.
To provide headings for the grid columns, I'm using another QGridLayout positioned directly above the scroll area - this works... but looks a little odd, even when styled appropriately, especially when the on-demand (vertical) scrollbar appears or disappears as needed and the headers no longer line up correctly with the grid columns. It's an aesthetic thing I know... but I'm kinda picky ;)
Other widgets are added/removed to the self.results_grid_layout programatically elsewhere. The last line above I've just recently added as I thought it would be easy to use the created margin area, the docs for setViewportMargins state:
Sets margins around the scrolling area. This is useful for applications such as spreadsheets with "locked" rows and columns. The marginal space is is left blank; put widgets in the unused area.
But I cannot for the life of me work out how to actually achieve this, and either my GoogleFu has deserted me today, or there's little information/examples out there on how to actually achieve this.
My head is telling me I can assign just one widget, controlled by a layout (containing any number of other widgets) to the scrollarea - as I have done. If I add say a QHeaderview for example to row 0 of the gridlayout, it will just appear below the viewport's margin and scroll with the rest of the layout? Or am I missing something and just can't see the wood for the trees?
I'm just learning Python/Qt, so any help, pointers and/or examples (preferably with Python but not essential) would be appreciated!
Edit: Having followed the advice given so far (I think), I came up with the following little test program to try things out:
import sys
from PySide.QtCore import *
from PySide.QtGui import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setMinimumSize(640, 480)
self.container_widget = QWidget()
self.container_layout = QVBoxLayout()
self.container_widget.setLayout(self.container_layout)
self.setCentralWidget(self.container_widget)
self.info_label = QLabel(
"Here you can see the problem.... I hope!\n"
"Once the window is resized everything behaves itself.")
self.info_label.setWordWrap(True)
self.headings_widget = QWidget()
self.headings_layout = QGridLayout()
self.headings_widget.setLayout(self.headings_layout)
self.headings_layout.setContentsMargins(1,1,0,0)
self.heading_label1 = QLabel("Column 1")
self.heading_label1.setContentsMargins(16,0,0,0)
self.heading_label2 = QLabel("Col 2")
self.heading_label2.setAlignment(Qt.AlignCenter)
self.heading_label2.setMaximumWidth(65)
self.heading_label3 = QLabel("Column 3")
self.heading_label3.setContentsMargins(8,0,0,0)
self.headings_layout.addWidget(self.heading_label1,0,0)
self.headings_layout.addWidget(self.heading_label2,0,1)
self.headings_layout.addWidget(self.heading_label3,0,2)
self.headings_widget.setStyleSheet(
"background: green; border-bottom: 1px solid black;" )
self.grid_scrollarea = QScrollArea()
self.grid_widget = QWidget()
self.grid_layout = QGridLayout()
self.grid_layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
self.grid_widget.setLayout(self.grid_layout)
self.grid_scrollarea.setWidgetResizable(True)
self.grid_scrollarea.setWidget(self.grid_widget)
self.grid_scrollarea.setViewportMargins(0,30,0,0)
self.headings_widget.setParent(self.grid_scrollarea)
### Add some linedits to the scrollarea just to test
rows_to_add = 10
## Setting the above to a value greater than will fit in the initial
## window will cause the lineedits added below to display correctly,
## however - using the 10 above, the lineedits do not expand to fill
## the scrollarea's width until you resize the window horizontally.
## What's the best way to fix this odd initial behaviour?
for i in range(rows_to_add):
col1 = QLineEdit()
col2 = QLineEdit()
col2.setMaximumWidth(65)
col3 = QLineEdit()
row = self.grid_layout.rowCount()
self.grid_layout.addWidget(col1,row,0)
self.grid_layout.addWidget(col2,row,1)
self.grid_layout.addWidget(col3,row,2)
### Define Results group to hold the above sections
self.test_group = QGroupBox("Results")
self.test_layout = QVBoxLayout()
self.test_group.setLayout(self.test_layout)
self.test_layout.addWidget(self.info_label)
self.test_layout.addWidget(self.grid_scrollarea)
### Add everything to the main layout
self.container_layout.addWidget(self.test_group)
def resizeEvent(self, event):
scrollarea_vpsize = self.grid_scrollarea.viewport().size()
scrollarea_visible_size = self.grid_scrollarea.rect()
desired_width = scrollarea_vpsize.width()
desired_height = scrollarea_visible_size.height()
desired_height = desired_height - scrollarea_vpsize.height()
new_geom = QRect(0,0,desired_width+1,desired_height-1)
self.headings_widget.setGeometry(new_geom)
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.show()
app.exec_()
if __name__ == '__main__':
main()
Is something along these lines the method to which you were pointing? Everything works as expected as is exactly what I was after, except for some odd initial behaviour before the window is resized by the user, once it is resized everything lines up and is fine.
I'm probably over-thinking again or at least overlooking something... any thoughts?
I had a similar problem and solved it a little differently. Instead of using one QScrollArea I use two and forward a movement of the lower scroll area to the top one. What the code below does is
It creates two QScrollArea widgets in a QVBoxLayout.
It disables the visibility of the scroll bars of the top QScrollArea and assigns it a fixed height.
Using the valueChanged signal of the horizontal scroll bar of the lower QScrollArea it is possible to "forward" the horizontal scroll bar value from the lower QScrollArea to the top one resulting a fixed header at the top of the window.
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
widget = QWidget()
self.setCentralWidget(widget)
vLayout = QVBoxLayout()
widget.setLayout(vLayout)
# TOP
scrollAreaTop = QScrollArea()
scrollAreaTop.setWidgetResizable(True)
scrollAreaTop.setFixedHeight(30)
scrollAreaTop.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scrollAreaTop.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scrollAreaTop.setWidget(QLabel(" ".join([str(i) for i in range(100)])))
# BOTTOM
scrollAreaBottom = QScrollArea()
scrollAreaBottom.setWidgetResizable(True)
scrollAreaBottom.setWidget(QLabel("\n".join([" ".join([str(i) for i in range(100)]) for _ in range(10)])))
scrollAreaBottom.horizontalScrollBar().valueChanged.connect(lambda value: scrollAreaTop.horizontalScrollBar().setValue(value))
vLayout.addWidget(scrollAreaTop)
vLayout.addWidget(scrollAreaBottom)
You may be over-thinking things slightly.
All you need to do is use the geometry of the scrollarea's viewport and the current margins to calculate the geometry of any widgets you want to place in the margins.
The geometry of these widgets would also need to be updated in the resizeEvent of the scrollarea.
If you look at the source code for QTableView, I think you'll find it uses this method to manage its header-views (or something very similar).
EDIT
To deal with the minor resizing problems in your test case, I would advise you to read the Coordinates section in the docs for QRect (in particular, the third paragraph onwards).
I was able to get more accurate resizing by rewriting your test case like this:
import sys
from PySide.QtCore import *
from PySide.QtGui import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setMinimumSize(640, 480)
self.container_widget = QWidget()
self.container_layout = QVBoxLayout()
self.container_widget.setLayout(self.container_layout)
self.setCentralWidget(self.container_widget)
self.grid_scrollarea = ScrollArea(self)
self.test_group = QGroupBox("Results")
self.test_layout = QVBoxLayout()
self.test_group.setLayout(self.test_layout)
self.test_layout.addWidget(self.grid_scrollarea)
self.container_layout.addWidget(self.test_group)
class ScrollArea(QScrollArea):
def __init__(self, parent=None):
QScrollArea.__init__(self, parent)
self.grid_widget = QWidget()
self.grid_layout = QGridLayout()
self.grid_widget.setLayout(self.grid_layout)
self.setWidgetResizable(True)
self.setWidget(self.grid_widget)
# save the margin values
self.margins = QMargins(0, 30, 0, 0)
self.setViewportMargins(self.margins)
self.headings_widget = QWidget(self)
self.headings_layout = QGridLayout()
self.headings_widget.setLayout(self.headings_layout)
self.headings_layout.setContentsMargins(1,1,0,0)
self.heading_label1 = QLabel("Column 1")
self.heading_label1.setContentsMargins(16,0,0,0)
self.heading_label2 = QLabel("Col 2")
self.heading_label2.setAlignment(Qt.AlignCenter)
self.heading_label2.setMaximumWidth(65)
self.heading_label3 = QLabel("Column 3")
self.heading_label3.setContentsMargins(8,0,0,0)
self.headings_layout.addWidget(self.heading_label1,0,0)
self.headings_layout.addWidget(self.heading_label2,0,1)
self.headings_layout.addWidget(self.heading_label3,0,2)
self.headings_widget.setStyleSheet(
"background: green; border-bottom: 1px solid black;" )
rows_to_add = 10
for i in range(rows_to_add):
col1 = QLineEdit()
col2 = QLineEdit()
col2.setMaximumWidth(65)
col3 = QLineEdit()
row = self.grid_layout.rowCount()
self.grid_layout.addWidget(col1,row,0)
self.grid_layout.addWidget(col2,row,1)
self.grid_layout.addWidget(col3,row,2)
def resizeEvent(self, event):
rect = self.viewport().geometry()
self.headings_widget.setGeometry(
rect.x(), rect.y() - self.margins.top(),
rect.width() - 1, self.margins.top())
QScrollArea.resizeEvent(self, event)
if __name__ == '__main__':
app = QApplication(sys.argv)
form = MainWindow()
form.show()
sys.exit(app.exec_())