Layout in QWidget makes background white when adding a stretch - python

Im using PyQt5 and it's styling system to create a modern looking GUI for my application and i can't seem to get this right.
So i've got a costum titlebar all working. It has 3 parts; a menubar, a label and another menubar that serves as the titlebar buttons for closing, min- and maximizing.
I need this titlebar to be a light grey color, but as you can see in the image below, there is white space between the elements.
What it is now:
What is should be:
When you run the example below, you can see that between the labels there is some empty space. Even though the labels are inside a box without styling, the styling is set on the widget.
#### PyQt imports....
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QWidget, QHBoxLayout, QMenuBar, QApplication,
QLabel, QVBoxLayout)
#### Python imports....
import sys
#### Class for sampleWindow....
class sampleWindow(QWidget):
def __init__(self):
super().__init__()
#### Some window settings....
self.setWindowTitle('Sample Program')
self.setGeometry(400, 300, 1000, 500)
######## THE SAME PROBLEM BUT THIS TIME NOT IN A QMENUBAR ########
#### Creating the widget and it's layout....
parentLayout = QHBoxLayout()
parentWidget = QWidget()
#### Creating the elements....
sampleLabelLeft = QLabel('left')
sampleLabelCenter = QLabel('center')
sampleLabelRight = QLabel('right')
#### Setting alignment for the elements....
sampleLabelLeft.setAlignment(Qt.AlignLeft)
sampleLabelCenter.setAlignment(Qt.AlignCenter)
sampleLabelRight.setAlignment(Qt.AlignRight)
#### Adding the elements to the parentLayout....
parentLayout.addWidget(sampleLabelLeft)
parentLayout.addWidget(sampleLabelCenter)
parentLayout.addWidget(sampleLabelRight)
#### Setting parentLayout as layout for parentWidget....
parentWidget.setLayout(parentLayout)
#### Set styling for elements....
self.setStyleSheet('QWidget{background:blue;} QLabel{background:red;}')
#### Setting some a box to put parentWidget in so it can be set as the main layout....
mainBox = QVBoxLayout()
mainBox.addStretch()
mainBox.addWidget(parentWidget)
mainBox.addStretch()
mainBox.setContentsMargins(200,200,200,200)
self.setLayout(mainBox)
app = QApplication(sys.argv)
sampleWindow = sampleWindow()
sampleWindow.show()
app.exec()
So after this i set the background color of the QWidget to a bit of a light grey and the stretches are ignored.
Does anyone know a workaround for this?

By default the layout has a style-dependent spacing, so the solution for your case is to set it to 0:
# ...
parentLayout = QHBoxLayout()
parentLayout.setSpacing(0)
# ...

I found that setting a background widget solved the problem:
parentWidget = QWidget()
label_background = QLabel(parentWidget)
label_background.setFixedSize(1920, 1080)

Related

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

How does stretch factor work in Qt?

