How to prevent QComboBox from displaying unnecessary scrollbar - python

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.

Related

Why do QGridLayout Widgets move around when adding new ones?

I can't seem to wrap my head around how they work. The the best for placing multiple widgets seems to be QGridLayout but when I add something into a specific row/column and later decide to add somthing into another row/column everything shifts and it's just really frustrating.
For example I would not even be able to do such a simple layout as the google mainpage. When I add a searchbar to the place I want it to be and then add an image/text above it everything moves into weird spots etc and I can't find proper explanations online on how to handle it. Thus I would be delighted if anyone could explain it to an absolute beginner, like me, in an understandable way.
So when I have the following code:
from PyQt6.QtWidgets import *
import sys
import numpy as np
import os
from PyQt6 import QtCore, QtGui
from PyQt6.QtCore import QEvent, Qt
from PyQt6.QtGui import QPalette, QColor
from pathlib import Path
app = QApplication(sys.argv)
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.resize(1024, 768)
self.setWindowTitle("Tracker")
layout = QGridLayout()
self.setLayout(layout)
#layout.setRowMinimumHeight(0, 50)
#layout.setColumnMinimumWidth(0,50)
self.input = QLineEdit(self)
self.input.setPlaceholderText('Enter Username')
layout.addWidget(self.input,1,1)
self.input.setFixedSize(300,30)
self.darkmode_check = QCheckBox('Darkmode',self)
self.darkmode_check.toggled.connect(self.darkmode)
self.darkmode_check.setChecked(True)
self.darkmode_check.move(0,0)
def darkmode(self):
if self.darkmode_check.isChecked() == True:
app.setStyleSheet(Path('D:\CODE\League Code\darkorange.qss').read_text())
else:
app.setStyleSheet(Path('D:\CODE\League Code\classic_edit.qss').read_text())
window = MainWindow()
window.show()
sys.exit(app.exec())
I get this screen which is what I want.
When I only want to add a text above this search bar:
by adding
self.text = QLabel(self)
self.text.setText('Tracker')
layout.addWidget(self.text,0,1)
I get this:
which is all over the place.
Does anyone have good explanations on GridLayout or can recommend good websites for it? I found a lot about what the grid looks like etc but nothing helped (and also some posts giving me 3x3 grids, some 4x4 etc, I'm just confused at this point)
I basically just want to place a searchbar in the middle, then a text above that and keep on adding little things here and there.
Thank you
Qt basic layouts always try to evenly divide the available space in its "cells", and each widget will have that space reserved (even if it doesn't use all of it).
Note that different widget types have also different size policies that tell the layout how it should allocate the available space and eventually set the geometry of those widgets.
For instance, QLineEdit has a Fixed vertical policy, meaning that its size hint will always be considered as the only valid height (which is similar to calling setFixedHeight() or setFixedSize() as you did).
QLabel, instead, has a Preferred size policy, meaning that if there's space left in the layout, it can take advantage of it.
When you only have the line edit, the layout only has that widget, so it will place it in the center (because you didn't specify an alignment). But when you add the label, the layout finds that the line edit needs a very small space, so it will leave the remaining to the label, hence your result.
For a simple case like this, you can just specify a proper alignment when adding the widgets: when the alignment is provided, the item will not try to occupy the whole cell and the layout will align it to the available space of that layout cell.
layout.addWidget(self.text, 0, 0, alignment=Qt.AlignBottom)
layout.addWidget(self.input, 1, 0, alignment=Qt.AlignTop)
Note that I changed the column to 0, as there is no point in placing widgets in the second column if there's nothing in the first, unless you want to get advantage of setColumnStretch() or setColumnMinimumWidth().
Also consider that for this kind of "wide" layouts with lots of empty spaces it's usually better to use nested layouts, or use container widgets.
For instance:
layout = QGridLayout(self)
centerLayout = QVBoxLayout()
layout.addLayout(centerLayout, 0, 0, alignment=Qt.AlignCenter)
# ...
centerLayout.addWidget(self.text)
centerLayout.addWidget(self.input)
Or, alternatively:
layout = QGridLayout(self)
centerWidget = QWidget()
layout.addWidget(centerWidget, 0, 0, alignment=Qt.AlignCenter)
centerLayout = QVBoxLayout(centerWidget)
# ... as above
Try to remove the alignment argument in the two examples above and you'll see the difference.
I suggest you to do some experiments using layouts in Qt Designer, which makes it easier to immediately understand how layout work and behave with different widget types.

Size of QWidget.rect doesn't change even when I resize the main window

I have a Main window, and I want to apply some QEvent in that window mouse events like move press... etc., My problem is after I created the app and main window I resized it, but when I call the rect() function which is from QWidget it actually gives me the default (0, 0, 100, 30), I want it to give me the size of the window.
class TestTools(unittest.TestCase):
app = QApplication([])
main_window = QMainWindow()
main_window.resize(958, 584)
scene = QGraphicsScene()
canvas = Canvas(scene, main_window)
print(canvas.rect()) # It Print (0, 0, 100, 30) Whereas it should be (0, 0, 958, 584)
def test():
pass # There is many functions am using but I don't think it is important to share it
And This is Canvas Class
class Canvas(QGraphicsView):
def __init__(self, scene, centralwidget):
super().__init__(scene, centralwidget)
self.scene = scene
background_color = Qt.white
self.pixmap_item: QGraphicsItem = self.scene.addPixmap(QPixmap(780, 580))
self.pixmap_item.setTransformationMode(Qt.FastTransformation)
So Where is the problem.
You're making an assumption based on wrong premises.
First of all, you're just creating a widget (the QGraphicsView) with a parent. Doing this will only make the widget a child of that parent, but without any size constraints: the widget is just "floating" inside its parent, so it will have a default size that usually is 100x30 for new child widgets (and 640x480 for widgets without parent set in the constructor)).
If you want to adjust the child to the parent it must be added to a layout. QMainWindow has its own (private) layout manager that uses a central widget to show the "main" content of the window, so if that QGraphicsView is going to be the only widget, you can just set that using main_window.setCentralWidget(canvas).
Then, a widget doesn't usually receive a resizeEvent until it's mapped the first time, unless it's manually resized. When it's shown the first time, any non previously layed out item receives the resize event. Since you're not showing the main window, no resize event is emitted, but even in that case, you resized the window before adding the child, so you would still see the same default size.
In order to ensure that the layout is properly computed even without showing the widget in which it is set, activate() must be explicitly called.
So, summing it up:
widgets must be added to a layout in order to adapt their size to their parent;
whenever a QMainWindow is used, setCentralWidget() must be used on the "main widget";
manual resizing should be done after adding children to the layout in order to ensure that their geometries adapt to the parent (if you do the opposite, some size policies could result in the parent resizing itself based on the children);
if the widget is not going to be shown, the layout must be manually activated;
class TestTools(unittest.TestCase):
app = QApplication([])
main_window = QMainWindow()
scene = QGraphicsScene()
canvas = Canvas(scene, main_window)
main_window.setCentralWidget(canvas)
main_window.resize(958, 584)
main_window.layout().activate()
print(canvas.rect())

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.

How to set QStackedWidget size to child widgets minimum size?

Unable to get the QLabel in this example to be of the minimum size to contain its text. I need the layout and stacked widget to then size themselves to the minimum required to fit the label.
I have used code from https://www.tutorialspoint.com/pyqt/pyqt_qstackedwidget.htm to demonstrate my issue.
Setting the size policies seems to work when the application starts, but increasing the application width eventually causes the label to expand after the list reaches a certain width.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class stackedExample(QWidget):
def __init__(self):
super(stackedExample, self).__init__()
self.rightlist = QListWidget()
self.rightlist.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding)
self.rightlist.insertItem(0, 'Contact')
self.stack1 = QWidget()
self.stack1.setSizePolicy(QSizePolicy.Minimum,QSizePolicy.Minimum)
self.stack1UI()
self.Stack = QStackedWidget(self)
self.Stack.setSizePolicy(QSizePolicy.Minimum,QSizePolicy.Minimum)
self.Stack.addWidget(self.stack1)
hbox = QHBoxLayout(self)
hbox.addWidget(self.Stack)
hbox.addWidget(self.rightlist)
self.setLayout(hbox)
self.rightlist.currentRowChanged.connect(self.display)
self.setGeometry(300, 50, 10, 10)
self.setWindowTitle('StackedWidget demo')
self.show()
def stack1UI(self):
layout = QVBoxLayout()
label = QLabel("Hello World")
label.setSizePolicy(QSizePolicy.Minimum,QSizePolicy.Minimum)
label.setStyleSheet("QLabel { background-color : red; color : blue; }")
layout.addWidget(label)
self.stack1.setLayout(layout)
def display(self, i):
self.Stack.setCurrentIndex(i)
def main():
app = QApplication(sys.argv)
ex = stackedExample()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
tl;dr
Remove the size policy settings for everything but the QStackedWidget only (for which you'll have to set the horizontal policy to Maximum), and everything should be fine.
Explanation
I have to admit: I always felt that QSizePolicy enum names are confusing (I know that I'm not the only one), so I sympathize with your doubts.
Setting the stretch resolves the issue only partially, because sometime you might need or want to manually set the stretches, and that will possibly mess around with some size policies.
The problem is that you're setting the size policy to "Minimum", which, as the QSizePolicy documentation explains, says that:
The sizeHint() is minimal, and sufficient. The widget can be expanded [...]
And that's because Minimum uses the GrowFlag.
This means that, if the layout "thinks" that there's some available space for a widget, it will let it expand: Minimum does not mean that the widget will use it's minimal size (or, better, its minimumSizeHint()), but that it will use the sizeHint() as a minimum size for the layout, while it keeping its capacity to expand; if there's available space, it will use it.
What you actually need is to set the horizontal policy to Maximum instead, and, specifically, to the Stack object only (the QStackWidget, not the QWidget container, nor the QLabel).
That's because Maximum actually uses the ShrinkFlag (again, from the QSizePolicy docs):
The sizeHint() is a maximum. The widget can be shrunk any amount
without detriment if other widgets need the space (e.g. a separator
line). It cannot be larger than the size provided by sizeHint().
That said, be aware that there are known issues with QLabels in certain cases, specifically if the label has word wrapping.
Not sure if it is the correct approach, but adding in a stretch factor seems to have achieved what I was looking for.
hbox.addWidget(self.rightlist, stretch=1)

How exactly does addStretch work in QBoxLayout?

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.

Categories

Resources