Dynamic positioning of widgets in PyQt5? - python

I want my window to have this layout:
The graph will be from matplotlib.
I want a way of dynamically creating this layout, so that it would fit to any sized screen without changing the basic layout. How do I do this? This is what I've got so far:
import sys
from PyQt5.QtWidgets import (QWidget, QPushButton,
QHBoxLayout, QVBoxLayout, QApplication, QFrame, QScrollArea, QListWidget, QListWidgetItem, QLabel)
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
#top left downwards
vbox = QVBoxLayout()
vbox.addStretch(1)
hbox = QHBoxLayout()
hbox.addStretch(1)
hbox.addLayout(vbox)
hbox.setDirection(1)
self.setLayout(hbox)
qlist = QListWidget()
hbox.addWidget(qlist)
for i in range(0,31):
qlist.addItem(str(i))
qlist.setFrameStyle(QFrame.Raised)
vbox2 = QVBoxLayout()
vbox2.addStretch()
graph = QFrame(self)
graph.setStyleSheet("QWidget { background-color: red }" )
vbox2.addWidget(graph)
graph.setFrameShape(1)
self.setStyleSheet("font: 20pt Cambria")
self.showMaximized()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Which gives me this:
Anyone know how to do this? I'm new to PyQt5 and am not sure how box layouts work after looking at multiple examples. Is there an easier way of doing this without the layout being disturbed?
Thanks!

