resizing a dialog with PyQt4 - python

I have this sample of code:
import sys
from PyQt4.QtGui import (QApplication, QHBoxLayout, QVBoxLayout, QDialog,
QFrame, QPushButton, QComboBox)
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
moreButton = QPushButton('moreButton')
moreButton.setCheckable(True)
resizeButton = QPushButton('Resize')
combo = QComboBox()
combo.addItems(['item1', 'item2'])
layout1 = QHBoxLayout()
layout1.addWidget(moreButton)
layout1.addWidget(resizeButton)
layout2 = QHBoxLayout()
layout2.addWidget(combo)
self.frame = QFrame()
self.frame.setLayout(layout2)
self.frame.hide()
layout3 = QVBoxLayout()
layout3.addLayout(layout1)
layout3.addWidget(self.frame)
moreButton.toggled.connect(self.frame.setVisible)
moreButton.clicked.connect(self.method)
resizeButton.clicked.connect(self.method)
self.setLayout(layout3)
self.resize(630, 50)
def method(self):
if self.frame.isVisible():
self.resize(630, 150)
else:
self.resize(630, 250)
app = QApplication(sys.argv)
form = Form()
form.show()
app.exec_()
I run it and when moreButton clicked the ComboBox appears or disappears. Dialog's size also changes. But if I change the method to:
def method(self):
if self.frame.isVisible():
self.resize(630, 150)
else:
self.resize(630, 50)
(in order to set the initial size when combo is hidden) the resizing does not work. However, if I click resizeButton -which is connected to the same method- the resizing works properly.
I know that there are other ways to achieve such a result (eg. layout.setSizeConstraint(QLayout.SetFixedSize)), but I want to declare size explicitly.
What am I doing wrong?

My guess is that you are trying to resize the QDialog before it has time to re-adjust its size after you hide stuff. So at the time resize is called it has a minimumSize that makes sure the buttons and the combobox visible. When you call it after some time, it now has proper miminumSize and responds properly.
A quick fix is manually overriding minimumSize before resizing:
def method(self):
if self.frame.isVisible():
# uncomment below, if you like symmetry :)
# self.setMinimumSize(630, 150)
self.resize(630, 150)
else:
self.setMinimumSize(630, 50)
self.resize(630, 50)
But, if I were to tackle this, I'd just leave managing resizing to the layout and use sizeConstraint. That's what these layouts for anyways.

