PQYT Make QGridLayout scrollable - python

I was wondering if it is possible to add a QScrollArea to a QGridLayout? Below is my attempt however, it always fails with the error.
TypeError: setWidget(self, QWidget): argument 1 has unexpected type 'QGridLayout'
Is this method only possible for combo and list boxes? I am basically passing QPixmap images into a QGridLayout which needs to be scrolled. Any help much appreciated thank you.
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class PicClip(QWidget):
def __init__(self):
super().__init__()
# Load image
self.im = QPixmap("1.jpg")
self.im1 = QPixmap("1.jpg")
# Label 1
self.label = QLabel()
self.label.setPixmap(self.im)
# Label 2
self.label1 = QLabel()
self.label1.setPixmap(self.im1)
# Make Grid
self.grid = QGridLayout()
# Create widgets to grid
self.grid.addWidget(self.label, 0, 1, alignment=Qt.AlignCenter)
self.grid.addWidget(self.label1, 1, 1, alignment=Qt.AlignCenter)
# Set layout of Grid
self.setLayout(self.grid)
# Scroll
scroll = QScrollArea()
scroll.setWidget(self.grid)
scroll.setWidgetResizable(True)
# Show
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = PicClip()
sys.exit(app.exec_())

self.grid is layout and you are trying to add layout using setWidget function. setWidget function only gets QWidget
simple trick is to add wrapper QWidget and set self.grid as its layout
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class PicClip(QMainWindow): # I modified this line to show window properly
def __init__(self):
super().__init__()
# Load image
self.im = QPixmap("1.jpg")
self.im1 = QPixmap("1.jpg")
# Label 1
self.label = QLabel()
self.label.setPixmap(self.im)
# Label 2
self.label1 = QLabel()
self.label1.setPixmap(self.im1)
# Make Grid
self.grid = QGridLayout()
# Create widgets to grid
self.grid.addWidget(self.label, 0, 1, alignment=Qt.AlignCenter)
self.grid.addWidget(self.label1, 1, 1, alignment=Qt.AlignCenter)
# Set layout of Grid
self.setLayout(self.grid)
# Scroll
scroll = QScrollArea()
# add wrapper widget and set its layout
wrapper_widget = QWidget()
wrapper_widget.setLayout(self.grid)
scroll.setWidget(wrapper_widget)
scroll.setWidgetResizable(True)
# Show
self.setCentralWidget(scroll) # I modified this line to show window properly
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = PicClip()
sys.exit(app.exec_())

Related

PyQt5 Python Scroll area can't scroll it's content

I can't make scroll area scrolling.
Added into it QLabel and grid with 100 lines.
You can see code and screenshot bellow.
Does someone know how to add grid into scrolling area?
import sys
from PyQt5 import QtWidgets
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 960, 820)
self.setMinimumWidth(960)
self.setMinimumHeight(820)
self.user_interface()
def user_interface(self):
self.setFont(QFont('Times', 11))
# 100 lines grid
grid = QGridLayout()
for i in range(0, 100):
for j in range(0, 1):
grid.addWidget(QLabel("Hello There"), i, j)
grid.addWidget(QLabel("General Kenobi"), i, j + 1)
# scroll layout with QLabel and 100 lines grid
scroll_layout = QVBoxLayout()
scroll_layout.addWidget(QLabel("Scroll me down baby"))
scroll_layout.addLayout(grid)
scroll_layout.addStretch()
# Creation of scroll area and set scroll layout as its layout
scroll_area = QScrollArea()
scroll_area.setLayout(scroll_layout)
scroll_area.setWidgetResizable(False)
# main layout
main_layout = QtWidgets.QVBoxLayout()
main_layout.addWidget(QLabel("I stay on my place"))
main_layout.addWidget(scroll_area)
# application GUI setup
central_widget = QtWidgets.QWidget(self)
self.setCentralWidget(central_widget)
central_widget.setLayout(main_layout)
self.show()
def main():
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec())
if __name__ == '__main__':
main()
Here is screenshot:
Thanks guys!
Found solution for my question ->
https://www.pythonguis.com/tutorials/qscrollarea/

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.

Resizing QGraphicsScene when layout changes [duplicate]