I'm working on a GUI application with pyqt5. At a certain dialog I need many components, being one of those a QWebEngineView as a canvas for plotting data, which should take most of the space available even if the chart is not ready when creating the dialog.
I expect it to look something like this:
I investigated and found about the stretch factor. I saw that QSizePolicy is directly applicable only to widgets and not to layouts, as shown in this SO answer. But then I saw that the methods addWidget and addLayout allow me to set the stretch factor in the direction of the QBoxLayout, and that seemed ideal for my intentions.
So I tried with:
from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout, QHBoxLayout
from strategy_table import StrategyTable
layout = QVBoxLayout()
layout.addWidget(QLabel("Strategy components"))
# Upper layout for a table. Using a 30% of vertical space
upper_layout = QHBoxLayout() # I'm using a HBox because I also want a small button at the right side of the table
self.table = StrategyTable(self) # Own class derived from QTableWidget
upper_layout.addWidget(self.table)
add_button = QPushButton('Add')
add_button.clicked.connect(self._show_add_dialog)
upper_layout.addWidget(add_button)
layout.addLayout(upper_layout, 3) # Setting 20% of size with the stretch factor
# Then the plot area, using 60% of vertical space
layout.addWidget(QLabel("Plot area"))
canvas = QWebEngineView()
layout.addWidget(self.canvas, 6)
# Finally, a small are of 10% of vertical size to show numerical results
layout.addWidget(QLabel("Results"))
params_layout = self._create_results_labels() # A column with several QLabel-QTextArea pairs, returned as a QHBoxLayout
layout.addLayout(params_layout, 1)
self.setLayout(layout)
But it looked exactly the same as before:
It looked quite ok before adding the results Layout at the bottom, I guess because the upper table is empty at the beginning, and therefore took very little space and left the rest to the canvas.
Anyway, it seems that the stretch factor is being ignored, so I don't know if I am missing something here, or that I didn't fully understand the stretch factor.
BTW, I know I would use the QtEditor for designing the GUI, but I kind of prefer doing these things manually.
The problem is simple, the layouts handle the position and size of the widgets, but it has limits, among them the minimum size of the widgets, and in your case the last element has a height higher than 10%, so physically it is impossible. We can see that by removing the content or using a QScrollArea:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
class StrategyTable(QtWidgets.QTableWidget):
pass
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
table = StrategyTable()
button = QtWidgets.QPushButton("Add")
hlay = QtWidgets.QHBoxLayout()
hlay.addWidget(table)
hlay.addWidget(button)
canvas = QtWebEngineWidgets.QWebEngineView()
canvas.setUrl(QtCore.QUrl("http://www.google.com/"))
scroll = QtWidgets.QScrollArea()
content_widget = QtWidgets.QWidget()
scroll.setWidgetResizable(True)
scroll.setWidget(content_widget)
vlay = QtWidgets.QVBoxLayout()
vlay.addWidget(QtWidgets.QLabel("Results:"))
params_layout = self._create_results_labels()
content_widget.setLayout(params_layout)
vlay.addWidget(scroll)
lay.addLayout(hlay, 3)
lay.addWidget(canvas, 6)
lay.addLayout(vlay, 1)
def _create_results_labels(self):
flay = QtWidgets.QFormLayout()
for text in ("Delta", "Gamma", "Rho", "Theta", "Vega"):
flay.addRow(text, QtWidgets.QTextEdit())
return flay
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Hope this will be useful to understand Layouts along with stretch factor
Sample Layout based on percentages (CPP)
QVBoxLayout *topMostVerticalLayout = new QVBoxLayout(this);
QHBoxLayout *upperHorznLayout = new QHBoxLayout();
QHBoxLayout *bottomHorznLayout = new QHBoxLayout();
QVBoxLayout *InnerVerticalLayout1 = new QVBoxLayout(this);
QVBoxLayout *InnerVerticalLayout2 = new QVBoxLayout(this);
QVBoxLayout *InnerVerticalLayout3 = new QVBoxLayout(this);
QVBoxLayout *InnerVerticalLayout4 = new QVBoxLayout(this);
QVBoxLayout *InnerVerticalLayout5 = new QVBoxLayout(this);
bottomHorznLayout->addLayout(InnerVerticalLayout1,15); //(15% stretch)
bottomHorznLayout->addLayout(InnerVerticalLayout2,15); //(15% stretch)
bottomHorznLayout->addLayout(InnerVerticalLayout3,15); //(15% stretch)
bottomHorznLayout->addLayout(InnerVerticalLayout4,15); //(15% stretch)
bottomHorznLayout->addLayout(InnerVerticalLayout5,40); //(40% stretch)
topMostVerticalLayout->addLayout(upperHorznLayout,3); //(30% stretch)
topMostVerticalLayout->addLayout(bottomHorznLayout,7); //(70% stretch)
this->setLayout(topMostVerticalLayout);

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

How to remove spacing inside a gridLayout (QT)?

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

How to prevent QPushButtons from changing their position while maximizing the window

