Keep aspect ratio of image in a QLabel whilst resizing window - python

Say i have a layout like this:
I want the ratio of the left object and the right object to always be like this:
So that i can add a small image in the top left corner that wont be gigantic or too small, but always in nice relation to the size of the window. How can i do this, either in the designer, or in the code?
I already found that you seem to be able to do this via selecting the layout and changing the LayoutStretch to something like 1,3 - this worked in the designer, however, when i inserted my image in the code, it did not respect it and blew the layout out of proption again.
I added a stretcher and used a QLabel to display the image, and then added the file via self.LogoLabel.setPixmap(QtGui.QPixmap('res/image.png')) , so i do not understand what i need to change in order to get the image to always be nice and small in the top left corner.
A test example, in case the question wasnt clear enough - the image i need is 1000x710px large.
from PyQt5 import QtCore, QtWidgets, QtGui
import sys
class Ui_ZEBRA(object):
def setupUi(self, ZEBRA):
ZEBRA.setObjectName("ZEBRA")
ZEBRA.resize(315, 134)
self.centralwidget = QtWidgets.QWidget(ZEBRA)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setContentsMargins(-1, -1, -1, 0)
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.LogoLabel = QtWidgets.QLabel(self.centralwidget)
self.LogoLabel.setText("")
self.LogoLabel.setScaledContents(True)
self.LogoLabel.setObjectName("LogoLabel")
self.verticalLayout_3.addWidget(self.LogoLabel)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_3.addItem(spacerItem)
self.ComboBox_InputType = QtWidgets.QComboBox(self.centralwidget)
self.ComboBox_InputType.setObjectName("ComboBox_InputType")
self.ComboBox_InputType.addItem("")
self.ComboBox_InputType.addItem("")
self.ComboBox_InputType.addItem("")
self.ComboBox_InputType.addItem("")
self.verticalLayout_3.addWidget(self.ComboBox_InputType)
self.horizontalLayout.addLayout(self.verticalLayout_3)
self.TextInput_Devices = QtWidgets.QPlainTextEdit(self.centralwidget)
self.TextInput_Devices.setObjectName("TextInput_Devices")
self.horizontalLayout.addWidget(self.TextInput_Devices)
self.horizontalLayout.setStretch(0, 1)
self.horizontalLayout.setStretch(1, 3)
self.verticalLayout_4.addLayout(self.horizontalLayout)
ZEBRA.setCentralWidget(self.centralwidget)
self.menuBar_EnterToken = QtWidgets.QAction(ZEBRA)
self.menuBar_EnterToken.setObjectName("menuBar_EnterToken")
self.menuBar_TestToken = QtWidgets.QAction(ZEBRA)
self.menuBar_TestToken.setObjectName("menuBar_TestToken")
self.menuBar_About = QtWidgets.QAction(ZEBRA)
self.menuBar_About.setObjectName("menuBar_About")
self.retranslateUi(ZEBRA)
QtCore.QMetaObject.connectSlotsByName(ZEBRA)
def retranslateUi(self, ZEBRA):
_translate = QtCore.QCoreApplication.translate
ZEBRA.setWindowTitle(_translate("ZEBRA", "ZEBRA"))
self.ComboBox_InputType.setItemText(0, _translate("ZEBRA", "ip"))
self.ComboBox_InputType.setItemText(1, _translate("ZEBRA", "Use all devices"))
self.ComboBox_InputType.setItemText(2, _translate("ZEBRA", "displayName"))
self.ComboBox_InputType.setItemText(3, _translate("ZEBRA", "id"))
self.menuBar_EnterToken.setText(_translate("ZEBRA", "Enter Accesstoken"))
self.menuBar_TestToken.setText(_translate("ZEBRA", "Test Accesstoken"))
self.menuBar_About.setText(_translate("ZEBRA", "About..."))
class Test(QtWidgets.QMainWindow, Ui_ZEBRA):
def __init__(self, parent=None):
super(Test, self).__init__(parent)
self.setupUi(self)
self.LogoLabel.setPixmap(QtGui.QPixmap('res/image.png'))
def main():
app = QtWidgets.QApplication(sys.argv)
form = Test()
form.show()
app.exec_()
if __name__ == '__main__':
main()
EDIT: Weirdly enough, i could not find a single working example on how to use an image in a QLabel and scale its size while changing the window size, while also keeping the aspect ratio. Such a basic thing that is nowhere to be found?