This question already has an answer here:
Coordinates of an image PyQt
(1 answer)
Closed 2 years ago.
I'm using a QGraphicsScene in PySide2, and I want to resize its contents when the scene resizes. I'm able to trigger the resize when the main window resizes, but I can't figure out how to trigger it when the contents of the window change.
Here's a small example where I have a graphics scene and two push buttons. In the scene is a circle that should just touch the edges. When I click one of the buttons, it shows or hides the other button.
import sys
from PySide2.QtGui import QResizeEvent
from PySide2.QtWidgets import (QPushButton, QApplication, QVBoxLayout, QDialog,
QSizePolicy, QGraphicsScene, QGraphicsView)
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Create widgets
self.scene = QGraphicsScene()
self.circle = self.scene.addEllipse(0, 0, 10, 10)
self.extra = QGraphicsView(self.scene)
self.extra.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
self.button1 = QPushButton("Button 1")
self.button2 = QPushButton("Button 2")
# Create layout and add widgets
layout = QVBoxLayout()
layout.addWidget(self.extra)
layout.addWidget(self.button1)
layout.addWidget(self.button2)
# Set dialog layout
self.setLayout(layout)
# Add button signal to greetings slot
self.button1.clicked.connect(self.on_click1)
self.button2.clicked.connect(self.on_click2)
def on_click1(self):
self.button2.setVisible(not self.button2.isVisible())
self.resizeEvent()
def on_click2(self):
self.button1.setVisible(not self.button1.isVisible())
self.resizeEvent()
def resizeEvent(self, event: QResizeEvent = None):
size = self.extra.maximumViewportSize()
target_size = min(size.width(), size.height()) - 1
self.circle.setRect((size.width() - target_size) // 2,
(size.height() - target_size) // 2,
target_size,
target_size)
self.extra.scene().setSceneRect(0, 0, size.width(), size.height())
if __name__ == '__main__':
# Create the Qt Application
app = QApplication(sys.argv)
# Create and show the form
form = Form()
form.show()
# Run the main Qt loop
sys.exit(app.exec_())
The scene is resized when the main window resizes and when one of the buttons disappears or reappears. The circle adjusts its size when the main window resizes, but not when the buttons change.
How can I handle the resizing when the buttons change? I'd like to keep the circle just touching the edge of the display as it resizes.
The problem was that I was handling the resize of the main window, and not the graphics view. Create a new subclass of QGraphicsView, and handle the resize event there. It will catch both kinds of resize event.
import sys
from PySide2.QtGui import QResizeEvent
from PySide2.QtWidgets import (QPushButton, QApplication, QVBoxLayout, QDialog,
QSizePolicy, QGraphicsScene, QGraphicsView, QWidget)
class ResizingView(QGraphicsView):
def __init__(self, parent: QWidget = None):
super().__init__(parent)
self.setScene(QGraphicsScene())
self.circle = self.scene().addEllipse(0, 0, 1, 1)
def resizeEvent(self, event: QResizeEvent):
super().resizeEvent(event)
size = event.size()
target_size = min(size.width(), size.height()) - 1
x = (size.width() - target_size) // 2
y = (size.height() - target_size) // 2
self.circle.setRect(x, y, target_size, target_size)
self.setSceneRect(0, 0, size.width(), size.height())
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Create widgets
self.extra = ResizingView()
self.extra.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
self.button1 = QPushButton("Button 1")
self.button2 = QPushButton("Button 2")
# Create layout and add widgets
layout = QVBoxLayout()
layout.addWidget(self.extra)
layout.addWidget(self.button1)
layout.addWidget(self.button2)
# Set dialog layout
self.setLayout(layout)
# Add button signal to greetings slot
self.button1.clicked.connect(self.on_click1)
self.button2.clicked.connect(self.on_click2)
def on_click1(self):
self.button2.setVisible(not self.button2.isVisible())
def on_click2(self):
self.button1.setVisible(not self.button1.isVisible())
if __name__ == '__main__':
# Create the Qt Application
app = QApplication(sys.argv)
# Create and show the form
form = Form()
form.show()
# Run the main Qt loop
sys.exit(app.exec_())
If you don't want to move all the resize logic into the graphics view, create a new signal on your subclass, and connect it to a slot on whatever class should handle the resize event. If this still isn't working for you, check that you're calling setSceneRect() on the graphics view.
If you don't like recalculating the sizes of everything, you may prefer the simpler approach of just scaling everything with the fitInView() method. It's certainly easier, but I prefer the finer control you can have with a full set of size calculations. Here's an example of using fitInView().
import sys
from PySide2.QtCore import Qt
from PySide2.QtGui import QResizeEvent, QColor
from PySide2.QtWidgets import (QPushButton, QApplication, QVBoxLayout, QDialog,
QSizePolicy, QGraphicsScene, QGraphicsView, QWidget)
class ResizingView(QGraphicsView):
def __init__(self, parent: QWidget = None):
super().__init__(scene=QGraphicsScene(), parent=parent)
self.circle = self.scene().addEllipse(1, 1, 98, 98)
self.scene().addSimpleText('Centre').setPos(50, 50)
self.scene().setBackgroundBrush(QColor('green'))
self.scene().setSceneRect(0, 0, 100, 100)
def resizeEvent(self, event: QResizeEvent):
self.fitInView(0, 0, 100, 100, Qt.KeepAspectRatio)
super().resizeEvent(event)
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Create widgets
self.extra = ResizingView()
self.extra.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
self.button1 = QPushButton("Button 1")
self.button2 = QPushButton("Button 2")
# Create layout and add widgets
layout = QVBoxLayout()
layout.addWidget(self.extra)
layout.addWidget(self.button1)
layout.addWidget(self.button2)
# Set dialog layout
self.setLayout(layout)
# Add button signal to greetings slot
self.button1.clicked.connect(self.on_click1)
self.button2.clicked.connect(self.on_click2)
def on_click1(self):
self.button2.setVisible(not self.button2.isVisible())
def on_click2(self):
self.button1.setVisible(not self.button1.isVisible())
if __name__ == '__main__':
# Create the Qt Application
app = QApplication(sys.argv)
# Create and show the form
form = Form()
form.show()
# Run the main Qt loop
sys.exit(app.exec_())
Either way, the key thing is to subclass QGraphicsView and handle its resizeEvent() instead of the one on the main form.