The first thing you have to do is design your project, for example in your case you have two elements placed vertically: the red QFrame and something underneath, that something underneath commands an element handling the horizontal position so that below it must be a QHBoxLayout, and the element which places the QFrame and is something below it must be a QVBoxLayout, that is your simple structure that you could express it using the following scheme
QWidget
└── QVBoxLayout
├── QFrame
└── QHBoxLayout
└── QListWidget (with stretch = 1)
I prefer to create the widgets first and finally add it to the respective layouts.
import sys
from PyQt5 import QtWidgets
class Example(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# create widgets
graph = QtWidgets.QFrame()
graph.setFixedHeight(40)
graph.setFrameShape(QtWidgets.QFrame.Box)
graph.setStyleSheet("QWidget { background-color: red }" )
qlist = QtWidgets.QListWidget()
qlist.setFrameStyle(QtWidgets.QFrame.Raised)
qlist.addItems(map(str, range(31)))
# create QHBoxLayout and add QListWidget
hlay = QtWidgets.QHBoxLayout()
hlay.addWidget(qlist)
hlay.addStretch(1)
# create QVBoxLayout and add QFrame and QHBoxLayout
vlay = QtWidgets.QVBoxLayout(self)
vlay.addWidget(graph)
vlay.addLayout(hlay)
self.setStyleSheet("font: 20pt Cambria")
self.showMaximized()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Note: The QFrame does not have a certain height because its task is to take the task of the content, in this case there is not so we must establish a fixed size, if you are going to add elements to the QFrame do it through a layout and eliminate the line of setFixedHeight().

Related

PyQT layouts: why does QVBoxLayout stack the buttons instead of creating the layout? [duplicate]

I'm making an application in PyQt4 and this is my code so far:
import sys
from PyQt4 import QtGui, QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.initUi()
def initUi(self):
self.setWindowTitle('Main Menu')
self.setFixedSize(1200, 625)
self.firstWidgets()
self.show()
def firstWidgets(self):
self.vbox1 = QtGui.QVBoxLayout()
self.task1 = QtGui.QLabel('Check 1', self)
self.task1CB = QtGui.QCheckBox(self)
self.hbox1 = QtGui.QHBoxLayout()
self.hbox1.addWidget(self.task1)
self.hbox1.addWidget(self.task1CB)
self.vbox1.addLayout(self.hbox1)
self.setLayout(self.vbox1)
def main():
application = QtGui.QApplication(sys.argv)
gui = MainWindow()
sys.exit(application.exec_())
if __name__=='__main__':
main()
My problem is in MainWindow.firstWidgets(). I try to set a layout but I get an error even though that's my first time using .setLayout for that form, which confuses me.
QWidget::setLayout: Attempting to set QLayout "" on MainWindow "",
which already has a layout
You can't set a QLayout directly on the QMainWindow. You need to create a QWidget and set it as the central widget on the QMainWindow and assign the QLayout to that.
wid = QtGui.QWidget(self)
self.setCentralWidget(wid)
layout = QtGui.QVBoxLayout()
wid.setLayout(layout)
NOTE: This is for Qt4 -- see the other answer on this question for the Qt5 updated code.
Just an update to Brenden Abel's answer:
QWidget and QVBoxLayout (for Python3, PyQt5) are now contained in the PyQt5.QtWidgets module and not the PyQt5.QtGui module.
So updated code:
wid = QtWidgets.QWidget(self)
self.setCentralWidget(wid)
layout = QtWidgets.QVBoxLayout()
wid.setLayout(layout)
This is an example using PyQt5
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QPushButton, QWidget
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('My App')
# Cannot set QxxLayout directly on the QMainWindow
# Need to create a QWidget and set it as the central widget
widget = QWidget()
layout = QVBoxLayout()
b1 = QPushButton('Red' ); b1.setStyleSheet("background-color: red;")
b2 = QPushButton('Blue' ); b2.setStyleSheet("background-color: blue;")
b3 = QPushButton('Yellow'); b3.setStyleSheet("background-color: yellow;")
layout.addWidget(b1)
layout.addWidget(b2)
layout.addWidget(b3)
widget.setLayout(layout)
self.setCentralWidget(widget)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

PyQt5 add QScrollArea to a Frame or a Widget

I want to hide a QScrollArea which displays pages of PDF files. I know how to hide widgets and frames, and so I want to add the QScrollArea to either one of them so that it can be hidden. I tried setStyleSheet ('height:0px;'), which doesn't work.
This is the sample Code taken from https://www.learnpyqt.com/tutorials/qscrollarea/#adding%20a%20qscrollarea%20from%20code. Specific to this code how do I add self.scroll to a frame or a widget?
import sys
from PyQt5 import QtCore, QtGui, QtWidgets, uic, QtMultimediaWidgets, QtWebEngineWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.Qt import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.scroll = QScrollArea() # Scroll Area which contains the widgets, set as the centralWidget
self.widget = QWidget() # Widget that contains the collection of Vertical Box
self.vbox = QVBoxLayout() # The Vertical Box that contains the Horizontal Boxes of labels and buttons
self.pageLabel0 = QLabel()
self.pageLabel0.setAlignment(Qt.AlignCenter)
self.pageLabel0PageImage = QPixmap('pdfs/page_0.png').scaledToWidth(self.width(), Qt.SmoothTransformation)
self.pageLabel0.setPixmap(self.pageLabel0PageImage)
self.vbox.addWidget(self.pageLabel0)
self.pageLabel1 = QLabel()
self.pageLabel1.setAlignment(Qt.AlignCenter)
self.pageLabel1PageImage = QPixmap('pdfs/page_1.png').scaledToWidth(self.width(), Qt.SmoothTransformation)
self.pageLabel1.setPixmap(self.pageLabel1PageImage)
self.vbox.addWidget(self.pageLabel1)
self.widget.setLayout(self.vbox)
#Scroll Area Properties
self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.scroll.setWidgetResizable(True)
self.scroll.setWidget(self.widget)
self.setCentralWidget(self.scroll)
self.setGeometry(600, 100, 1000, 900)
self.setWindowTitle('Scroll Area Demonstration')
self.show()
return
def main():
app = QtWidgets.QApplication(sys.argv)
main = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Just add self.scroll.hide() to your code.

PyQt5 Layouts not moving button [duplicate]

I'm making an application in PyQt4 and this is my code so far:
import sys
from PyQt4 import QtGui, QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.initUi()
def initUi(self):
self.setWindowTitle('Main Menu')
self.setFixedSize(1200, 625)
self.firstWidgets()
self.show()
def firstWidgets(self):
self.vbox1 = QtGui.QVBoxLayout()
self.task1 = QtGui.QLabel('Check 1', self)
self.task1CB = QtGui.QCheckBox(self)
self.hbox1 = QtGui.QHBoxLayout()
self.hbox1.addWidget(self.task1)
self.hbox1.addWidget(self.task1CB)
self.vbox1.addLayout(self.hbox1)
self.setLayout(self.vbox1)
def main():
application = QtGui.QApplication(sys.argv)
gui = MainWindow()
sys.exit(application.exec_())
if __name__=='__main__':
main()
My problem is in MainWindow.firstWidgets(). I try to set a layout but I get an error even though that's my first time using .setLayout for that form, which confuses me.
QWidget::setLayout: Attempting to set QLayout "" on MainWindow "",
which already has a layout
You can't set a QLayout directly on the QMainWindow. You need to create a QWidget and set it as the central widget on the QMainWindow and assign the QLayout to that.
wid = QtGui.QWidget(self)
self.setCentralWidget(wid)
layout = QtGui.QVBoxLayout()
wid.setLayout(layout)
NOTE: This is for Qt4 -- see the other answer on this question for the Qt5 updated code.
Just an update to Brenden Abel's answer:
QWidget and QVBoxLayout (for Python3, PyQt5) are now contained in the PyQt5.QtWidgets module and not the PyQt5.QtGui module.
So updated code:
wid = QtWidgets.QWidget(self)
self.setCentralWidget(wid)
layout = QtWidgets.QVBoxLayout()
wid.setLayout(layout)
This is an example using PyQt5
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QPushButton, QWidget
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('My App')
# Cannot set QxxLayout directly on the QMainWindow
# Need to create a QWidget and set it as the central widget
widget = QWidget()
layout = QVBoxLayout()
b1 = QPushButton('Red' ); b1.setStyleSheet("background-color: red;")
b2 = QPushButton('Blue' ); b2.setStyleSheet("background-color: blue;")
b3 = QPushButton('Yellow'); b3.setStyleSheet("background-color: yellow;")
layout.addWidget(b1)
layout.addWidget(b2)
layout.addWidget(b3)
widget.setLayout(layout)
self.setCentralWidget(widget)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

widget position and window size

I am new to GUI development and I am trying to learn how to use pyqt5 in python.
Below is the example code I am working on. I want a window with some checkboxes,combobox and radiobuttons on the right side of the window. The details of the QtWidget objects are in the code.
from PyQt5.QtWidgets import QApplication, QMainWindow, QSizePolicy, QWidget, QPushButton, QComboBox, QRadioButton, QVBoxLayout, QCheckBox
from PyQt5.QtGui import QIcon
class App(QMainWindow):
def __init__(self):
super().__init__()
self.left = 0
self.top = 500
self.title = 'Chip2 Torque Data'
self.width = 500
self.height =500
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
xselect=QRadioButton("X",self)
xselect.setChecked(True)
xselect.move(340,400)
zselect=QRadioButton("Z",self)
zselect.move(380,400)
sselect=QRadioButton("SP1",self)
sselect.move(420,400)
pass_list=QComboBox(self)
pass_list.addItems(sheets_idealcut)
pass_list.move(340,300)
rawdata_check=QCheckBox("Raw Data",self)
rawdata_check.setChecked(True)
rawdata_check.move(340,200)
mvgavg_check=QCheckBox("Moving average",self)
mvgavg_check.setChecked(True)
mvgavg_check.move(380,200)
mvgstd_check=QCheckBox("Moving stdev",self)
mvgstd_check.setChecked(True)
mvgstd_check.move(420,200)
self.show()
if __name__ == '__main__':
  sheets_idealcut=['pass2','pass3','pass4','pass5']
app = QApplication.instance()
if app is None:
app = QApplication(sys.argv)
else:
print('QApplication instance already exists: %s' % str(app))
ex = App()
#ex.show()
app.exec_()
Below is the output of the code. (Please ignore the overlapping checkbox names. I intend to correct it later.)
My problem is when I maximise this window, the QtWidget objects(QComboBox,QRadioButton,QCheckBox) don't readjust their position with the new window size.
So for the widgets to reposition themselves automatically, what method should be used ?
I googled it but I couldn't find anything useful.
Please help.
You are using move(x,y) which sets the Widgets to a specific fixed position. So you could write a function which is called on maximizing the window and then reposition your widgets accordingly.
Another automated approach is to use box layouts. QtGui.QHBoxLayout and QtGui.QVBoxLayout are basic layout classes which lines up widgets horizontally or vertically. From your pictures I do not know what exact positions you want your widgets to end up, so I try to explain it like I think you want it.
Each row of elements can be assigned into a HBox like this:
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(rawdata_check)
hbox.addWidget(mvgavg_check)
hbox.addWidget(mvgstd_check)
If you then resize the window the widgets should automaticly be repositioned to the right side of the window. The QtDesigner can help you arranging all the elements.
You should be using layouts for dynamic sizing. There are alot of different layouts. The layouts used in this example is the most basic types. I also changed the parent of "App" to a QDialog. They are easier to handle.
Here is an example of your app, using layouts:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QSizePolicy, QWidget, QPushButton, QComboBox, QRadioButton, \
QVBoxLayout, QCheckBox, QHBoxLayout, QGroupBox, QDialog
from PyQt5.QtGui import QIcon
class App(QDialog):
def __init__(self):
super().__init__()
self.left = 0
self.top = 500
self.title = 'Chip2 Torque Data'
self.width = 500
self.height = 500
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.main_layout = QVBoxLayout()
xselect=QRadioButton("X",self)
xselect.setChecked(True)
# xselect.move(340,400)
self.main_layout.addWidget(xselect)
zselect=QRadioButton("Z",self)
# zselect.move(380,400)
self.main_layout.addWidget(zselect)
sselect=QRadioButton("SP1",self)
# sselect.move(420,400)
self.main_layout.addWidget(sselect)
pass_list=QComboBox(self)
pass_list.addItems(sheets_idealcut)
self.main_layout.addWidget(pass_list)
#pass_list.move(340,300)
rawdata_check=QCheckBox("Raw Data",self)
rawdata_check.setChecked(True)
self.main_layout.addWidget(rawdata_check)
#rawdata_check.move(340,200)
mvgavg_check=QCheckBox("Moving average",self)
mvgavg_check.setChecked(True)
#mvgavg_check.move(380,200)
mvgstd_check=QCheckBox("Moving stdev",self)
mvgstd_check.setChecked(True)
#mvgstd_check.move(420,200)
self.check_group = QHBoxLayout()
self.check_group.addWidget(mvgavg_check)
self.check_group.addWidget(mvgstd_check)
self.check_group.stretch(1)
self.main_layout.addLayout(self.check_group)
self.setLayout(self.main_layout)
self.show()
if __name__ == '__main__':
sheets_idealcut=['pass2','pass3','pass4','pass5']
app = QApplication.instance()
if app is None:
app = QApplication(sys.argv)
else:
print('QApplication instance already exists: %s' % str(app))
ex = App()
#ex.show()
app.exec_()

PyQt5 Unable to Center Align Text in Qlabel

I'm using pyqt for the first time and I am trying to figure out how to get the text "example.py" in my Qlabel to align vertically. In this code I have set the Qlabel to a fixed height of 35, but the text is not sitting center between the top of the window and the top of the splitter below it.
If I set the fixed height of the Qlabel to 20, that gets a little closer to things looking vertically aligned (but not totally) but also introduces the problem of some of the bottom of the test "example.py" getting cut off.
Maybe it has something to do with some margin or something above the splitter section... Makes me think that it could be the case and that's why the text is getting cut off at the bottom. In either case I am not sure how to address the issue.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import os, sys
from PyQt5.QtWidgets import (QApplication, QWidget, QFrame, QSplitter, QStyleFactory,
QHBoxLayout, QVBoxLayout, QLabel)
from PyQt5.QtCore import Qt
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
#self.showFullScreen()
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
topbar = QLabel("example.py", self)
topbar.setAlignment(Qt.AlignVCenter)
topbar.setIndent(20)
topbar.setFixedHeight(35)
layout.addWidget(topbar)
v_left = QFrame(self)
v_left.setFrameShape(QFrame.StyledPanel)
v_middle = QFrame(self)
v_middle.setFrameShape(QFrame.StyledPanel)
v_right = QFrame(self)
v_right.setFrameShape(QFrame.StyledPanel)
splitter = QSplitter(Qt.Horizontal)
splitter.setHandleWidth(0)
splitter.addWidget(v_left)
splitter.addWidget(v_middle)
splitter.addWidget(v_right)
layout.addWidget(splitter)
self.setLayout(layout)
self.setGeometry(100, 100, 1000, 800)
self.setWindowTitle('PiePy')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
I found the solution to my issue while I was working on setting up transparency. I added the line self.setAttribute(Qt.WA_NoSystemBackground, True) to my code and that made it clear in the application that there is indeed a space between the Qlabel and the splitter below it.
The solution was a simple one I just added layout.setSpacing(0) to the code and that took care of it.
You are using a QVBoxLayout where it will obey its default behavior, try putting your topbar inside a QHBoxLayout and adding this layout to your main layout(QVBoxLayout). Once you have this configuration add a stretch in each side of your topbar inside the QHBoxLayout to make it goes to the middle.
You would have something like that, from your code:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import os, sys
from PyQt5.QtWidgets import (QApplication, QWidget, QFrame, QSplitter, QStyleFactory,
QHBoxLayout, QVBoxLayout, QLabel)
from PyQt5.QtCore import Qt
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
#self.showFullScreen()
layout = QVBoxLayout()
layout_title = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
topbar = QLabel("example.py", self)
topbar.setAlignment(Qt.AlignVCenter)
topbar.setIndent(20)
topbar.setFixedHeight(35)
layout_title.addStretch(-1)
layout_title.addWidget(topbar)
layout_title.addStretch(-1)
layout.addLayout(layout_title)
v_left = QFrame(self)
v_left.setFrameShape(QFrame.StyledPanel)
v_middle = QFrame(self)
v_middle.setFrameShape(QFrame.StyledPanel)
v_right = QFrame(self)
v_right.setFrameShape(QFrame.StyledPanel)
splitter = QSplitter(Qt.Horizontal)
splitter.setHandleWidth(0)
splitter.addWidget(v_left)
splitter.addWidget(v_middle)
splitter.addWidget(v_right)
layout.addWidget(splitter)
self.setLayout(layout)
self.setGeometry(100, 100, 1000, 800)
self.setWindowTitle('PiePy')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
The logic is that you have something like that:
# [---------------------------------] <-main_layout (QVBoxLayout)
# item1 [---stretch----topbar----stretch] <-layout_title (QHBoxLayout)
# item2 [------------QSplitter----------]
# [---------------------------------] <-main_layout (QVBoxLayout)

Categories