You firstly need to change the layout so that it uses alignment rather than expanding spacers to keep the label at the top left corner. Also, some properties of the label need adjustment so that it can resize itself freely. This can all be done in Qt Designer, but your example code can also be fixed manually like this:
self.LogoLabel = QtWidgets.QLabel(self.centralwidget)
self.LogoLabel.setText("")
self.LogoLabel.setObjectName("LogoLabel")
# new stuff
self.LogoLabel.setScaledContents(False)
self.LogoLabel.setMinimumSize(1, 1)
self.LogoLabel.setSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.LogoLabel.setAlignment(QtCore.Qt.AlignTop)
self.verticalLayout_3.setAlignment(QtCore.Qt.AlignTop)
self.verticalLayout_3.addWidget(self.LogoLabel)
The dynamic resizing can then be handled using an event-filter, like this:
class Test(QtWidgets.QMainWindow, Ui_ZEBRA):
def __init__(self, parent=None):
super(Test, self).__init__(parent)
self.setupUi(self)
self._logo = QtGui.QPixmap('res/image.png')
self.LogoLabel.setPixmap(self._logo)
self.LogoLabel.installEventFilter(self)
def eventFilter(self, widget, event):
if event.type() == QtCore.QEvent.Resize and widget is self.LogoLabel:
self.LogoLabel.setPixmap(self._logo.scaled(
self.LogoLabel.width(), self.LogoLabel.height(),
QtCore.Qt.KeepAspectRatio))
return True
return super(Test, self).eventFilter(widget, event)