how to let the layout showed in the bottom

The window would have two layouts: A, the main layout to show some images; B, the qlabel to show some status. When the window is started, A would be empty. A would show some images after some operations.
My question is: how to let B show in the bottom? The current code is:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class TimeLine(QMainWindow):
def __init__(self):
super(TimeLine, self).__init__()
widget = QWidget()
self.setCentralWidget(widget)
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
widget.setLayout(layout)
statusLabel = QLabel('status bar')
statusLabel.setStyleSheet("background-color:blue")
statusLabel.setMaximumHeight(20)
layout.addWidget(statusLabel)
layout.setSpacing(0)
app = QApplication(sys.argv)
window = TimeLine()
window.show()
app.exec_()
And the result is:
The blue label shows in the middle, and I want it to be show in the bottom.
Then, I add a stretch. The layout B would show in the bottom, however, the layout A would also be squeezed. The code is:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class TimeLine(QMainWindow):
def __init__(self):
super(TimeLine, self).__init__()
widget = QWidget()
self.setCentralWidget(widget)
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
widget.setLayout(layout)
mainLabel = QLabel()
mainLabel.setStyleSheet("background-color:yellow")
statusLabel = QLabel()
statusLabel.setStyleSheet("background-color:blue")
statusLabel.setMaximumHeight(20)
layout.addWidget(mainLabel)
layout.addStretch()
layout.addWidget(statusLabel)
layout.setSpacing(0)
app = QApplication(sys.argv)
window = TimeLine()
window.show()
app.exec_()
And the result is:
The yellow label is the main label, the gray color represents the back ground. I want the yellow label to occupy all the window except from the blue label.
Thus, what I want is:
when there is no other label, I hope the blue label show in the bottom, like:
where there is another label (yellow label), I hope the yellow label occupy all the space except from the blue label, like:
I saw your task like this:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class TimeLine(QMainWindow):
def __init__(self):
super(TimeLine, self).__init__()
widget = QWidget()
self.setCentralWidget(widget)
mainLabel = QLabel("This is a label.", alignment = Qt.AlignCenter)
mainLabel.setStyleSheet("background-color:yellow")
layout = QVBoxLayout(widget)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(mainLabel)
self.statusBar().setStyleSheet("background-color:blue; color: #fff")
self.statusBar().showMessage('Hello {} !'.format('status bar'))
if __name__ == "__main__":
app = QApplication(sys.argv)
window = TimeLine()
window.show()
sys.exit(app.exec_())
If I understand your question correctly, you want the blue label to be aligned with the bottom edge of the main window at all times. One way to get what you want to to set the alignment of the label to Qt.AlignBottom when adding it to the layout. You also need to adjust the stretch factors of the two labels to make sure the yellow label takes up all the space above the blue label when it's present, i.e.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
class TimeLine(QMainWindow):
def __init__(self):
super(TimeLine, self).__init__()
widget = QWidget()
self.setCentralWidget(widget)
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
widget.setLayout(layout)
mainLabel = QLabel()
mainLabel.setStyleSheet("background-color:yellow")
statusLabel = QLabel('status bar')
statusLabel.setStyleSheet("background-color:blue")
statusLabel.setMaximumHeight(20)
layout.addWidget(mainLabel, stretch = 1)
layout.addWidget(statusLabel, stretch = 0, alignment = Qt.AlignBottom)
layout.setSpacing(0)
app = QApplication(sys.argv)
window = TimeLine()
window.show()
app.exec_()
Both labels:
Blue label only:

How to overlay widgets in PyQt5?

I would like to render some layouts - that groups some widgets - over a background layout that contains an image widget.
Let me explain myself with an image. The following image shows the desired output:
However, all I haven't find the way to overlay the two image widgets, and they show like this:
And this is the Python code I wrote:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5 import QtCore, Qt
import sys
class ExampleWindow(QMainWindow):
def __init__(self, windowsize):
super().__init__()
self.windowsize = windowsize
self.initUI()
def initUI(self):
self.setGeometry(0, 0, self.windowsize.width(), self.windowsize.height())
self.setMaximumSize(self.windowsize.width(), self.windowsize.height())
self.setMinimumSize(self.windowsize.width(), self.windowsize.height())
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.FramelessWindowHint)
widget = QWidget()
self.setCentralWidget(widget)
# Picture 1's widget
pixmap = QPixmap('picture_1.jpg')
pixmap = pixmap.scaledToWidth(self.windowsize.width())
self.image = QLabel()
self.image.setPixmap(pixmap)
left_box = QVBoxLayout()
left_box.setContentsMargins(0,0,0,0)
left_box.setSpacing(0)
left_box.setAlignment(Qt.Qt.AlignLeft)
left_box.addWidget(self.image, 0, Qt.Qt.AlignLeft | Qt.Qt.AlignTop)
# Picture 2's widget
pixmap = QPixmap('picture_2.jpg')
pixmap = pixmap.scaledToWidth(400)
self.image2 = QLabel()
self.image2.setPixmap(pixmap)
right_box = QVBoxLayout()
right_box.setContentsMargins(0, 0, 0, 0)
right_box.setSpacing(0)
right_box.setAlignment(Qt.Qt.AlignLeft)
right_box.addWidget(self.image2, 0, Qt.Qt.AlignLeft | Qt.Qt.AlignBottom)
layout_box = QHBoxLayout()
layout_box.addLayout(left_box)
layout_box.addLayout(right_box)
widget.setLayout(layout_box)
if __name__ == '__main__':
app = QApplication(sys.argv)
screensize = app.desktop().availableGeometry()
ex = ExampleWindow(screensize)
ex.show()
sys.exit(app.exec_())
Could you please help me to modify my code in order to get the widgets overlaid as shown on first image?
Layouts are elements that manage the geometry of widgets, in your case both layouts try to occupy the largest available space. For a widget to be part of another widget there are 2 possibilities, the first is to use a layout, and the second is that the main widget is the father of the new widget, in the case of the first image we will use the first one, and for the second one we will use the second method.
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class ExampleWindow(QMainWindow):
def __init__(self, windowsize):
super().__init__()
self.windowsize = windowsize
self.initUI()
def initUI(self):
self.setFixedSize(self.windowsize)
self.setWindowFlags(Qt.CustomizeWindowHint | Qt.FramelessWindowHint)
widget = QWidget()
self.setCentralWidget(widget)
pixmap1 = QPixmap('picture_1.jpg')
pixmap1 = pixmap1.scaledToWidth(self.windowsize.width())
self.image = QLabel()
self.image.setPixmap(pixmap1)
layout_box = QHBoxLayout(widget)
layout_box.setContentsMargins(0, 0, 0, 0)
layout_box.addWidget(self.image)
pixmap2 = QPixmap('picture_2.jpg')
self.image2 = QLabel(widget)
self.image2.setPixmap(pixmap2)
self.image2.setFixedSize(pixmap2.size())
p = self.geometry().bottomRight() - self.image2.geometry().bottomRight() - QPoint(100, 100)
self.image2.move(p)
if __name__ == '__main__':
app = QApplication(sys.argv)
screensize = app.desktop().availableGeometry().size()
ex = ExampleWindow(screensize)
ex.show()
sys.exit(app.exec_())

Categories