How to remove spacing inside a gridLayout (QT)? - python

I want to create a child container layout which will contains 2 widgets. Those 2 widgets should be placed right next to each other but my current setup still has some spacing in between.
I have already set the spacing to 0 setSpacing(0). And setContentsMargins(0,0,0,0) doesn't helped.
I am using PyQt5 but it shouldn't be a problem converting c++ code.
As you can see in the picture there is still a small gap:
(Left: LineEdit - Right: PushButton)
import PyQt5.QtCore as qc
import PyQt5.QtGui as qg
import PyQt5.QtWidgets as qw
import sys
class Window(qw.QWidget):
def __init__(self):
qw.QWidget.__init__(self)
self.initUI()
def initUI(self):
gridLayout = qw.QGridLayout()
height = 20
self.label1 = qw.QLabel("Input:")
self.label1.setFixedHeight(height)
gridLayout.addWidget(self.label1, 0, 0)
# Child Container
childGridLayout = qw.QGridLayout()
childGridLayout.setContentsMargins(0,0,0,0)
childGridLayout.setHorizontalSpacing(0)
self.lineEdit1 = qw.QLineEdit()
self.lineEdit1.setFixedSize(25, height)
childGridLayout.addWidget(self.lineEdit1, 0, 0)
self.pushButton1 = qw.QPushButton("T")
self.pushButton1.setFixedSize(20, height)
childGridLayout.addWidget(self.pushButton1, 0, 1)
# -----------------
gridLayout.addItem(childGridLayout, 0,1)
self.setLayout(gridLayout)
if __name__ == '__main__':
app = qw.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

The QT documentation says:
By default, QLayout uses the values provided by the style. On most platforms, the margin is 11 pixels in all directions.
Ref:http://doc.qt.io/qt-4.8/qlayout.html#setContentsMargins
So you may need to use "setHorizontalSpacing(int spacing)" for horizontal space and "setVerticalSpacing(int spacing)" for vertical.
Based on the documentation, this may delete space in your case.
Ref:http://doc.qt.io/qt-4.8/qgridlayout.html#horizontalSpacing-prop
If not resolved, there is an option to override style settings for space (from where the layout gets).... I think this is tedious
If you want to provide custom layout spacings in a QStyle subclass, implement a slot called layoutSpacingImplementation() in your subclass.
More detials:
http://doc.qt.io/qt-4.8/qstyle.html#layoutSpacingImplementation

Related

What is the difference between QLayout's setAlignment(QWidget, Alignment), setAlginment(QLayout, Alignment) and setAlginment(Alignment)? (PyQt5)

