How exactly does addStretch work in QBoxLayout? - python

I'm doing a PyQt4 tutorial about box layouts. But I dont understand how addStretch works.
If i use vbox.addStretch(1) and hbox.addStretch(1), the two buttons appear down-right. Why?
if i comment vbox.addStretch(1) and hbox.addStretch(1) out, the two buttons appear in the center of my window, and they're deformable horizontally, but not vertically. Why?
theres no difference if I change the value "1"... so what does the value do?
Below is the code I'm using:
import sys
from PyQt4 import QtGui
class BoxLayout(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setWindowTitle('box layout')
ok = QtGui.QPushButton("OK")
cancel = QtGui.QPushButton("Cancel")
vbox = QtGui.QHBoxLayout()
vbox.addStretch(1)
vbox.addWidget(ok)
vbox.addWidget(cancel)
hbox = QtGui.QVBoxLayout()
hbox.addStretch(1)
hbox.addLayout(vbox)
self.setLayout(hbox)
self.resize(100, 100)
app = QtGui.QApplication(sys.argv)
qb = BoxLayout()
qb.show()
sys.exit(app.exec_())

The addStretch method adds a QSpacerItem to the end of a box layout. A QSpacerItem is an adjustable blank space.
Using vbox.addStretch(1) will add a zero-width spacer-item that
expands vertically from the top of the layout downwards.
Using hbox.addStretch(1) will add a zero-height spacer-item that
expands horizontally from the left of the layout rightwards.
Without stretch, the layout will be determined by the
sizePolicy
of the widgets. For a QPushButton, this is
QSizePolicy.Fixed
for the vertical dimension, and
QSizePolicy.Minimum
for the horizontal dimension. If you wanted the buttons to expand in
both directions, you could do something like this:
ok.setSizePolicy(QtGui.QSizePolicy.Minimum,
QtGui.QSizePolicy.Minimum)
cancel.setSizePolicy(QtGui.QSizePolicy.Minimum,
QtGui.QSizePolicy.Minimum)
The argument passed to addStretch changes the stretch factor. If you
add a second stretch after the ok button:
vbox = QtGui.QHBoxLayout()
vbox.addStretch(1)
vbox.addWidget(ok)
vbox.addStretch(2)
vbox.addWidget(cancel)
you will see that the second spacer item grows twice as fast as the
first. And if you set the first stretch to zero, it won't grow at
all.
If you want more information, see the Layout Management article in the Qt docs. It's also a good idea to use Qt Designer to experiment with stuff like this, as it gives you immediate visual feedback and shows you all the default values of the various properties involved.

Related

How to prevent QComboBox from displaying unnecessary scrollbar

The code below, which is based on an example from zetcode.com, creates a single combo box. There are several issues with the resulting dialog, but the following are especially annoying:
PyQt displays a vertical scrollbar for the combo box, although there is plenty of space to display the entire list of options without a scrollbar.
I've tried to move the combo box to a position near the upper-left corner of the window, but this isn't working.
#!/usr/bin/python
import sys
from PyQt5.QtWidgets import QWidget, QLabel, QHBoxLayout, QComboBox, QApplication
class Example(QWidget):
def __init__(self):
super().__init__()
self.setFixedWidth(400)
self.setFixedHeight(500)
self.initUI()
def initUI(self):
hbox = QHBoxLayout()
self.lbl = QLabel('Animals', self)
self.lbl.setStyleSheet('font-size:11pt')
combo = QComboBox(self)
combo.addItem('bear')
combo.addItem('cat')
combo.addItem('dog')
combo.addItem('dolphin')
combo.addItem('elephant')
combo.addItem('fish')
combo.addItem('frog')
combo.addItem('horse')
combo.addItem('rabbit')
combo.addItem('rat')
combo.addItem('shark')
combo.addItem('snake')
combo.addItem('tiger')
combo.addItem('whale')
combo.activated[str].connect(self.onActivated)
hbox.addWidget(combo)
hbox.setSpacing(20)
hbox.addWidget(self.lbl)
self.setContentsMargins(20, 20, 20, 20)
self.setLayout(hbox)
combo.move(20, 60)
self.setWindowTitle('QComboBox')
self.show()
def onActivated(self, text):
self.lbl.setText(text)
self.lbl.adjustSize()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
There are two wrong assumptions in the question.
the list of a QComboBox is a popup widget, it doesn't care (nor it should) about the available space the combobox might have: consider it as much as a context menu, which just pops out over the window, possibly going outside its boundaries if it requires more space (and that's just because those boundaries are meaningless to the menu);
the combo has been added to a layout manager, which takes care about resizing and positioning its (managed) child widgets, and that's why you cannot manually "move" them: the layout already sets the geometries automatically on its own everytime the widget is resized (which also happen when it's shown the first time), so any attempt to use move(), resize() or setGeometry() is completely useless;
When adding a widget to a layout, the default behavior is to make it occupy as much space as possible; since a QComboBox is one of those widgets that have a fixed size, the result is that it's actually centered (vertically and horizontally) in the space the layout is "assigning" to it, and this is clearly visible in your case because you set a fixed size for the container widget that is much bigger than what its contents would need.
There are two ways to align those widgets on top:
add the alignment arguments to addWidget:
hbox.addWidget(combo, alignment=QtCore.Qt.AlignTop)
hbox.addWidget(self.lbl, alignment=QtCore.Qt.AlignTop)
note that this won't give you good results in your case, because the label and the combo box have different heights, so the label might look "higher" than the combo;
use a QVBoxLayout layout as main layout for the widget, add the horizontal layout to it and then add a stretch after that (a stretch on a box layout is a "spacer" that tries to occupy as much space as possible)
# ...
mainLayout = QVBoxLayout()
mainLayout.addLayout(hbox)
mainLayout.addStretch()
self.setLayout(mainLayout)
PS: if you need to add lots of (string only) elements to a QComboBox, use addItems() instead of individually adding each of them.

