Resizing QGraphicsScene when layout changes [duplicate] - python

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.

Related

PQYT Make QGridLayout scrollable

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

PyQt5 - QFrame size is ignored in window

I was creating an application with a QFrame on the left side and a control panel on the right. However, I cannot get the QFrame on the left to be sized properly. I created the following example to demonstrate the problem:
import sys
from PyQt5.QtWidgets import QFrame, QApplication, QWidget, QVBoxLayout, QHBoxLayout, \
QLabel
class MainWindow(QWidget):
"""Main Windows for this demo."""
def __init__(self):
"""Constructor."""
super().__init__()
self.frame = MyFrame(self)
layout_main = QHBoxLayout(self)
layout_left = QVBoxLayout()
layout_right = QVBoxLayout()
layout_main.addLayout(layout_left)
layout_main.addLayout(layout_right)
self.frame.resize(600, 600)
layout_left.addWidget(self.frame)
self.label = QLabel('I am on the right')
layout_right.addWidget(self.label)
# self.setGeometry(300, 100, 900, 900)
self.show()
class MyFrame(QFrame):
"""Custom frame."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setFrameStyle(QFrame.Panel | QFrame.Raised)
self.setStyleSheet('QFrame { background-color: red; }')
def main():
"""Main function."""
app = QApplication([])
window = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I would expect a big red shape on the left but instead I get this:
Resizing the window (either at runtime by dragging or by setting the geometry in code) does resize the QFrame to neatly fill up half the screen. But I want it to have a predefined fixed size.
Why is frame.resize not working as expected?
Found it. Using frame.setFixedSize() does exactly what I want:
class MyFrame(QFrame):
def __init__(self, *args, **kwargs):
self.setFixedSize(300, 300) # < Added this line
The frame keeps it size and if I resize the whole window this is respected:
I am still not sure why resize() does nothing.

QTabWidget insert a QSplitter can't switch when the splitter disabled

I insert a QFrame and QTabWidget in the QSplitter. And I wanna forbidden to adjust the size of elements in QSplitter. So I call method of 'setDisabled' in QSplitter. It's useful for disabling resizing the elements. But I also can't switch tab of QTabWidget. Who can give me some suggestions? Thanks very much......
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QSplitter, QHBoxLayout, QFrame, QTabWidget
from PyQt5.QtCore import Qt
class Example1(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(0, 0, 600, 600)
self.setWindowTitle("Demo")
self.layout = QHBoxLayout()
top_frame = QFrame()
top_frame.setFrameShape(QFrame.StyledPanel)
bottom_frame = QTabWidget(self)
tab1 = QWidget()
tab2 = QWidget()
bottom_frame.setTabText(0, "Generic")
bottom_frame.setTabText(1, "Other")
bottom_frame.addTab(tab1, "Tab 1")
bottom_frame.addTab(tab2, "Tab 2")
splitter = QSplitter()
splitter.setOrientation(Qt.Vertical)
splitter.addWidget(top_frame)
splitter.addWidget(bottom_frame)
splitter.setSizes([300, 300])
**splitter.setDisabled(True)**
self.layout.addWidget(splitter)
self.setLayout(self.layout)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example1()
sys.exit(app.exec_())
the running result of the program
When you disable a widget you also disable its children, so disabling the QSplitter also disables the QTabWidget.
A possible solution is enable or disable the handles:
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QApplication,
QFrame,
QHBoxLayout,
QSplitter,
QSplitterHandle,
QTabWidget,
QWidget,
)
class CustomSplitter(QSplitter):
#property
def enabled(self):
if not hasattr(self, "_enabled"):
self._enabled = True
return self._enabled
#enabled.setter
def enabled(self, d):
self._enabled = d
for i in range(self.count()):
self.handle(i).setEnabled(self.enabled)
def createHandle(self):
handle = super().createHandle()
handle.setEnabled(self.enabled)
return handle
class Example1(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(0, 0, 600, 600)
self.setWindowTitle("Demo")
self.layout = QHBoxLayout()
top_frame = QFrame()
top_frame.setFrameShape(QFrame.StyledPanel)
bottom_frame = QTabWidget(self)
tab1 = QWidget()
tab2 = QWidget()
bottom_frame.setTabText(0, "Generic")
bottom_frame.setTabText(1, "Other")
bottom_frame.addTab(tab1, "Tab 1")
bottom_frame.addTab(tab2, "Tab 2")
splitter = CustomSplitter()
splitter.setOrientation(Qt.Vertical)
splitter.addWidget(top_frame)
splitter.addWidget(bottom_frame)
splitter.setSizes([300, 300])
splitter.enabled = False
self.layout.addWidget(splitter)
self.setLayout(self.layout)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = Example1()
sys.exit(app.exec_())
I haven't used a QSplitter before but are the .setFixedHeight(300) .setFixedWidth(300), or .setFixedSize(300, 300) methods not applicable here?
QSplitter lets you get hold of its handles, which are the GUI element visible to the user. If you have two widgets in a splitter, you have a single visible handle; the handle at index 0 is always invisible.
You can manipulate that widget explicitely, e.g. disable it. Try:
splitter.handle(1).enabled = False
This disables only said GUI element, while the rest of the splitter (your two content widgets) will stay enabled.

PyQt5: attach button position to the window coordinates

So, I'm trying to experiment a bit with PyQt5. I have created a basic window with two QPushButtons.
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow, QSizePolicy
import sys
class Okno(QMainWindow):
def __init__(self):
super(Okno, self).__init__()
self.setGeometry(500, 500, 900, 500) # window size setGeometry(xpos, ypos, w, h)
# self.setFixedSize(900, 500) # fix window position
self.setWindowTitle("My window")
self.label = QtWidgets.QLabel(self)
self.button1 = QtWidgets.QPushButton(self)
self.button2 = QtWidgets.QPushButton(self)
self.iniUI()
# Buttons
def iniUI(self):
self.button1.setText("Open file")
self.button1.move(75, 450)
self.button1.setMinimumWidth(150)
self.button1.clicked.connect(Button_events.open_file) # add (connect) button event
self.button2.setText("Exit")
self.button2.move(750, 450)
self.button2.clicked.connect(exit)
# Button events
class Button_events(QMainWindow):
def open_file(self):
print("Open file")
# Main method
def window():
app = QApplication(sys.argv)
okno = Okno()
okno.show()
sys.exit(app.exec_())
window()
I have set button's position by absolute coordinates. When I resize My Window, the button simply holds its absolute coordinates.
How can I attach the button's coordinates to My Window, so when I scale the window, the button would move within.
Is it possible to set a minimum My Window size with respect to minimum QPushButtons size (when buttons won't fit My Window, it can't be adjusted any smaller)?
Here is a simple screen of what I'm trying to achieve (the original buttons are cut in half, they would disappear if My Window gets any smaller):
Thank you.
I would recommend using a layout to handle the position of all the widgets and minimum size of the window. With QGridLayout, you can align the buttons to the bottom left and bottom right.
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QSizePolicy
import sys
class Okno(QMainWindow):
def __init__(self):
super(Okno, self).__init__()
self.setGeometry(500, 500, 900, 500)
self.setWindowTitle("My window")
self.label = QtWidgets.QLabel()
self.button1 = QtWidgets.QPushButton()
self.button2 = QtWidgets.QPushButton()
self.iniUI()
# Buttons
def iniUI(self):
w = QtWidgets.QWidget()
self.setCentralWidget(w)
grid = QtWidgets.QGridLayout(w)
self.button1.setText("Open file")
self.button1.setMinimumWidth(150)
self.button1.clicked.connect(self.open_file)
self.button2.setText("Exit")
self.button2.clicked.connect(self.close)
grid.addWidget(self.button1, 0, 0, QtCore.Qt.AlignLeft | QtCore.Qt.AlignBottom)
grid.addWidget(self.button2, 0, 1, QtCore.Qt.AlignRight | QtCore.Qt.AlignBottom)
def open_file(self):
print("Open file")
def window():
app = QApplication(sys.argv)
okno = Okno()
okno.show()
sys.exit(app.exec_())
window()

How can I get rid of the previous layout and set new Grid Layout in QMainWindow?

I am a newbie with PyQt. I am trying to organize my buttons on a grid layout, but I guess the window has a default layout already. How can I get rid of it and replace it with the new Grid Layout? I have contained the code block relevant with hashes ###, Here is my program:
import sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import QWidget
class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QWidget.__init__(self)
self.setMinimumSize (800,600) # set minimum size for window
self.setWindowTitle("CoolPlay Kabul") # set window title
self.setWindowIcon(QtGui.QIcon("images/CoolPlay.png"))# set icon for Window
myMenu = self.menuBar()
File_Menu = myMenu.addMenu("&File")
Items_Menu = myMenu.addMenu("&Items")
Playlist_Menu = myMenu.addMenu("&Playlist")
Option_Menu = myMenu.addMenu("&Option")
Exit_Menu = myMenu.addMenu("&Exit")
File_Menu.addAction("New Time")
File_Menu.addAction("Delete Time")
File_Menu.addSeparator()
File_Menu.addAction("Exit")
Items_Menu.addAction("New Item")
Items_Menu.addAction("Delete Item")
Items_Menu.addSeparator()
Items_Menu.addAction("Toggle Segue")
Playlist_Menu.addAction("Clear Playlist")
Playlist_Menu.addAction("Save playlist")
Playlist_Menu.addAction("Load Playlist")
Playlist_Menu.addSeparator()
Playlist_Menu.addAction("Clear 'Played' Indication")
Option_Menu.addAction("Application Setup")
Exit_Menu.addAction("Help")
Exit_Menu.addAction("About")
######################################################
Overall_Layout = QtGui.QGridLayout(self)
self.setLayout(Overall_Layout)
Play_Button = QtGui.QPushButton(QtGui.QIcon("images/PLAY.bmp"), "PLAY",self)
Overall_Layout.addWidget(Play_Button,1,2)
Overall_Layout.addWidget(Play_Button,2,2)
########################################################
self.show()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
CoolPlay = MainWindow()
CoolPlay.show()
sys.exit(app.exec_())
QMainWindow is a special widget since it already has a preset layout as shown below:
So in this case you should not set a layout to the QMainWindow but to the central widget, but first establish a centralwidget, using the indicated thing we get the following:
######################################################
central_widget = QtGui.QWidget()
self.setCentralWidget(central_widget)
Overall_Layout = QtGui.QGridLayout(central_widget)
Play_Button = QtGui.QPushButton(QtGui.QIcon("images/PLAY.bmp"), "PLAY")
Overall_Layout.addWidget(Play_Button,1,2)
Overall_Layout.addWidget(Play_Button,2,2)
########################################################
On the other hand if you inherit from QMainWindow you must call the QMainWindow constructor, but in code you call QWidget, so you must modify it to:
class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
Or
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()

Categories