Related
I want two Widgets/Layouts be next to each other (using QHBoxLayout) but they should have the same size (half the size of the window).
It should look like this:
but I want the button to be smaller (the button is contained in another Layout) while the list widget remains the same size.
I tried using minimumSize, sizeHint, stretch, etc. but it never worked.
My code:
import sys
from PyQt5 import QtWidgets as qtw
class SelectView(qtw.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.width = 750
self.height = 500
self.setGeometry(200, 200, self.width, self.height)
self.setWindowTitle("SelectView")
self.list_widget = qtw.QListWidget()
self.list_widget.setGeometry(0, 0, self.width//2, self.height)
for i in range(100):
self.list_widget.addItem(qtw.QListWidgetItem(f"something{i}"))
left_layout = qtw.QVBoxLayout()
btn = qtw.QPushButton("button")
# I'm using this to show you that I want the widgets/layout
# to be half the size of the window
btn.setFixedWidth(375)
left_layout.addWidget(btn)
h_layout = qtw.QHBoxLayout()
h_layout.addWidget(self.list_widget)
h_layout.addLayout(left_layout)
self.setLayout(h_layout)
self.show()
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = SelectView()
sys.exit(app.exec_())
edit:
self.list_widget.setMinimumSize(self.width//2, self.height)
would do the job if the window had a fixed size.
You have to use the stretch factor:
h_layout.addWidget(self.list_widget, stretch=1)
h_layout.addLayout(left_layout, stretch=1)
Note: Remove btn.setFixedWidth(...)
SInce you have specified the size of the window, no need for the fixed width sizes you defined for the widgets. Instead do something like this:
layout = QHBoxLayout()
layout.addWidget(widget1)
layout.addWidget(widget2)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
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)
# ...
So I'm trying to get a grip on Qt (more specifically, Pyqt), and I want to create a simple feedback form. It should have
a title
a name ('author')
a message
a send and a cancel button
Let's try without the buttons, first (the App class just provides a button to create a popup. the question concerns the Form class below it):
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QDesktopWidget,\
QHBoxLayout, QVBoxLayout, QGridLayout,\
QPushButton, QLabel,QLineEdit, QTextEdit,\
qApp
from PyQt5.QtGui import QIcon
class App(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'PyQt5 Layout Demo'
self.popup = None
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setWindowIcon(QIcon('imgs/python3.png'))
formButton = QPushButton("show form")
formButton.clicked.connect(self.showPopup)
formBox = QHBoxLayout()
formBox.addWidget(formButton)
formBox.addStretch(1)
vbox = QVBoxLayout()
vbox.addLayout(formBox)
vbox.addStretch(1)
# self.setLayout(vbox) # would work if this was a QWidget
# instead, define new central widget
window = QWidget()
window.setLayout(vbox)
self.setCentralWidget(window)
self.center(self)
self.show()
#staticmethod
def center(w: QWidget):
qr = w.frameGeometry() # get a rectangle for the entire window
# center point = center of screen resolution
cp = QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp) # move center of rectangle to cp
w.move(qr.topLeft()) # move top-left point of window to top-let point of rectangle
def showPopup(self):
if self.popup is None:
self.popup = Form(self)
self.popup.setGeometry(10, 10, 300, 400)
self.center(self.popup)
self.popup.show()
class Form(QWidget):
def __init__(self, main):
super().__init__()
self.initUI()
self.main = main
def initUI(self):
self.setWindowTitle('Feedback')
self.setWindowIcon(QIcon('imgs/python3.png'))
title = QLabel('Title')
author = QLabel('Author')
message = QLabel('Message')
titleEdit = QLineEdit()
authorEdit = QLineEdit()
messageEdit = QTextEdit()
grid = QGridLayout()
grid.setSpacing(10)
grid.addWidget(title, 1, 0)
grid.addWidget(titleEdit,1, 1)
grid.addWidget(author, 2, 0)
grid.addWidget(authorEdit,2, 1)
grid.addWidget(message, 3, 0)
grid.addWidget(messageEdit, 4, 0, 6, 0)
self.setLayout(grid)
# probably should delegate to self.main, but bear with me
def send(self):
self.main.popup = None
self.hide()
def cancel(self):
self.hide()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
Ok, looks about right. There's a bit too much spacing in-between the line edits and the text edit, but since I want to add some buttons below it, that should be be a problem.
So I add:
sendBtn = QPushButton("send")
cancelBtn = QPushButton("cancel")
sendBtn.clicked.connect(self.send)
cancelBtn.clicked.connect(self.cancel)
grid.addWidget(sendBtn, 7, 1)
grid.addWidget(cancelBtn, 7, 2)
which yields
Now obviously, I forgot to stretch the title and author line edits to the newly introduced column 2. Easy enough to fix but what really bothers me is the placement of the buttons.
WHY do they show up in the middle of the text edit? I can see how Qt chooses the column size, and why that would lead to the buttons' being of different size, but since the tutorial doesn't actually add buttons to the form, I have no idea how to fix that.
I could, of course, simply add boxes:
sendBtn = QPushButton("send")
cancelBtn = QPushButton("cancel")
sendBtn.clicked.connect(self.send)
cancelBtn.clicked.connect(self.cancel)
btns = QHBoxLayout()
btns.addStretch(1)
btns.addWidget(sendBtn)
btns.addWidget(cancelBtn)
l = QVBoxLayout()
l.addLayout(grid)
l.addLayout(btns)
self.setLayout(l)
With which the popup then actually starts looking closer to something acceptable:
But is there a way to fix this within the grid layout, instead?
You seem to have misunderstood the signature of addWidget. The second and third arguments specify the row and column that the widget is placed in, whilst the third and fourth specify the row-span and column-span.
In your example, the problems start here:
grid.addWidget(message, 3, 0)
grid.addWidget(messageEdit, 4, 0, 6, 0)
where you make the text-edit span six rows and zero columns - which I doubt is what you intended. Instead, you probably want this:
grid.addWidget(message, 3, 0, 1, 2)
grid.addWidget(messageEdit, 4, 0, 1, 2)
which will make the message label and text-edit span the two columns created by the title and author fields above.
Now when you add the buttons, they must have a layout of their own, since the top two rows are already determining the width of the two columns. If you added the buttons directly to the grid, they would be forced to have the same widths as the widgets in the top two rows (or vice versa). So the buttons should be added like this:
hbox = QHBoxLayout()
sendBtn = QPushButton("send")
cancelBtn = QPushButton("cancel")
sendBtn.clicked.connect(self.send)
cancelBtn.clicked.connect(self.cancel)
hbox.addStretch()
hbox.addWidget(sendBtn)
hbox.addWidget(cancelBtn)
grid.addLayout(hbox, 5, 0, 1, 2)
I'm trying to create a rich-text editor with a layout similar to Microsoft Word's 'Page View' or 'Print Layout.' I'd like to have a QTextEdit horizontally centered in the main window, with the scroll-bar aligned against the far right edge of the main window.
I couldn't find a way to move a QTextEdit's default scroll-bar independent of the QTextEdit itself. Instead, I tried creating a separate scroll-bar, and making the QTextEdit grow vertically using the solution found here: A QWidget like QTextEdit that wraps its height automatically to its contents?
Here is my attempt:
import sys
from PySide import QtGui, QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.initUI()
def initUI(self):
cw = CentralWidget()
self.setCentralWidget(cw)
self.setGeometry(200, 200, 1000, 600)
self.show()
def resizeEvent(self, event):
self.centralWidget().setFixedHeight(event.size().height())
class CentralWidget(QtGui.QWidget):
def __init__(self):
super(CentralWidget, self).__init__()
self.initUI()
def initUI(self):
text = MainTextEdit()
text.setMinimumWidth(850)
text.setStyleSheet('border: 0;')
pageWidget = QtGui.QWidget()
scroll = QtGui.QScrollArea()
scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
scroll.setMaximumWidth(18)
# If you change setWidgetResizeable to False,
# the textedit will center, but scrolling will not work.
scroll.setWidgetResizable(True)
scroll.setWidget(pageWidget)
hbox = QtGui.QHBoxLayout()
hbox.setContentsMargins(0,0,0,0)
hbox.addStretch(0.5)
hbox.addWidget(text)
hbox.addStretch(0.5)
pageWidget.setLayout(hbox)
hbox2 = QtGui.QHBoxLayout()
hbox2.setContentsMargins(0,0,0,0)
hbox2.addWidget(pageWidget)
hbox2.addWidget(scroll)
self.setLayout(hbox2)
class MainTextEdit(QtGui.QTextEdit):
def __init__(self, *args, **kwargs):
super(MainTextEdit, self).__init__(*args, **kwargs)
self.document().contentsChanged.connect(self.sizeChange)
self.setFontPointSize(80)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
def sizeChange(self):
docHeight = self.document().size().height()
self.setMinimumHeight(docHeight)
def main():
app = QtGui.QApplication(sys.argv)
mw = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
There are at least two problems with this:
Problem #1
As is, the code above does not horizontally center the QTextEdit in the main window, but the scroll bar at the far-right does work. If you change scroll.setWidgetResizable(True) to scroll.setWidgetResizable(False) on line 41, the QTextEdit will center horizontally, but the scroll-bar does not work. It seems you can get one feature or the other, but not both.
Problem #2
In order to keep the MainWindow from auto-expanding when the QTextEdit grows, the MainWindow assigns a fixed height to the CentralWidget whenever the MainWindow is resized (see line 19 of the code above). This works well until the user tries to vertically shrink the main window. The window can be vertically expanded by clicking and dragging the bottom border, but it can't be vertically shrunk.
Conclusion
Maybe this is the wrong approach all-together. Any suggestions?
Set a symmetrical margin via setViewportMargins on the QTextEdit which inherits from QAbstractScrollArea.
Example:
from PySide import QtGui, QtCore
app = QtGui.QApplication([])
window = QtGui.QWidget()
layout = QtGui.QVBoxLayout(window)
edit = QtGui.QTextEdit('jfdh afdhgfkjg fdnvfh vklkfjvkflj lddkl ljklfjkl jvkldjfkvljfgvjldf ll dl dljvklj ljljlbl llkb jbgl')
edit.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
edit.setViewportMargins(30, 0, 30, 30)
layout.addWidget(edit)
window.show()
app.exec_()
Gives:
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_())