In the following code:
import sys
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QTableWidget, QPushButton
class Window(QtWidgets.QMainWindow):
def __init__(self, method):
super(Window, self).__init__()
mainWidget = QWidget()
mainLayout = QHBoxLayout(mainWidget)
table = QTableWidget(10, 3)
button1 = QPushButton("Play")
button2 = QPushButton("Cancel")
mainLayout.addWidget(table)
mainLayout.addWidget(button1)
mainLayout.addWidget(button2)
if (method == 1):
rtnValue = mainLayout.setAlignment(button1, Qt.AlignTop)
print("Method 1:", rtnValue)
elif (method == 2):
rtnValue = mainLayout.setAlignment(mainLayout, Qt.AlignTop)
print("Method 2:", rtnValue)
else:
rtnValue = mainLayout.setAlignment(Qt.AlignTop)
print("Method X:", rtnValue)
self.setCentralWidget(mainWidget)
self.show()
if __name__ == "__main__":
print("python QLayoutAlignment.py[ <MethodToUse=1>")
app = QApplication(sys.argv)
method = 1 if (len(sys.argv) < 2) else int(sys.argv[1])
GUI = Window(method)
sys.exit(app.exec_())
when I call the program like below with mainLayout.setAlignment(button1, Qt.AlignTop) being called, it works as expected with the "Play" button aligned at the top and "Cancel" button aligned at the center vertically. I also found the documentation for bool QLayout::setAlignment(QWidget *w, Qt::Alignment alignment) although in Qt.
python QLayoutAlignment.py 1
However when I call the the program like below with mainLayout.setAlignment(mainLayout, Qt.AlignTop) being called, it does not seem to work. All the buttons are vertically center aligned. I interpreted the Qt documentation of bool QLayout::setAlignment(QLayout *l, Qt::Alignment alignment)) as "it align all the added widget of the layout to the set alignment". So what does this function actually do (when is it used)?
python QLayoutAlignment.py 2
Lastly, I also saw another example from Center and top align a QVBoxLayout. When I call the program like below with mainLayout.setAlignment(Qt.AlignTop) being called, it also does not work with all the buttons vertically center aligned. For this one I could not find its documentation. So what does this function actually do (when is it used) and where can I find its documentation?
python QLayoutAlignment.py 3
The .setAlignment method which accepts a layout is used for aligning sub-layouts, that is child layouts you've added to the parent layout using .addLayout.
Below is a little demo based on your code.
import sys
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QTableWidget, QPushButton
class Window(QtWidgets.QMainWindow):
def __init__(self, method=0):
super(Window, self).__init__()
mainWidget = QWidget()
self.mainLayout = QHBoxLayout(mainWidget)
table = QTableWidget(10, 3)
self.button1 = QPushButton("Play")
self.button2 = QPushButton("Cancel")
self.subLayout = QHBoxLayout()
buttona1 = QPushButton("1")
buttona1.clicked.connect(self.clicked1)
buttona2 = QPushButton("2")
buttona2.clicked.connect(self.clicked2)
buttona3 = QPushButton("3")
buttona3.clicked.connect(self.clicked3)
buttona4 = QPushButton("4")
buttona4.clicked.connect(self.clicked4)
self.subLayout.addWidget(buttona1)
self.subLayout.addWidget(buttona2)
self.subLayout.addWidget(buttona3)
self.subLayout.addWidget(buttona4)
self.mainLayout.addWidget(table)
self.mainLayout.addWidget(self.button1)
self.mainLayout.addWidget(self.button2)
self.mainLayout.addLayout(self.subLayout)
self.setCentralWidget(mainWidget)
self.show()
def clicked1(self):
rtnValue = self.mainLayout.setAlignment(self.button1, Qt.AlignTop)
print("Method 1:", rtnValue)
def clicked2(self):
rtnValue = self.mainLayout.setAlignment(self.mainLayout, Qt.AlignTop)
print("Method 2:", rtnValue)
def clicked3(self):
rtnValue = self.mainLayout.setAlignment(Qt.AlignTop)
print("Method 3:", rtnValue)
def clicked4(self):
rtnValue = self.mainLayout.setAlignment(self.subLayout, Qt.AlignTop)
print("Method 4:", rtnValue)
if __name__ == "__main__":
print("python QLayoutAlignment.py[ <MethodToUse=1>")
app = QApplication(sys.argv)
method = 1 if (len(sys.argv) < 2) else int(sys.argv[1])
GUI = Window(method)
sys.exit(app.exec_())
You'll notice if you trigger this self.mainLayout.setAlignment(self.mainLayout, Qt.AlignTop) the return value is False. This is telling you that the layout you're aligning could not be found in the current layout. Since you're calling .setAlignment on mainLayout the layout you're affecting must be in that layout.
In the 4th method, I've added a sub-layout, and as you can see this ( rtnValue = self.mainLayout.setAlignment(self.subLayout, Qt.AlignTop)) works as expected and returns True.
First of all, it's important to understand that the Qt layout system works by using layout items (see QLayoutItem), which are abstract items that are used as virtual containers for objects: widgets, spacers and layouts (when nested layouts are used). Every QLayout is, in fact, a subclass of QLayoutItem.
Using setAlignment means setting the alignment of the specified layout item:
layout.setAlignment(item, alignment) sets the alignment of item, which has to be directly contained in layout;
layout.setAlignment(alignment) sets the alignment of layout related to its parent layout; note that this does not mean that items inside layout will use the specified alignment;
Your second case, mainLayout.setAlignment(mainLayout, Qt.AlignTop), does not work and returns False because mainLayout is, obviously, not "contained" in mainLayout. In fact, if you carefully read the documentation, it says:
returns true if w/l is found in this layout (not including child layouts);
In your third case, you don't see any result because mainLayout is the top layout for the widget, and since there's no parent layout the alignment seems to be ignored. As specified above, using layout.setAlignment(alignment) does not set the alignment of child items, but only of the layout item of layout. If you add that mainLayout to another layout, you will see that the alignment is respected for the layout.
Setting the layout alignment is rarely used, also because it's often counterintuitive: one might led to believe that setting the alignment of a layout will align its contents, but that's not the case.
To clarify, consider that setting the layout alignment is almost the same as creating a widget with that layout, and adding that widget with the specified alignment. With that in mind, it makes more sense: you're not aligning the contents of the widget, but the widget itself.
Consider the following example: besides the table on the left (used for comparison), I'm adding a layout on the left by specifying its alignment, then I'm adding a widget on the right by specifying the alignment of the widget for the main layout. As you can see, they appear exactly the same.
from PyQt5 import QtCore, QtWidgets
import sys
app = QtWidgets.QApplication(sys.argv)
test = QtWidgets.QWidget()
mainLayout = QtWidgets.QHBoxLayout(test)
# a very tall table to show the difference in alignment
mainLayout.addWidget(QtWidgets.QTableView(minimumHeight=300))
leftLayout = QtWidgets.QHBoxLayout()
# setting the alignment of leftLayout relative to mainLayout
leftLayout.setAlignment(QtCore.Qt.AlignTop)
leftLayout.addWidget(QtWidgets.QTableView(maximumHeight=100))
leftLayout.addWidget(QtWidgets.QPushButton())
mainLayout.addLayout(leftLayout)
rightWidget = QtWidgets.QWidget()
# adding the widget to mainLayout by aligning it on top as with leftLayout
mainLayout.addWidget(rightWidget, alignment=QtCore.Qt.AlignTop)
rightLayout = QtWidgets.QHBoxLayout(rightWidget)
rightLayout.addWidget(QtWidgets.QTableView(maximumHeight=100))
rightLayout.addWidget(QtWidgets.QPushButton())
test.show()
sys.exit(app.exec_())
Finally, if you want to align widgets, you either specify the alignment for each widget, or you add nested layout.
When many widgets are going to be added with the same alignment, the nested layout is usually the best solution: in your case, add a vertical layout to the main layout, then add horizontal layout to the vertical for the buttons, and add a stretch to the vertical to "push" the horizontal layout on top.
Alternatively, you can use a grid layout and eventually use spacers to ensure that the widgets are "aligned" as required.
from PyQt5 import QtCore, QtWidgets
import sys
app = QtWidgets.QApplication(sys.argv)
test = QtWidgets.QWidget()
mainLayout = QtWidgets.QHBoxLayout(test)
class Button(QtWidgets.QPushButton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setSizePolicy(
QtWidgets.QSizePolicy.Preferred,
QtWidgets.QSizePolicy.Preferred
)
noAlignGroup = QtWidgets.QGroupBox('no alignment')
mainLayout.addWidget(noAlignGroup)
noAlignLayout = QtWidgets.QHBoxLayout(noAlignGroup)
noAlignLayout.addWidget(QtWidgets.QTableView())
noAlignLayout.addWidget(Button())
noAlignLayout.addWidget(Button())
widgetAlignGroup = QtWidgets.QGroupBox('addWidget(widget, alignment)')
mainLayout.addWidget(widgetAlignGroup)
widgetAlignLayout = QtWidgets.QHBoxLayout(widgetAlignGroup)
widgetAlignLayout.addWidget(QtWidgets.QTableView())
widgetAlignLayout.addWidget(Button(), alignment=QtCore.Qt.AlignTop)
widgetAlignLayout.addWidget(Button(), alignment=QtCore.Qt.AlignTop)
layoutAlignGroup = QtWidgets.QGroupBox('nestedLayout.setAlignment()')
mainLayout.addWidget(layoutAlignGroup)
layoutAlignLayout = QtWidgets.QHBoxLayout(layoutAlignGroup)
layoutAlignLayout.addWidget(QtWidgets.QTableView())
buttonLayout = QtWidgets.QHBoxLayout()
layoutAlignLayout.addLayout(buttonLayout)
buttonLayout.setAlignment(QtCore.Qt.AlignTop)
buttonLayout.addWidget(Button())
buttonLayout.addWidget(Button())
stretchGroup = QtWidgets.QGroupBox('nestedLayout + stretch')
mainLayout.addWidget(stretchGroup)
stretchLayout = QtWidgets.QHBoxLayout(stretchGroup)
stretchLayout.addWidget(QtWidgets.QTableView())
rightLayout = QtWidgets.QVBoxLayout()
stretchLayout.addLayout(rightLayout)
buttonLayout = QtWidgets.QHBoxLayout()
rightLayout.addLayout(buttonLayout)
buttonLayout.addWidget(Button())
buttonLayout.addWidget(Button())
rightLayout.addStretch()
gridAlignGroup = QtWidgets.QGroupBox('grid + spacer')
mainLayout.addWidget(gridAlignGroup)
gridLayout = QtWidgets.QGridLayout(gridAlignGroup)
gridLayout.addWidget(QtWidgets.QTableView(), 0, 0, 2, 1)
gridLayout.addWidget(Button(), 0, 1)
gridLayout.addWidget(Button(), 0, 2)
spacer = QtWidgets.QSpacerItem(1, 50, vPolicy=QtWidgets.QSizePolicy.Expanding)
gridLayout.addItem(spacer, 1, 1)
test.show()
sys.exit(app.exec_())

Resizing QSplitter doesn't respect QSizePolicy or setStretchFactor?

I set the size policy of items in a QSplitter but it doesn't seem to affect anything. When I resize the window in the example below I want my left widget to stop growing once it reaches its size hint (QSizePolicy.Preferred) and the right widget to expand as big as possible (QSizePolicy.Expanding).
It works if I put them in a layout:
from PyQt5 import QtWidgets, QtCore
app = QtWidgets.QApplication([])
class ColoredBox(QtWidgets.QFrame):
def __init__(self, color):
super().__init__()
self.setStyleSheet(f'background-color: {color}')
def sizeHint(self) -> QtCore.QSize:
return QtCore.QSize(200, 200)
box1 = ColoredBox('red')
box2 = ColoredBox('green')
splitter = QtWidgets.QHBoxLayout()
splitter.setContentsMargins(0, 0, 0, 0)
splitter.setSpacing(0)
splitter.addWidget(box1)
splitter.addWidget(box2)
box1.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
box2.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
container = QtWidgets.QWidget()
container.setLayout(splitter)
container.show()
app.exec_()
But it doesn't work if I put them in a QSplitter, the splitter just splits space between them evenly:
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import Qt
app = QtWidgets.QApplication([])
class ColoredBox(QtWidgets.QFrame):
def __init__(self, color):
super().__init__()
self.setStyleSheet(f'background-color: {color}')
def sizeHint(self) -> QtCore.QSize:
return QtCore.QSize(200, 200)
box1 = ColoredBox('red')
box2 = ColoredBox('green')
splitter = QtWidgets.QSplitter(Qt.Horizontal)
splitter.setStretchFactor(0, 0)
splitter.setStretchFactor(1, 1)
splitter.addWidget(box1)
splitter.addWidget(box2)
box1.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
box2.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
container = QtWidgets.QMainWindow()
container.setCentralWidget(splitter)
container.show()
app.exec_()
Layout vs Splitter:
The problem comes from the fact that you're setting the stretch factor too early.
setStretchFactor() is ignored if done on a widget index that doesn't exist yet (see the source code for its implementation);
QSplitter resizes the widget proportions based on their size policy; the size policy also includes the horizontal and vertical stretch factors, and they're reset to the 0 default value whenever set again with the basic setSizePolicy(horizontalPolicy, verticalPolicy) (which means that you can specify the stretch in the QSizePolicy, which is the same as using setStretchFactor).
The solution is simple: move the setStretchFactor lines after adding the widgets and setting their policies.
Obviously, a possible alternative is to set the maximum dimension (based on the QSplitter orientation), but that is not exactly the same thing.
If all widgets in the splitter have a maximum size (including situations for which only one widget exists), the result is that the splitter will have a maximum size based on those widgets, depending on other widgets in (or "above") its layout, but if the splitter is the top level window then there will be some blank space remaining whenever it's resized to a size bigger than the maximum of its child[ren].

Using QScrollArea collapses children widgets

I am trying to create a dynamic GUI with multiple Groupbox objects in a QVBoxLayout. As there are a lot of them, I will be needing a scroll area to make them available to the end user.
So I tried to change to top widget of this tab from a QWidget to a QScrollArea.
Before the change:
This is the kind of result I want but with a scroll bar because the window is too high.
After the change to QScrollArea:
My GroupBoxs are now "collapsed" and there is not scrollbar. I tried setting their size but it is not adequate because they are not fixed. I searched the documentation and tried to use WidgetResizable or I tried to set a fixed height or the sizehint but nothing worked as I wanted.
After creating the the Groupbox, the sizeHint for my QScrollArea is already very low (around 150px of height) so I think I'm missing a parameter.
It would be complicated to provide code as it is intricate. If necessary I could recreate the problem in a simpler way.
How to reproduce:
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtWidgets import *
import sys
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
v_layout = QVBoxLayout()
scroll_area = QScrollArea()
self.layout().addWidget(scroll_area)
scroll_area.setLayout(v_layout)
# v_layout.setSizeConstraint(QLayout.SetMinimumSize)
for i in range(50):
box = QGroupBox()
grid = QGridLayout()
box.setLayout(grid)
grid.addWidget(QLabel("totototo"), 0, 0)
grid.addWidget(QLineEdit(), 1, 0)
grid.addWidget(QPushButton(), 2, 0)
v_layout.addWidget(box)
self.show()
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Uncommenting # v_layout.setSizeConstraint(QLayout.SetMinimumSize) allows the content of the group boxes to deploy and fixes the first part of the issue. But there is still not scroll bar.
You have 2 errors:
A widget should not be added to the layout of a QMainWindow, but the setCentralWidget method should be used.
You should not add the layout to the QScrollArea but use a widget as a container for the other widgets, also if you use layouts then you have to activate the widgetResizable property.
Considering the above, the solution is:
def initUI(self):
scroll_area = QScrollArea(widgetResizable=True)
self.setCentralWidget(scroll_area)
container = QWidget()
scroll_area.setWidget(container)
v_layout = QVBoxLayout(container)
for i in range(50):
box = QGroupBox()
grid = QGridLayout()
box.setLayout(grid)
grid.addWidget(QLabel("totototo"), 0, 0)
grid.addWidget(QLineEdit(), 1, 0)
grid.addWidget(QPushButton(), 2, 0)
v_layout.addWidget(box)
self.show()

Setting initial size of QTabWidget in QSplitter PyQt application

I have a vertical splitter with a QTabWidget at the top and a QPlainTextEdit widget below (used as a logging window). In the real application, the tabs are filled with QWidgets, containing a matplotlib canvas and a QFrame with some control elements:
QSplitter
QPlainTextEdit
QVBoxLayout
QTabWidget
QWidget
QVBoxLayout
FigureCanvas (QSizePolicy.Expanding, QSizePolicy.Expanding)
QFrame (optional)
I would like the application to start with a nice vertical ratio of say 4:1 between the tabs and the logging window. However, using mysplitter.setStretchFactor(4,1) doesn't work here as the sizeHint() of the QTabWidget only is (4,4), causing the QPlainTextEdit with sizeHint() = (256,192) to gobble up nearly all available vertical space. As a workaround, I'm currently setting a fixed height for the QPlainTextWidget but I know that this widget is not the culprit.
I guess I need to fiddle around with sizePolicies or with the layout / sizes of the individual tabs but so far I haven't been successful. I've attached a MWE, the full code is available at https://github.com/chipmuenk/pyFDA/blob/master/pyfda/pyfdax.py :
# -*- coding: utf-8 -*-
from __future__ import print_function
from PyQt5.QtWidgets import (QWidget, QTabWidget, QPlainTextEdit, QSplitter,
QMainWindow, QVBoxLayout, QApplication)
from PyQt5.QtGui import QFontMetrics
from PyQt5 import QtCore
#------------------------------------------------------------------------------
class TabWidgets(QTabWidget):
def __init__(self, parent):
super(TabWidgets, self).__init__(parent)
self.wdg1 = QWidget(self)
self.wdg2 = QWidget(self)
self._construct_UI()
#------------------------------------------------------------------------------
def _construct_UI(self):
""" Initialize UI with tabbed subplots """
self.tabWidget = QTabWidget(self)
self.tabWidget.addTab(self.wdg1, 'Wdg 1')
self.tabWidget.addTab(self.wdg2, 'Wdg 2')
layVMain = QVBoxLayout()
layVMain.addWidget(self.tabWidget)
self.setLayout(layVMain)
# When user has switched the tab, call self.current_tab_redraw
self.tabWidget.currentChanged.connect(self.current_tab_redraw)
#------------------------------------------------------------------------------
def current_tab_redraw(self):
pass
#self.tabWidget.currentWidget().resize()
class MWin(QMainWindow):
"""
Main window consisting of a tabbed widget and a status window.
QMainWindow is used as it understands GUI elements like central widget
"""
def __init__(self, parent=None):
super(QMainWindow,self).__init__()
#---------------------------------------------------------------
statusWin = QPlainTextEdit(self) # status window
tabWin = TabWidgets(self) # tabbed window
print('size status win: {0}'.format(statusWin.sizeHint()))
print('size_tab win: {0}'.format(tabWin.sizeHint()))
mSize = QFontMetrics(statusWin.font())
rowHt = mSize.lineSpacing()
# fixed height for statusWin needed as the sizeHint of tabWin is very small
statusWin.setFixedHeight(4*rowHt+4)
# add status window underneath plot Tab Widgets:
spltVMain = QSplitter(QtCore.Qt.Vertical)
spltVMain.addWidget(tabWin)
spltVMain.addWidget(statusWin)
# relative initial sizes of subwidgets, this doesn't work here
spltVMain.setStretchFactor(4,1)
spltVMain.setFocus()
# make spltVMain occupy the main area of QMainWindow and set inheritance
self.setCentralWidget(spltVMain)
#----------------------------------------------------------------------------
def main():
import sys
app = QApplication(sys.argv)
mainw = MWin(None)
mainw.resize(300,400)
app.setActiveWindow(mainw)
mainw.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I've found an easy workaround: Setting the splitter in absolute units instead of a ratio does the job. Stating with the total height of the splitter widget, makes the solution work with different resolutions etc. The code snippet below shows the updated __init__() part:
def __init__(self, parent=None):
super(QMainWindow,self).__init__()
#---------------------------------------------------------------
statusWin = QPlainTextEdit(self) # status window
tabWin = TabWidgets(self) # tabbed window
print('size status win: {0}'.format(statusWin.sizeHint()))
print('size_tab win: {0}'.format(tabWin.sizeHint()))
# fixed height for statusWin no longer needed here
# mSize = QFontMetrics(statusWin.font())
# rowHt = mSize.lineSpacing()
# statusWin.setFixedHeight(4*rowHt+4)
# add status window underneath plot Tab Widgets:
spltVMain = QSplitter(QtCore.Qt.Vertical)
spltVMain.addWidget(tabWin)
spltVMain.addWidget(statusWin)
# relative initial sizes of subwidgets, this doesn't work here
# spltVMain.setStretchFactor(4,1)
# Use absolute values instead:
spltVMain.setSizes([spltVMain.size().height() * 0.8,
spltVMain.size().height() * 0.2])
spltVMain.setFocus()
# make spltVMain occupy the main area of QMainWindow and set inheritance
self.setCentralWidget(spltVMain)
#-----------------------------------------------------------------------

PyQt4 - Ambiguity in QLayout Spacing

I have a QHBoxLayout which I add widgets to, one by one.
I alternate by adding a Custom Widget, than a QLabel (repeating).
The QHBoxLayout is owned by a QGroupBox.
However, I notice that the spacing between the Custom Widget and the QLabel is 'irregular' when there are few widgets added to the Layout.
I intend for the QLabels to be centered in the gap between the custom widgets, but the QLabels only approach the centre as more widgets are added.
These screenshots show the two cases.
The desired case (only occuring with lots of widgets in the layout),
and the undesired case (occuring when little widgets are in the layout).
As you can see in the bottom case, the QLabels are not centered between the Custom Widgets. Instead, they are very far to the right!
What is causing this behaviour??
I believe the QGroupBox has a centered (horizontal) alignment and the QLabels have a fixed width of 10 (or 20 for the '->' QLabel) pixels (to avoid ugly overlap).
Any help at all would be greatly appreciated!
Thanks!
Specs:
python 2.7.1
PyQt4
Windows 7
The instantiation of the QHBoxLayout is absolutely normal and similiar to all examples.
Here is the code for the filling of the layout.
for i in range (0,len(Reactants)):
self.WidgetHouse.Reaction_Element_Layout.addWidget(eval('self.OverallContainer_Reactants.Reactant_'+str(i)))
# self.WidgetHouse.Reaction_Element_Layout is the QHBoxLayout
# self.OverallContainer_Reactants.Reactant_'+str(i) is a Custom Widget
if i != (len(Reactants)-1):
tmp = QtGui.QLabel('+')
tmp.setFixedWidth(10)
tmp.setAlignment(QtCore.Qt.AlignCenter)
self.WidgetHouse.Reaction_Element_Layout.addWidget(tmp)
else:
tmp = QtGui.QLabel('->')
tmp.setFixedWidth(20)
tmp.setAlignment(QtCore.Qt.AlignCenter)
self.WidgetHouse.Reaction_Element_Layout.addWidget(tmp)
EDIT:
Setting a fixed width of the QLabels (tmp.setFixedWidth(10)) is the source of the 'right anchoring'.
However, not setting a fixed width results in the same space being dedicated to QLabels and Custom Widgets in the layout, leading to an overlap of QLabels and Custom Widgets.
Identical to the code above, discluding 'tmp.setFixedWidt(10)'
What can I do to prevent this that's not completely horrible?
Can I move the Labels 'back' from the front?
(Calling .raise_() on all the custom widgets after everything was added to the layout did not work)
(Or will I have to do something like manually calculate the appropriate width of the labels based off the amount of widgets in the layout? Yuck!)
ANOTHER EDIT:
Progress:
I do not change the maximum/minimum (or fixed) width of the QLabels, but I do set their alignment to center.
Instead, I set a minimum width of the custom widget.
This fixes the apparent 'overlapping' (which wasn't really the case) and makes the Labels appear 'more centered'.
However, as you can see, the QLabels still aren't perfectly centered - too far right.
If I don't set a center alignment on the QLabels they are too far left.
What could be the problem now??
(I do not set a maximum width on the labels)
Thanks for all the help so far guys!
Here is a simple example script which is a reasonable approximation of the UI in the question, but without any of the layout issues:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.groupBox = QtGui.QGroupBox(self)
hbox = QtGui.QHBoxLayout(self.groupBox)
length = 3
for index in range(length):
hbox.addWidget(Widget(u'H\u2082O', self))
if index < length - 1:
hbox.addWidget(Label(u'+', self))
else:
hbox.addWidget(Label(u'\u2192', self))
hbox.addWidget(Widget(u'4 H\u2082O', self))
hbox.addWidget(Label(u'+', self))
hbox.addWidget(Widget(u'H\u2084O\u2082', self))
vbox = QtGui.QVBoxLayout(self)
vbox.addWidget(self.groupBox)
vbox.addStretch()
class Label(QtGui.QLabel):
def __init__(self, label, parent=None):
QtGui.QLabel.__init__(self, label, parent)
self.setAlignment(QtCore.Qt.AlignCenter)
class Widget(QtGui.QWidget):
def __init__(self, label, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setMaximumWidth(100)
layout = QtGui.QGridLayout(self)
self.label = QtGui.QLabel(label, self)
self.label.setAlignment(QtCore.Qt.AlignCenter)
layout.addWidget(self.label, 0, 0, 1, 2)
self.lineEdit = QtGui.QLineEdit(self)
layout.addWidget(self.lineEdit, 1, 0, 1, 2)
self.toolButton = QtGui.QToolButton(self)
layout.addWidget(self.toolButton, 2, 0, 1, 1)
self.comboBox = QtGui.QComboBox(self)
layout.addWidget(self.comboBox, 2, 1, 1, 1)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Solution to all problems:
Give the custom widgets a fixed width.
Results in perfectly centered, not 'overlapping' widgets.
:)
Thanks for all the help guys!

Categories