This question and the answers were helpful in my situation: Automatic sizing of a QDialog with QLayout/QVBoxLayout containing a QLabel of variable size content/message to the user while also avoiding the double arrow cursor at the border of the overall QDialog container. The sizePolicy of the dialog itself was set to Fixed and yet the arrows would appear even though it cannot be resized (wouldn't budge). And even though the inner layout does automatically/magically resize, using SetFixedSize on the layout (surprise, surprise) made those annoying double arrows of the overall QDialog go away.
QDialog: QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
QLayout: setSizeConstraint(QLayout.SetFixedSize)
QLabel: QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
...and now the dialog sizes itself appropriately to the volume contained in the label yet the dialog itself is not (seemingly) user-resizable, which is good for info and error messages.
Seemed counterintuitive to me so I thought worth adding here for others.
A bit more detail...
self.label = QtGui.QLabel(self)
self.label.sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.label.setSizePolicy(self.label.sizePolicy)
self.label.setMinimumSize(QtCore.QSize(450, 40))
self.label.setWordWrap(True)
self.verticalLayout = QtGui.QVBoxLayout(self)
# get rid of overall dialog resize arrows
self.verticalLayout.setSizeConstraint(QtGui.QLayout.SetFixedSize) # no resize arrows
self.verticalLayout.addWidget(self.label)

This problem appears to be caused by the order in which events are processed.
Here's a simple fix:
def method(self):
app.processEvents()
if self.frame.isVisible():
self.resize(630, 150)
else:
self.resize(630, 50)

did you try this ? unless i misunderstand, this is what you wanna do.
def method(self):
if self.frame.isVisible():
self.resize(630, 150)
self.frame.setVisible(False)
else:
self.resize(630, 50)
edit : the final answer is layout3.setSizeConstraint(QLayout.SetNoConstraint)

Related

Problem with GridLayout widget stretching/sizing

I'm trying to make a simple image editor GUI using the GridLayout. However, I am coming across a problem where the ratios between the image and the side panels are not what I want them to be. Currently, my code is:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Window(QMainWindow):
def __init__(self, parent = None):
super().__init__(parent)
self.setWindowTitle("PyEditor")
self.setGeometry(100, 100, 500, 300)
self.centralWidget = QLabel()
self.setCentralWidget(self.centralWidget)
self.gridLayout = QGridLayout(self.centralWidget)
self.createImagePanel()
self.createDrawPanel()
self.createLayerPanel()
def createImagePanel(self):
imageLabel = QLabel(self)
pixmap = QPixmap('amongus.png')
imageLabel.setPixmap(pixmap)
self.gridLayout.addWidget(imageLabel, 0, 0, 3, 4)
def createDrawPanel(self):
drawPanel = QLabel(self)
drawLayout = QVBoxLayout()
drawPanel.setLayout(drawLayout)
tabs = QTabWidget()
filterTab = QWidget()
drawTab = QWidget()
tabs.addTab(filterTab, "Filter")
tabs.addTab(drawTab, "Draw")
drawLayout.addWidget(tabs)
self.gridLayout.addWidget(drawPanel, 0, 4, 1, 1)
def createLayerPanel(self):
layerPanel = QLabel(self)
layerLayout = QVBoxLayout()
layerPanel.setLayout(layerLayout)
tab = QTabWidget()
layerTab = QWidget()
tab.addTab(layerTab, "Layers")
layerLayout.addWidget(tab)
self.gridLayout.addWidget(layerPanel, 1, 4, 1, 1)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
This gives me the following window:
When I resize the window, only the filter/draw and layer panels are stretching, and not the image panel. I want to image panel to stretch as well and take up the majority of the window instead.
While theoretically every Qt widget could be used as a container, some widgets should not be used for such a purpose, as their size hints, size policies and resizing have different and specific behavior depending on their nature.
QLabel is intended as a display widget, not as a container. Everything related to its size is based on the content (text, image or animation), so the possible layout set for it will have no result in size related matters and will also create some inconsistencies in displaying the widgets added to that layout.
If a basic container is required, then basic QWidget is the most logical choice.
Then, if stretching is also a requirement, that should be applied using the widget or layout stretch factors. For QGridLayout, this is achieved by using setColumnStretch() or setRowStretch().
Trying to use the row or column span is not correct for this purpose, as the spanning only indicates how many grid "cells" a certain layout item will use, which only makes sense whenever there are widgets that should occupy more than one "cell", exactly like the spanning of a table.
So, the following changes are required to achieve the wanted behavior:
change all QLabel to QWidget (except for the label that shows the image, obviously);
use the proper row/column spans; the imageLabel should be added with only one column span (unless otherwise required):
self.gridLayout.addWidget(imageLabel, 0, 0, 3, 1, alignment=Qt.AlignCenter)
set a column stretch of (at least) 1 for the first column:
self.gridLayout.setColumnStretch(0, 1)
if you want the image to be center aligned in the available space, set the alignment on the widget (not when adding it to the layout):
imageLabel = QLabel(self, alignment=Qt.AlignCenter)
Note that all the above will not scale the image whenever the available size is greater than that of the image. While you can set the scaledContents to True, the result will be that the image will be stretched to fill the whole available space, and unfortunately QLabel doesn't provide the ability to keep the aspect ratio. If you need that, then it's usually easier to subclass QWidget and provide proper implementation for size hint and paint event.
class ImageViewer(QWidget):
_pixmap = None
def __init__(self, pixmap=None, parent=None):
super().__init__(parent)
self.setPixmap(pixmap)
def setPixmap(self, pixmap):
if self._pixmap != pixmap:
self._pixmap = pixmap
self.updateGeometry()
def sizeHint(self):
if self._pixmap and not self._pixmap.isNull():
return self._pixmap.size()
return super().sizeHint()
def paintEvent(self, event):
if self._pixmap and not self._pixmap.isNull():
qp = QPainter(self)
scaled = self._pixmap.scaled(self.width(), self.height(),
Qt.KeepAspectRatio, Qt.SmoothTransformation)
rect = scaled.rect()
rect.moveCenter(self.rect().center())
qp.drawPixmap(rect, scaled)

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()

PyQt5: How to Resize mainwindow to fit a Stackedwidget or Stackedlayout

I have a MainWindow with a Dockwidget attached to it for the sole purpose of switching between multiple Stackedwidget/StackedLayout
The Issue i am having is that the StackedLayout holds a particular widget that displays a large image which resizes the MainWindow as expected but when switching from that Widget to another using the docked widget the MainWindow still uses the SizeHint from the large widget even after calling a MainWindow.Resize(stackedwidget) which leaves the window larger than necessary for the current widget in view
Here's the code:
from PyQt5 import QtCore, QtGui, QtWidgets
class Mainwindow(QtWidgets.QMainWindow):
def __init__(self):
super(Mainwindow, self).__init__()
# window setting
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# window central widget
self.centerwidget = QtWidgets.QWidget()
self.setCentralWidget(self.centerwidget)
# stack for page/widget management
self.stack = QtWidgets.QStackedLayout(self.centerwidget)
# widgets or page management
self.widgeta = None
self.dockWidget = None
self.widgetb= None
def pageManager(self, widget):
# switching stack widgets
self.stack.setCurrentWidget(widget)
widget.updateGeometry()
widget.layout.update()
self.centerwidget.updateGeometry()
self.updateGeometry()
self.resize(widget.layout.totalSizeHint())
def load(self):
# basically startup
# stack settings
self.widgeta= Widgeta()
self.widgetb= Widgetb()
self.stack.addWidget(self.widgeta)
self.stack.addWidget(self.widgetb)
self.stack.setStackingMode(QtWidgets.QStackedLayout.StackOne)
self.stack.setCurrentWidget(self.widgeta)
# dock widget
self.dockWidget = Duck()
self.addDockWidget(QtCore.Qt.TopDockWidgetArea, self.dockWidget)
# show mainwindow
self.show()
class Duck(QtWidgets.QDockWidget):
def __init__(self, parent=None):
super(Duck, self).__init__(parent)
# create subwidget assign layout to subwidget
self.dockCentralWidget = QtWidgets.QWidget()
self.dockCentralWidgetlayout = QtWidgets.QHBoxLayout()
self.dockCentralWidgetlayout.setContentsMargins(QtCore.QMargins(1,1,1,1))
self.dockCentralWidgetlayout.setSpacing(1)
self.dockCentralWidget.setLayout(self.dockCentralWidgetlayout)
self.dockCentralWidget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
self.setStyleSheet("background-color: #354465;")
# subwidget elements
self.Ui()
# add subwidget and elements
self.setWidget(self.dockCentralWidget)
self.dockCentralWidgetlayout.addWidget(self.widgetAButton)
self.dockCentralWidgetlayout.addWidget(self.widgetBButton)
def Ui(self):
# first button
self.widgetAButton = QtWidgets.QPushButton('aaaa')
self.widgetAButton.clicked.connect(lambda : self.clicker((self.widgetAButton, Window.widgeta)))
# second button
self.widgetBButton = QtWidgets.QPushButton('bbbb')
self.widgetBButton.clicked.connect(lambda : self.clicker((self.widgetBButton, Window.widgetb)))
def clicker(self, arg):
Window.pageManager(arg[1])
class Widgeta(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widgeta, self).__init__(parent)
# sizing and layout
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.layout = QtWidgets.QVBoxLayout()
self.setLayout(self.layout)
# widgets
self.ui()
def ui(self):
# a check button
self.goButton = QtWidgets.QPushButton()
self.goButton.setObjectName('gobutton')
self.goButton.setText('ccc')
self.goButton.setMinimumSize(QtCore.QSize(400, 150))
self.goButton.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
self.goButton.setStyleSheet(goButtonCSS)
# label
self.infoLabel = QtWidgets.QLabel()
self.infoLabel.setObjectName('infolabel')
self.infoLabel.setMinimumSize(QtCore.QSize(400, 250))
self.infoLabel.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
self.infoLabel.setText('jjjj')
self.infoLabel.setStyleSheet(topDisplayCSS)
# add widgets to layout
self.layout.addWidget(self.infoLabel)
self.layout.addWidget(self.goButton, QtCore.Qt.AlignVCenter, QtCore.Qt.AlignHCenter)
self.layout.setContentsMargins(QtCore.QMargins(0,0,0,0))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Window = Mainwindow()
Window.load()
sys.exit(app.exec_())
i Have also tried using a StackedWidget as the central widget and switching, same thing.
The QStackedLayout always uses its minimum size based on the largest minimum size of all its widgets. This also means that all widgets using a stacked layout behave in the same way: QStackedWidget and QTabWidget.
This is done by design, and it's the correct approach (otherwise the parent widget would always try to resize itself whenever a new "page" is set with a different minimum size)
The solution is to subclass the QStackedLayout (or the QStackedWidget) and override its minimumSize(); if needed, sizeHint() could be overridden in the same fashion, but I wouldn't suggest it.
class StackedLayout(QtWidgets.QStackedLayout):
def minimumSize(self):
if self.currentWidget():
s = self.currentWidget().minimumSize()
if s.isEmpty():
s = self.currentWidget().minimumSizeHint()
return s
return super().minimumSize()
Note that if you want to resize the main window to the minimum possible size you should use mainWindow.resize(mainWindow.minimumSizeHint()), since a QMainWindow also has minimum size requirements depending on its extra widgets, like toolbars or, as in your case, dock widgets.

How to reduce the size of a QComboBox with PyQt?

I created a small program with PyQt in which I put a QComboBox, but this one contains only a list of 2 letters. The program window being small, to save space, I would like to reduce the width of the QComboBox.
This is what it looks like now. The width is too large.
I searched the internet, but after a lot of searching time, I still haven't found anything. Thank you in advance if you have an idea.
There are several methods to resize a widget's size. Lets say the QComboBox is defined as this:
combo = QComboBox(self)
One way is to use QWidget.resize(width, height)
combo.resize(200,100)
To obtain a proper size automatically, you can use QWidget.sizeHint() or sizePolicy()
combo.resize(combo.sizeHint())
If you want to set fixed size, you can use setFixedSize(width, height), setFixedWidth(width), or setFixedHeight(height)
combo.setFixedSize(400,100)
combo.setFixedWidth(400)
combo.setFixedHeight(100)
Here's an example:
from PyQt5.QtWidgets import (QWidget, QLabel, QComboBox, QApplication)
import sys
class ComboboxExample(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("Ubuntu", self)
self.combo = QComboBox(self)
self.combo.resize(200,25)
# self.combo.resize(self.combo.sizeHint())
# self.combo.setFixedWidth(400)
# self.combo.setFixedHeight(100)
# self.combo.setFixedSize(400,100)
self.combo.addItem("Ubuntu")
self.combo.addItem("Mandriva")
self.combo.addItem("Fedora")
self.combo.addItem("Arch")
self.combo.addItem("Gentoo")
self.combo.move(25, 25)
self.label.move(25, 75)
self.combo.activated[str].connect(self.onActivated)
# self.setGeometry(0, 0, 500, 125)
self.setWindowTitle('QComboBox Example')
self.show()
def onActivated(self, text):
self.label.setText(text)
self.label.adjustSize()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = ComboboxExample()
sys.exit(app.exec_())

Set widget width based on its height

I am trying to find a way to have one of my widgets maintain a width based on its height. I have other widgets working fine reimplementing the heightForWidth method. That was easy because that method is standard. I know there is no included widthForHeight method so I have tried many options the internet has suggested but have not gotten anything to work all the way. What I currently have almost gets me there.
I first reimplement my widget's sizeHint to get its width to be a ratio of its parent's (QHBoxLayout) height.
The sizeHint makes MyCustomLabel show with the right size at first show but did not update during times the user resized the window. I don't know if this the best way but to fix that I am reimplementing resizeEvent and calling adjustSize to force the sizeHint recalculation.
With those two reimplemented methods MyCustomLabel shows with the right size. I placed this custom widget in a QHBoxLayout with a few other standard widgets. The problem is the other widgets in the layout don't respect the new size of my MyCustomLabel when the user resizes the window. What I end up with is the other widgets in the layout either overlapping or being placed too far from MyCustomLabel. I kind of get it, I am brute forcing my widget to a size and not letting the layout do the work. However I thought updating the sizeHint would inform the layout of MyCustomLabel's new size and adjust everything accordingly. How do I fix this layout problem or am I going about this widthForHeight problem all the wrong way?
Edit:
I tried #AlexanderVX suggestion of setting the SizePolicy to Minimum and while it does prevent the other widgets from overlapping it also locked MyCustomLabel to a fixed size. I need the widget to expand and shrink with the layout. I also tried Preferred, Expanding, MinimumExpanding policies just to see if they would do anything but with no luck.
from __future__ import division
from PySide import QtCore
from PySide import QtGui
import sys
class MyCustomLabel(QtGui.QLabel):
clicked = QtCore.Signal(int)
dblClicked = QtCore.Signal(int)
def __init__(self, leadSide='height', parent=None):
super(MyCustomLabel, self).__init__()
self.leadSide = leadSide
# sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,
# QtGui.QSizePolicy.Preferred)
# sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,
# QtGui.QSizePolicy.Expanding)
# sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding,
# QtGui.QSizePolicy.MinimumExpanding)
# sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum,
# QtGui.QSizePolicy.Minimum)
# self.setSizePolicy(sizePolicy)
def resizeEvent(self, event):
super(MyCustomLabel, self).resizeEvent(event)
self.adjustSize()
def sizeHint(self):
super(MyCustomLabel, self).sizeHint()
parentSize = self.parent().size().toTuple()
if self.leadSide.lower() == 'height':
new_size = QtCore.QSize(parentSize[1] * (16 / 9), parentSize[1]) * .9
if self.leadSide.lower() == 'width':
new_size = QtCore.QSize(parentSize[0], parentSize[0] / (16 / 9)) * .9
return new_size
class __Test__(QtGui.QWidget):
def __init__(self):
super(__Test__, self).__init__()
self.initUI()
def initUI(self):
customLabel = MyCustomLabel(leadSide='height')
# customLabel.setScaledContents(True)
customLabel.setStyleSheet('background-color: blue;'
'border:2px solid red;')
btn01 = QtGui.QPushButton('button')
btn01.setFixedHeight(80)
textEdit = QtGui.QTextEdit()
textEdit.setFixedSize(150, 150)
layout01 = QtGui.QHBoxLayout()
layout01.setContentsMargins(0,0,0,0)
layout01.setSpacing(0)
layout01.addWidget(customLabel)
layout01.addWidget(btn01)
layout01.addWidget(textEdit)
self.setLayout(layout01)
self.setGeometry(300, 300, 600, 300)
self.setWindowTitle('Testing')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = __Test__()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Categories