QWidget becomes invisible on layout assignment

I'm making a custom window using QGridLayout to place window elements: like title bar, size grip, etc.
When I set any layout to my widget it doesn't show up on a start.
If I set .setVisible(True) it works nice.
So the question is: why does it happen, why widget becomes invisible on layout assignment?
Is that some kind of a bug or so it is conceived?
Widget file is:
from PySide2 import QtWidgets, QtGui, QtCore
class QCustomWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.background_color = QtGui.QColor(23, 23, 34)
self._initUI()
def _initUI(self):
self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
print(self.isVisible())
#self.setVisible(True)
# Is visible without layout
self.setLayout(QtWidgets.QGridLayout())
self.layout().setMargin(0)
self.layout().setSpacing(0)
# ---------------------------------------
def showEvent(self, event: QtGui.QShowEvent):
self.centerOnScreen()
def paintEvent(self, event: QtGui.QPaintEvent):
painter = QtGui.QPainter(self)
painter.setBrush(self.background_color)
painter.setPen(QtCore.Qt.NoPen)
painter.drawRect(0, 0, self.width(), self.height())
def centerOnScreen(self):
screen = QtWidgets.QDesktopWidget()
screen_geometry = screen.screenGeometry(self)
screen_center_x = screen_geometry.center().x()
screen_center_y = screen_geometry.center().y()
self.move(screen_center_x - self.width() // 2,
screen_center_y - self.height() // 2)
App file is:
from PySide2 import QtWidgets
from QCustomWindow import QCustomWindow
import sys
app = QtWidgets.QApplication(sys.argv)
window = QCustomWindow()
window.show()
sys.exit(app.exec_())
Qt does not update the geometry unless necessary or force him to do it, and it does this for efficiency reasons. So if the setVisible(True) or equivalent method is not called then the geometry will not change.
By not calling setVisible(True) then the size is recalculated when the parent widget is visible, and at that moment the widget analyzes the information of the QSizePolicy, QLayouts, etc. In your particular case the one that has the preference has the layout, and the layout calculates the size based on the widgets that are added to it but in your case it has no added widgets so the geometry calculated by the layout is 0x0 making it not visible for our sight. So if you add widgets to the QGridLayout you won't see that problem.
But if you call setVisible(True) the geometry is calculated at that moment, since there is no layout at that moment, the sizeHint is used, which by default is 640x480 and is therefore visible. And when the layout is established the container size is equal to the maximum of the previous size and the size provided by the layout.
Summary:
why widget becomes invisible on layout assignment?
If there is a layout set in a widget, then the size of the container widget will be the size determined by the widgets assigned to the layout, which in your case, since there are none, the size is 0x0.
Is that some kind of a bug or so it is conceived?
No, it is not a bug but an expected behavior. Add widgets or set a size to show the window.

PyQt5 add custom QWidget to QLayout

Currently I am trying to add a custom QWidget class to a QVBoxLayout. The problem I'm getting is that the widget doesn't appear at all in the layout. I even tried setting the minimum size of the QWidget because I thought that the widget wasn't showing because it's default size was set to zero.
This is a simplification of what the class looks like:
class myWidget(QWidget):
def __init__(self):
super().__init__()
# Slider
self.mySlider = QSlider(Qt.Horizontal)
self.mySlider.setRange(-360, 360)
# LCD Screen
self.lcd = QLCDNumber()
self.lcd.setMinimumHeight(45)
self.lcd.setMaximumHeight(75)
# set Size
self.setMinimumSize(QSize(400,300))
I removed the signals and slots made between the slider and the LCD screen because I am not worried here about the functionality. Only the fact that I get a gray area of QSize(400,300) directly between the two buttons in the following code:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
#Create Widgets to be Added to Central Widget
self.w1 = QPushButton("First")
self.w2 = myWidget()
self.w3 = QPushButton("Third")
#Set Central Widget and VBox
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.layout = QVBoxLayout()
self.central_widget.setLayout(self.layout)
#Add widgets
self.layout.addWidget(self.w1)
self.layout.addWidget(self.w2)
self.layout.addWidget(self.w3)
So what I'm simply doing is creating the 3 widgets, and placing them into the QVBoxLayout within the central widget. The 2 button widgets w1 and w3 appear but my custom widget doesn't appear and increasing the size of the widget via setMinimumSize only adds grey spacing between w1 and w3.
So the widget is there it just isn't visible for some reason. I am pretty new to PyQt so please explain why this has happened.
QWidgets are just containers for other widgets. A QWidget without any layout and subwidgets will just look like empty space unless you're doing some custom painting or styling.
In your example, you're not actually adding any sub-widgets to your custom widget. In order to add a sub-widget to another widget, you need to either set the parent of the subwidget, or add the subwidget to the layout of the parent widget (which automatically re-parents the subwidget)
class myWidget(QWidget):
def __init__(self):
super().__init__()
# Slider
self.mySlider = QSlider(Qt.Horizontal)
Here, you are creating a QSlider, but it's not actually owned by MyWidget, it's going to end up being owned by Qt, and I would expect it to be drawn in the upper left hand corner of your main window.
In order to make this a subwidget of MyWidget you need to set the parent and add it to a layout.
class myWidget(QWidget):
def __init__(self):
super().__init__()
self.myLay = QVBoxLayout()
self.setLayout(self.myLay)
# Notice self is being passed in to set the parent
self.mySlider = QSlider(Qt.Horizontal, self)
# You need to add widgets to a layout to position them properly
self.myLay.addWidget(self.mySlider)

PyQt QHBoxLayout inside QPushButton text is being truncated

I'm trying to place a checkbox and some text inside a button, but I'm having trouble getting the button to expand wide enough to see the full text.
self.check = QtGui.QCheckBox("long text", self)
self.checkLayout = QtGui.QHBoxLayout()
self.checkLayout.addWidget(self.check)
self.checkButton = QtGui.QPushButton(None, self)
self.checkButton.setLayout(self.checkLayout)
I've tried various combinations of adding stretches, setting the size policy, setting margins and styles etc. but haven't had any luck so far.
Thanks
Add a layout to the button. Place the checkbox inside the layout. Looks like this is the only way for QWidget to automatically track a size of its subwidgets.
But QWidget doesn't automatically resize itself to fit its content. You must invoke adjustSize function to do it.
class TestWidget(QtGui.QWidget):
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
self.check = QtGui.QCheckBox("very very long text")
self.checkLayout = QtGui.QHBoxLayout()
self.checkLayout.addWidget(self.check)
self.checkButton = QtGui.QPushButton()
self.checkButton.setLayout(self.checkLayout)
self.checkButton.adjustSize()
self.checkButton.setParent(self)
UPD: There is a problem with adjustSize. This function doesn't work for me if I call it after adding checkButton to the screen:
# works
self.checkButton.adjustSize()
self.checkButton.setParent(self)
# doesn't work
self.checkButton.setParent(self)
self.checkButton.adjustSize()
My solution is manual resizing instead of calling adjustSize:
self.checkButton.setFixedSize(self.checkLayout.sizeHint())

PyQT center toolbar buttons

By default the toolbar buttons in PyQT are aligned to the left, is it possible to make them centered so that they slide along when resizing?
I am not sure I understand correctly, but if you are looking for a way to center buttons on toolbar with respect to QMainWindow, then yes there is a (hackish) way. You just need to put a widget that acts like a 'spacer'. That is basically a QWidget with expanding size policy.
Here is a minimal example:
import sys
from PyQt4 import QtGui
app = QtGui.QApplication(sys.argv)
main = QtGui.QMainWindow()
toolbar = QtGui.QToolBar()
# spacer widget for left
left_spacer = QtGui.QWidget()
left_spacer.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
# spacer widget for right
# you can't add the same widget to both left and right. you need two different widgets.
right_spacer = QtGui.QWidget()
right_spacer.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
# here goes the left one
toolbar.addWidget(left_spacer)
# some dummy actions
toolbar.addAction('one')
toolbar.addAction('two')
toolbar.addAction('three')
# and the right one
toolbar.addWidget(right_spacer)
main.addToolBar(toolbar)
main.show()
sys.exit(app.exec_())
Which gives you this:

Categories

Resources