To scale the image down as you wanted and keep the ratio between the two elements, you need to set setScaledContents(True) like this:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(559, 289)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
self.label = QtWidgets.QLabel(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
self.label.setSizePolicy(sizePolicy)
self.label.setScaledContents(True)
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.comboBox = QtWidgets.QComboBox(self.centralwidget)
self.comboBox.setObjectName("comboBox")
self.verticalLayout.addWidget(self.comboBox)
self.horizontalLayout.addLayout(self.verticalLayout)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.textBrowser = QtWidgets.QTextBrowser(self.centralwidget)
self.textBrowser.setObjectName("textBrowser")
self.horizontalLayout.addWidget(self.textBrowser)
self.horizontalLayout.setStretch(0, 1)
self.horizontalLayout.setStretch(2, 3)
self.verticalLayout_2.addLayout(self.horizontalLayout)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "TextLabel"))
import sys
class Test(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(Test, self).__init__(parent)
self.setupUi(self)
pixmap = QtGui.QPixmap('res/image.png')
pixmap = pixmap.scaled(256, 128, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
self.label.setPixmap(pixmap)
self.label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
def main():
app = QtWidgets.QApplication(sys.argv)
form = Test()
form.show()
app.exec_()
if __name__ == '__main__':
main()
In order to keep the aspect ratio - i think you have to use a resizeEvent of the Widget containing the Label which changes the size to the correct aspect ratio whenever the event is triggered.

Related

When two widget animation commands run at start only one gets animated

I am trying hide two list widgets upon start by animating the maximum width to zero, then two buttons to toggle each widget to appear and disappear, this works fine, but not at start, only one of the two widget gets animated, the other just stays on, but the same command works fine when signaled by the push buttons.
Please suggest if there is any better approach to achieve the same effect or the reason behind it.
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import QPropertyAnimation
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(640, 480)
self.horizontalLayout = QtWidgets.QHBoxLayout(Form)
self.horizontalLayout.setObjectName("horizontalLayout")
self.frame = QtWidgets.QFrame(Form)
self.frame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
self.frame.setObjectName("frame")
self.verticalLayout = QtWidgets.QVBoxLayout(self.frame)
self.verticalLayout.setObjectName("verticalLayout")
self.pushButton = QtWidgets.QPushButton(self.frame)
self.pushButton.setObjectName("pushButton")
self.verticalLayout.addWidget(self.pushButton)
self.pushButton_2 = QtWidgets.QPushButton(self.frame)
self.pushButton_2.setObjectName("pushButton_2")
self.verticalLayout.addWidget(self.pushButton_2)
self.pushButton_3 = QtWidgets.QPushButton(self.frame)
self.pushButton_3.setObjectName("pushButton_3")
self.verticalLayout.addWidget(self.pushButton_3)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.horizontalLayout.addWidget(self.frame)
self.listWidget = QtWidgets.QListWidget(Form)
self.listWidget.setObjectName("listWidget")
self.horizontalLayout.addWidget(self.listWidget)
self.listWidget_2 = QtWidgets.QListWidget(Form)
self.listWidget_2.setObjectName("listWidget_2")
self.horizontalLayout.addWidget(self.listWidget_2)
self.listWidget_3 = QtWidgets.QListWidget(Form)
self.listWidget_3.setObjectName("listWidget_3")
self.horizontalLayout.addWidget(self.listWidget_3)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
# Trying to Hide both widgets upon start, but one gets hidden
self.animate_listwidget2()
self.animate_listwidget3()
self.pushButton_2.clicked.connect(self.animate_listwidget2)
self.pushButton_3.clicked.connect(self.animate_listwidget3)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.pushButton.setText(_translate("Form", "PushButton"))
self.pushButton_2.setText(_translate("Form", "toggle lw2"))
self.pushButton_3.setText(_translate("Form", "toggle lw3"))
def animate_listwidget2(self):
width = self.listWidget_2.width()
if width != 0:
width1 = 0
else:
width1 = 350
self.animation = QPropertyAnimation(self.listWidget_2, b'maximumWidth')
self.animation.setDuration(800)
self.animation.setStartValue(width)
self.animation.setEndValue(width1)
self.animation.setEasingCurve(QtCore.QEasingCurve.Type.Linear)
self.animation.start()
def animate_listwidget3(self):
width = self.listWidget_3.width()
if width != 0:
width1 = 0
else:
width1 = 350
self.animation = QPropertyAnimation(self.listWidget_3, b'maximumWidth')
self.animation.setDuration(800)
self.animation.setStartValue(width)
self.animation.setEndValue(width1)
self.animation.setEasingCurve(QtCore.QEasingCurve.Type.Linear)
self.animation.start()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec())
First of all you should not modify the code generated by QtDesigner since that could cause other bugs so to apply my solutions you have to restore the file and call it gui.py
The problem is that using the same attribute is destroying the previous animation.
There are several solutions such as changing the name of the attribute assigned to one of them (for example, changing the name of the second to self.animation2) but the implementation can still be improved since with the current one, animations are always being created unnecessarily since one is enough single for each QListWidget.
from PyQt6.QtCore import QEasingCurve, QPropertyAnimation
from PyQt6.QtWidgets import QApplication, QWidget
from gui import Ui_Form
class Form(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_2.clicked.connect(self.animate_listwidget2)
self.ui.pushButton_3.clicked.connect(self.animate_listwidget3)
self.animation1 = self.build_animation(self.ui.listWidget_2)
self.animation2 = self.build_animation(self.ui.listWidget_3)
self.animate_listwidget2()
self.animate_listwidget3()
def build_animation(self, listwidget):
animation = QPropertyAnimation(listwidget, b"maximumWidth")
animation.setDuration(800)
animation.setEasingCurve(QEasingCurve.Type.Linear)
return animation
def start_animation(self, animation):
width = animation.targetObject().width()
animation.stop()
animation.setStartValue(width)
animation.setEndValue(0 if width != 0 else 350)
animation.start()
def animate_listwidget2(self):
self.start_animation(self.animation1)
def animate_listwidget3(self):
self.start_animation(self.animation2)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = Form()
w.show()
sys.exit(app.exec())

Problem with moving window using custom title bar in PyQt5

I'm trying to build a GUI with a custom title bar using along with setWindowFlags(FramelessWindowHint).
I have successfully created close/minimise buttons etc with their respective functions and they work perfectly (They are not included in my code below). However I haven't had the same success with making the window moveable using my custom title bar.
When I ran the code and attempted to drag the window, the cursor changed to the blue loading swirl then crashed. I also tried running it with debugger and when I tried to drag the window I got the error:
TypeError: unsupported operand type(s) for +: 'QPoint' and 'builtin_function_or_method'
Below is my a part of my code. (It may be a little messy as I cut it out of a larger project.)
Any help to solve my problem will be greatly appreciated, thank you.
from PyQt5 import QtCore, QtGui, QtWidgets, Qt
from PyQt5.QtCore import Qt
WINDOW_SIZE = 0
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1200, 700)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setSpacing(0)
self.verticalLayout.setObjectName("verticalLayout")
self.top_bar = QtWidgets.QWidget(self.centralwidget)
self.top_bar.setMaximumSize(QtCore.QSize(16777215, 60))
self.top_bar.setStyleSheet("background-color: rgb(52, 56, 60)")
self.top_bar.setObjectName("top_bar")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.top_bar)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setSpacing(0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.frame = QtWidgets.QFrame(self.top_bar)
self.frame.setMinimumSize(QtCore.QSize(80, 60))
self.frame.setMaximumSize(QtCore.QSize(80, 60))
self.frame.setStyleSheet("background-color:rgb(30, 34, 38);\n"
"border: 0px")
self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
self.frame.setObjectName("frame")
self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.frame)
self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_4.setSpacing(0)
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.horizontalLayout_2.addWidget(self.frame)
self.header_bar = QtWidgets.QFrame(self.top_bar)
self.header_bar.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.header_bar.setFrameShadow(QtWidgets.QFrame.Raised)
self.header_bar.setObjectName("header_bar")
self.horizontalLayout_2.addWidget(self.header_bar)
self.verticalLayout.addWidget(self.top_bar)
self.content = QtWidgets.QWidget(self.centralwidget)
self.content.setStyleSheet("")
self.content.setObjectName("content")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.content)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout.addWidget(self.content)
MainWindow.setCentralWidget(self.centralwidget)
self.header_bar.mouseMoveEvent = self.moveWindow
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
def mousePressEvent(self, event):
MainWindow.dragPos = event.globalPos()
def moveWindow(self, event):
if MainWindow.isMaximized():
MainWindow.showNormal()
else:
if event.buttons() == Qt.LeftButton:
MainWindow.move((MainWindow.pos() + event.globalPos() - MainWindow.dragPos))
MainWindow.dragPos = event.globalPos()
event.accept()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
I marked the places I changed
from PyQt5 import QtCore, QtGui, QtWidgets, Qt
from PyQt5.QtCore import Qt
WINDOW_SIZE = 0
class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1200, 700)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout.setSpacing(0)
        self.verticalLayout.setObjectName("verticalLayout")
        self.top_bar = QtWidgets.QWidget(self.centralwidget)
        self.top_bar.setMaximumSize(QtCore.QSize(16777215, 60))
        self.top_bar.setStyleSheet("background-color: rgb(52, 56, 60)")
        self.top_bar.setObjectName("top_bar")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.top_bar)
        self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout_2.setSpacing(0)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.frame = QtWidgets.QFrame(self.top_bar)
        self.frame.setMinimumSize(QtCore.QSize(80, 60))
        self.frame.setMaximumSize(QtCore.QSize(80, 60))
        self.frame.setStyleSheet("background-color:rgb(30, 34, 38);\n"
                                                       "border: 0px")
        self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
        self.frame.setObjectName("frame")
        self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.frame)
        self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout_4.setSpacing(0)
        self.horizontalLayout_4.setObjectName("horizontalLayout_4")
        self.horizontalLayout_2.addWidget(self.frame)
        self.header_bar = QtWidgets.QFrame(self.top_bar)
        self.header_bar.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.header_bar.setFrameShadow(QtWidgets.QFrame.Raised)
        self.header_bar.setObjectName("header_bar")
        self.horizontalLayout_2.addWidget(self.header_bar)
        self.verticalLayout.addWidget(self.top_bar)
        self.content = QtWidgets.QWidget(self.centralwidget)
        self.content.setStyleSheet("")
        self.content.setObjectName("content")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.content)
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout.setSpacing(0)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.verticalLayout.addWidget(self.content)
        MainWindow.setCentralWidget(self.centralwidget)
        
        self.header_bar.mouseMoveEvent = self.moveWindow
        #*************************************************************************************************************************************************************
        self.header_bar.mousePressEvent = self.mousePress
        #*************************************************************************************************************************************************************
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
    #*************************************************************************************************************************************************************
    def mousePress(self, event):
        MainWindow.dragPos = MainWindow.pos()
        self.mouse_original_pos = MainWindow.mapToGlobal(event.pos())
    #*************************************************************************************************************************************************************
    def moveWindow(self, event):
        if MainWindow.isMaximized():
            MainWindow.showNormal()
        else:
            if event.buttons() == Qt.LeftButton:
                #******************************************************************************************************************************************************
                MainWindow_last_pos = MainWindow.dragPos + MainWindow.mapToGlobal(event.pos()) - self.mouse_original_pos
                MainWindow.move(MainWindow_last_pos)
                event.accept()
                #******************************************************************************************************************************************************