I am making a kind of imageviewer in which we can see the histogram and hsv of the loaded image. Code is running as expected except whenever I am maximizing the window, all QPushButtons get misplaced.
from PyQt4 import QtGui, QtCore
import cv2
import numpy as np
from matplotlib import pyplot as plt
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
hbox = QtGui.QHBoxLayout(self)
top = QtGui.QFrame(self)
top.setFrameShape(QtGui.QFrame.StyledPanel)
bottomleft = QtGui.QFrame(self)
bottomleft.setFrameShape(QtGui.QFrame.StyledPanel)
bottomright = QtGui.QFrame(self)
bottomright.setFrameShape(QtGui.QFrame.StyledPanel)
splitter1 = QtGui.QSplitter(QtCore.Qt.Vertical)
splitter1.addWidget(top)
splitter2 = QtGui.QSplitter(QtCore.Qt.Horizontal)
splitter2.addWidget(bottomleft)
splitter2.addWidget(bottomright)
splitter1.addWidget(splitter2)
hbox.addWidget(splitter1)
splitter1.setSizes([190,220])
splitter2.setSizes([400,360])
self.setLayout(hbox)
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
self.setGeometry(600, 120, 990, 850)
self.setWindowTitle('PIMAD')
self.setWindowIcon(QtGui.QIcon('imag.jpg'))
self.show()
browseButton = QtGui.QPushButton("Browse")
browseButton.clicked.connect(self.loadFromFile)
browseButton.setToolTip('click to <b>Browse</b>')
histButton = QtGui.QPushButton("")
histButton.setToolTip('For image <b>Histogram</b> click here')
histButton.setIcon(QtGui.QIcon('download.jpg'))
histButton.setIconSize(QtCore.QSize(55,35))
histButton.clicked.connect(self.loadFromHist)
hsvButton = QtGui.QPushButton("")
hsvButton.clicked.connect(self.loadFromHsv)
hsvButton.setToolTip('For <b>Image HSV </b> click here')
hsvButton.setIcon(QtGui.QIcon('hsv.jpg'))
hsvButton.setIconSize(QtCore.QSize(50,35))
self.lbl= QtGui.QLabel()
self.lbl.setScaledContents(True)
bottomleftLayout = QtGui.QHBoxLayout()
self.lbl.setFixedSize(470, 480)
self.lbl2 = QtGui.QLabel()
self.lbl2.setScaledContents(True)
bottomrightLayout = QtGui.QHBoxLayout()
self.lbl3 = QtGui.QLabel()
self.lbl3.setScaledContents(True)
self.lbl3.setFixedSize(300,250)
self.lbl3.move(650,05)
self.lbl3.setParent(top)
self.image = "C:\New folder (2)\logo.jpeg"
self.pix = QtGui.QPixmap(self.image)
self.lbl3.setPixmap(self.pix)
self.lbl3.show()
topLayout = QtGui.QVBoxLayout()
self.fileName = "\Users\Public\Pictures\Sample Pictures\lord.jpg"
self.pixmap = QtGui.QPixmap(self.fileName)
self.lbl.setPixmap(self.pixmap)
bottomleftLayout.addWidget(self.lbl)
bottomleft.setLayout(bottomleftLayout)
bottomrightLayout.addWidget(self.lbl2)
bottomright.setLayout(bottomrightLayout)
topLayout.addWidget(self.lbl3)
topLayout.addStretch(1)
top.setLayout(topLayout)
topLayout.addStretch(1)
browseButton.setParent(top)
histButton.setParent(top)
hsvButton.setParent(top)
browseButton.move(720,260)
histButton.move(790,260)
hsvButton.move(860,260)
browseButton.resize(60,40)
histButton.resize(60,40)
hsvButton.resize(60,40)
browseButton.show()
histButton.show()
hsvButton.show()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('Example')
main = Example()
main.show()
app.exec_()
You are using both layouts and absolute positioning (with method move), which is a bit weird. You also don't need to use show() that much. It's should only be called on the main window.
Absolute positioning usually don't work well if you want to resize your windows. It is meant to be fixed. The widgets will stay at the same position relatively to the top left corner of the window. If the windows becomes smaller, some widgets will be hidden, and if the windows becomes larger, the new space won't be used.
Layouts are meant to be flexible. You can arrange your widgets on vertical or horizontal lines (QHBoxLayout and QVBoxLayout) or on a grid (QGridLayout). Layouts will filled all the space available: they work on any window size.
I'd suggest you re-start from scratch by following a tutorial (a good one is ZetCode Layout management in PyQt4). Try to start simple and progressively add more elements in your layout. Test at every steps to resize the window to see if it works as intended.

Categories