if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

GroupBox with radioButtons enabling another groupBox doesn't keep button state

I have three groupBoxes with two radioButtons each. Buttons in the first box
enable/disable the second groupBox. Buttons in the second enable/disable the
third groupBox.
This is how it's supposed to work.
The second and third groupBoxes are disabled by default.
radioButton in the first box sends signal toggled(bool) to enable the second
box. There, radioButton that deactivates the third box is clicked by default, a second radioButton sends signal toggled(bool) to the third box enabling it.
What actually happens is that when I enable the second box, the third box
becomes enabled. When I toggle buttons in the second box I can enable/disable the third box, but when I again disable the second box from the first and then enable it again, the third box is enabled regardles of which button is clicked in the second box.
What gives?
Fair game, here's an example (tried to make it as short as I could with Designer)(imports, first class and 'if name == ...' at the end of the file contain additional 4 spaces so that code shows as code, delete them to run):
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox.setObjectName("groupBox")
self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.groupBox)
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.radioButton_2 = QtWidgets.QRadioButton(self.groupBox)
self.radioButton_2.setChecked(True)
self.radioButton_2.setObjectName("radioButton_2")
self.horizontalLayout_2.addWidget(self.radioButton_2)
self.radioButton = QtWidgets.QRadioButton(self.groupBox)
self.radioButton.setObjectName("radioButton")
self.horizontalLayout_2.addWidget(self.radioButton)
self.horizontalLayout_3.addLayout(self.horizontalLayout_2)
self.gridLayout.addWidget(self.groupBox, 0, 0, 1, 1)
self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox_2.setEnabled(False)
self.groupBox_2.setObjectName("groupBox_2")
self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.groupBox_2)
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.radioButton_3 = QtWidgets.QRadioButton(self.groupBox_2)
self.radioButton_3.setChecked(True)
self.radioButton_3.setObjectName("radioButton_3")
self.horizontalLayout_4.addWidget(self.radioButton_3)
self.radioButton_4 = QtWidgets.QRadioButton(self.groupBox_2)
self.radioButton_4.setObjectName("radioButton_4")
self.horizontalLayout_4.addWidget(self.radioButton_4)
self.horizontalLayout_5.addLayout(self.horizontalLayout_4)
self.gridLayout.addWidget(self.groupBox_2, 1, 0, 1, 1)
self.groupBox_3 = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox_3.setEnabled(False)
self.groupBox_3.setObjectName("groupBox_3")
self.gridLayout.addWidget(self.groupBox_3, 2, 0, 1, 1)
self.horizontalLayout.addLayout(self.gridLayout)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 30))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.radioButton.toggled['bool'].connect(self.groupBox_2.setEnabled)
self.radioButton_4.toggled['bool'].connect(self.groupBox_3.setEnabled)
self.radioButton_2.toggled['bool'].connect(self.groupBox_3.setDisabled)
self.radioButton_3.toggled['bool'].connect(self.groupBox_3.setDisabled)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.groupBox.setTitle(_translate("MainWindow", "whatever"))
self.radioButton_2.setText(_translate("MainWindow", "Off"))
self.radioButton.setText(_translate("MainWindow", "Sensors"))
self.groupBox_2.setTitle(_translate("MainWindow", "Control method"))
self.radioButton_3.setText(_translate("MainWindow", "On/Off"))
self.radioButton_4.setText(_translate("MainWindow", "PID"))
self.groupBox_3.setTitle(_translate("MainWindow", "PID settings"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
First off (and I mean this in the nicest way) that is some really ugly code. I have taken your code and cleaned it up and using Python 3.7 with pyqt5 on Win10 it works fine and should be a lot easier to understand. I hope this helps you down a more structured path to development. Keep in mind that you often have to go back and revisit and/or reuse old code that you have written best to write it in a way that makes it really easy to understand at a glance.
from sys import exit as sysExit
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class BoxOne(QGroupBox):
def __init__(self, CentrPane):
QGroupBox.__init__(self, CentrPane)
# Reference back to parent
self.CntrPane = CentrPane
self.setTitle('Main Controller')
self.radBtnOff = QRadioButton('Off')
self.radBtnOff.setChecked(True)
self.radBtnOff.toggled['bool'].connect(self.BoxTwoOff)
self.radBtnSnsr = QRadioButton('Sensors')
self.radBtnSnsr.toggled['bool'].connect(self.BoxTwoOn)
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.addWidget(self.radBtnOff)
self.horizontalLayout.addWidget(self.radBtnSnsr)
self.setLayout(self.horizontalLayout)
def BoxTwoOff(self):
# I assume you did not mean to leave Box Three Enabled
self.CntrPane.SecndBox.radBtnOnOff.setChecked(True)
self.CntrPane.SecndBox.setDisabled(True)
def BoxTwoOn(self):
self.CntrPane.SecndBox.setEnabled(True)
class BoxTwo(QGroupBox):
def __init__(self, CentrPane):
QGroupBox.__init__(self, CentrPane)
# Reference back to parent
self.CntrPane = CentrPane
self.setTitle('Control Method')
self.setEnabled(False)
self.radBtnOnOff = QRadioButton('On/Off')
self.radBtnOnOff.setChecked(True)
self.radBtnOnOff.toggled['bool'].connect(self.BoxThreeOff)
self.radBtnPID = QRadioButton('PID')
self.radBtnPID.toggled['bool'].connect(self.BoxThreeOn)
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.addWidget(self.radBtnOnOff)
self.horizontalLayout.addWidget(self.radBtnPID)
self.setLayout(self.horizontalLayout)
def BoxThreeOff(self):
self.CntrPane.ThirdBox.setDisabled(True)
def BoxThreeOn(self):
self.CntrPane.ThirdBox.setEnabled(True)
class BoxThree(QGroupBox):
def __init__(self, CentrPane):
QGroupBox.__init__(self, CentrPane)
# Reference back to parent
self.CntrPane = CentrPane
self.setTitle('PID Settings')
self.setEnabled(False)
self.MyEditor = QTextEdit('Editorial')
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.addWidget(self.MyEditor)
self.setLayout(self.horizontalLayout)
class CenterPanel(QWidget):
def __init__(self, MainWin):
QWidget.__init__(self)
# Reference back to parent
self.MainWin = MainWin
self.FirstBox = BoxOne(self)
self.SecndBox = BoxTwo(self)
self.ThirdBox = BoxThree(self)
self.gridLayout = QGridLayout()
self.gridLayout.addWidget(self.FirstBox, 0, 0, 1, 1)
self.gridLayout.addWidget(self.SecndBox, 1, 0, 1, 1)
self.gridLayout.addWidget(self.ThirdBox, 2, 0, 1, 1)
self.setLayout(self.gridLayout)
class MenuToolBar(QDockWidget):
def __init__(self, MainWin):
QDockWidget.__init__(self)
# Reference back to parent
self.MainWin = MainWin
self.MainMenu = MainWin.menuBar()
class StatusBar(QDockWidget):
def __init__(self, MainWin):
QDockWidget.__init__(self)
# Reference back to parent
self.MainWin = MainWin
self.MainMenu = MainWin.statusBar()
class UI_MainWindow(QMainWindow):
def __init__(self, AppDesktop):
super(UI_MainWindow, self).__init__(AppDesktop)
# Reference back to parent
self.MainDeskTop = AppDesktop
self.setWindowTitle('Main Window')
# Left, Top, Width, Height
self.setGeometry(200, 200, 800, 600)
self.CenterPane = CenterPanel(self)
self.setCentralWidget(self.CenterPane)
self.MenuToolBar = MenuToolBar(self)
self.StatusBar = StatusBar(self)
if __name__ == '__main__':
MainApp = QApplication([])
MainGui = UI_MainWindow(MainApp.desktop())
MainGui.show()
sysExit(MainApp.exec_())

PyQt5 QScrollArea does not scroll, resizes items instead

I have the problem that completely drives me mad.
I am writing GUI application on Python using PyQt5. My application consists of multiple QGroupBoxes, that become visible and non-visible as user switches between them.
One of QGroupBoxes contains QScrollArea, in which another QGroupBoxes are placed. As user adds information to application, new QGroupBoxes might be added, so QScrollArea should allow to view all of them when there are too much elements added.
So the structure of elements is:
QGroupBox
=>QScrollArea
=>=>QScrollAreaWidgetContents
=>=>=>QVBoxLayout
=>=>=>=>QGroupBox
=>=>=>=>=>QFormLayout
=>=>=>=>QGroupBox
=>=>=>=>=>QFormLayout
However, even though I placed inner QGroupBoxes inside a vertical layout and then inside a single QScrollAreaWidgetContents, QScrollArea does not show any scrollbars, but instead resizes inner elements, so it looks like this.
My problem can be summed up in this example:
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(415, 213)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox.setGeometry(QtCore.QRect(0, 0, 801, 601))
self.groupBox.setObjectName("groupBox")
self.scrollArea = QtWidgets.QScrollArea(self.groupBox)
self.scrollArea.move(10, 30)
self.scrollArea.setFixedWidth(380)
self.scrollArea.setMinimumHeight(160)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setObjectName("scrollArea")
self.scrollAreaWidgetContents = QtWidgets.QWidget()
self.scrollAreaWidgetContents.move(0, 0)
self.scrollAreaWidgetContents.setFixedWidth(378)
self.scrollAreaWidgetContents.setMinimumHeight(158)
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
MainWindow.setCentralWidget(self.centralwidget)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
class competencyBox(QWidget):
def __init__(self, parent):
super(competencyBox, self).__init__(parent)
self.compCodeLineEdit = QLineEdit()
self.compDescrpTextEdit = QTextEdit()
self.box = QGroupBox(self)
self.form_lay = QFormLayout(self)
self.form_lay.addRow(QLabel("Код: "), self.compCodeLineEdit)
self.form_lay.addRow(QLabel("Описание: "), self.compDescrpTextEdit)
self.box.setLayout(self.form_lay)
self.box.setFixedSize(510, 240)
class test_window(QMainWindow):
def __init__(self):
super(test_window, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.addBox(self.ui.scrollAreaWidgetContents, competencyBox, 4)
def addBox(self, parent, element, number):
vert_lay = QVBoxLayout(parent)
for i in range(number):
e = element(parent)
vert_lay.addWidget(e)
vert_lay.setSpacing(5)
As you may notice, I tried different approaches, such as setting fixed size to inner QGroupBoxes, adding spacing into the vertical layout and so on, but QScrollArea still ignores them and shrinks inner elements. I am stuck and got no idea how to solve my problem. Please help me.
The main problem in your case is that the scrollAreaWidgetContents should not have a fixed size since it is the container of the widgets and if you use self.scrollAreaWidgetContents.setFixedWidth (378) you are setting it, the size of the scrollAreaWidgetContents should be the set size. widgets through the QVBoxLayout.
Another problem is that CompetencyBox must use a layout to set up the QGroupBox.
from PyQt5 import QtCore, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(415, 213)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox.setGeometry(QtCore.QRect(0, 0, 801, 601))
self.groupBox.setObjectName("groupBox")
self.scrollArea = QtWidgets.QScrollArea(self.groupBox)
self.scrollArea.move(10, 30)
self.scrollArea.setFixedWidth(380)
self.scrollArea.setMinimumHeight(160)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setObjectName("scrollArea")
self.scrollAreaWidgetContents = QtWidgets.QWidget()
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
MainWindow.setCentralWidget(self.centralwidget)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
class CompetencyBox(QtWidgets.QWidget):
def __init__(self, parent=None):
super(CompetencyBox, self).__init__(parent)
self.compCodeLineEdit = QtWidgets.QLineEdit()
self.compDescrpTextEdit = QtWidgets.QTextEdit()
lay = QtWidgets.QVBoxLayout(self)
box = QtWidgets.QGroupBox()
lay.addWidget(box)
form_lay = QtWidgets.QFormLayout()
form_lay.addRow(QtWidgets.QLabel("Код: "), self.compCodeLineEdit)
form_lay.addRow(QtWidgets.QLabel("Описание: "), self.compDescrpTextEdit)
box.setLayout(form_lay)
box.setFixedSize(510, 240)
class Test_Window(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(Test_Window, self).__init__(parent)
self.setupUi(self)
self.addBox(self.scrollAreaWidgetContents, CompetencyBox, 4)
def addBox(self, parent, element, number):
vert_lay = QtWidgets.QVBoxLayout(parent)
for i in range(number):
vert_lay.addWidget(element())
vert_lay.setSpacing(5)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Test_Window()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

How to make the bottom row of QGridLayout automatically fill the window's size in PyQt5?

As the window changes its size, I want to the bottom of the row, which is a QSplitter including 3 widgets, to expand and fill the remaining window while the widgets in the first row keep the original y-position. How to do that?
If the three widgets in QSplitter can keep their former ratio, it'll be better.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
self.MainWindow=MainWindow
self.MainWindow.setObjectName("self.MainWindow")
self.MainWindow.resize(850, 800)
self.centralwidget = QtWidgets.QWidget(self.MainWindow)
self.MainWindow.setCentralWidget(self.centralwidget)
self.vlayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setText("LABEL")
self.comboBox = QtWidgets.QComboBox(self.centralwidget)
self.gridLayout.addWidget(self.label,0,0,1,1)
self.gridLayout.addWidget(self.comboBox,0,1,1,9)
"""table """
self.tableWidget = QtWidgets.QTableWidget(self.centralwidget)
"""tab1"""
self.tabWidget = QtWidgets.QTabWidget(self.centralwidget)
self.tabWidget.setMinimumHeight(50)
"""tab2"""
self.tabWidget_2 = QtWidgets.QTabWidget(self.centralwidget)
self.tabWidget_2.setMinimumHeight(50)
"""splitter window"""
splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical)
splitter.addWidget(self.tableWidget)
splitter.addWidget(self.tabWidget)
splitter.addWidget(self.tabWidget_2)
splitter.setSizes([232,225,225])
self.gridLayout.addWidget(splitter,3,0,5,10)
self.gridLayout.setRowMinimumHeight(3,690)
self.vlayout.addLayout(self.gridLayout)
spacerItem = QtWidgets.QSpacerItem(20, 245, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.vlayout.addItem(spacerItem)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
I want to the QSplitter to expand to the bottom of the window.

Categories

Resources