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_())
Related
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.
I am trying to remove QTextDocument which is inside the QTextEdit but I am not able to do it. I used clear() method of QTextEdit but it won't clear the QTextEdit area. I also try to overwrite new document on previous by setting overwrite flag of QTextEdit but that doesn't work as well.
def create_doc(body_text):
doc = QTextDocument()
doc.setDocumentMargin(10)
cursor = QTextCursor(doc)
cursor.movePosition(QTextCursor.Start)
block_fmt = QTextBlockFormat()
block_fmt.setBottomMargin(6)
block_fmt.setAlignment(QtCore.Qt.AlignJustify)
para_fmt = QTextCharFormat()
para_fmt.setFontWeight(QFont.Normal)
para_fmt.setFontPointSize(12)
para_fmt.setFontFamilies(["Raleway", "Arial"])
cursor.insertBlock(block_fmt)
cursor.insertText(body_text, para_fmt)
cursor.movePosition(QTextCursor.End)
return doc
class AppUI(QtWidgets.QMainWindow):
def __init__(self):
super(AppUI, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.qTextEditArea.setOverwriteMode(True)
self.ui.button.clicked.connect(lambda : self.add_text("some text")
def add_text(self, text):
self.ui.qTextEditArea.clear()
document = create_doc(doc)
self.ui.qTextEditArea.setDocument(document)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ui = AppUI()
ui.show()
sys.exit(app.exec_())
If you know how to do it please help. Thanks
QTextEdit Class
document : QTextDocument*
This property holds the underlying document of the text editor.
Note: The editor does not take ownership of the document unless it is the document's parent object. The parent object of the provided document remains the owner of the object. If the previously assigned document is a child of the editor then it will be deleted.
Access functions:
QTextDocument * document() const
void setDocument(QTextDocument *document)
main.py
import sys
from PyQt5.Qt import *
from textEditArea import Ui_MainWindow
def create_doc(body_text):
doc = QTextDocument()
doc.setDocumentMargin(10)
cursor = QTextCursor(doc)
cursor.movePosition(QTextCursor.Start)
block_fmt = QTextBlockFormat()
block_fmt.setBottomMargin(6)
block_fmt.setAlignment(Qt.AlignJustify)
para_fmt = QTextCharFormat()
para_fmt.setFontWeight(QFont.Normal)
para_fmt.setFontPointSize(12)
para_fmt.setFontFamilies(["Raleway", "Arial"]) # This function was introduced in Qt 5.13.
cursor.insertBlock(block_fmt)
cursor.insertText(body_text, para_fmt)
cursor.movePosition(QTextCursor.End)
return doc
class AppUI(QMainWindow):
def __init__(self):
super(AppUI, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# self.ui.qTextEditArea.setOverwriteMode(True)
self.ui.button.clicked.connect(lambda : self.add_text("some text"))
def add_text(self, text):
# self.ui.qTextEditArea.clear()
# document = create_doc(doc) # ??? (doc)
document = create_doc(text) # +++ text
self.ui.qTextEditArea.setDocument(document)
if __name__ == "__main__":
app = QApplication(sys.argv)
ui = AppUI()
ui.show()
sys.exit(app.exec_())
textEditArea.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(456, 304)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.qTextEditArea = QtWidgets.QTextEdit(self.centralwidget)
self.qTextEditArea.setObjectName("qTextEditArea")
self.gridLayout.addWidget(self.qTextEditArea, 0, 0, 1, 1)
self.button = QtWidgets.QPushButton(self.centralwidget)
self.button.setObjectName("button")
self.gridLayout.addWidget(self.button, 1, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 456, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.button.setText(_translate("MainWindow", "PushButton"))
I have a class called TwoStateButton which inherits QPushButton so that the icon can change when the button is being clicked on and so the icon gets resized in proportion to the button when the whole window gets resized.
When I run this code, the button takes up the whole window and the icon is about 10% of the button. I'm trying to get it so that the icon image takes up the entire pushbutton and that they both get resized by the same amount when the window is resized.
What modifications to my code should I do to achieve this?
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class TwoStateButton(QtWidgets.QPushButton):
def __init__(self, on, hover, parent=None):
super(TwoStateButton, self).__init__(parent)
self.pad = 4 # padding between the icon and the button frame
self.minSize = 8 # minimum size of the icon
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
)
self.setSizePolicy(sizePolicy)
self.setIcon(QtGui.QIcon(on))
self.on = on
self.hover = hover
def mousePressEvent(self, event):
super(TwoStateButton, self).mousePressEvent(event)
self.setIcon(QtGui.QIcon(self.hover))
def mouseReleaseEvent(self, event):
super(TwoStateButton, self).mouseReleaseEvent(event)
self.setIcon(QtGui.QIcon("%s" % (self.on)))
def paintEvent(self, event):
qp = QtGui.QPainter(self)
opt = QtWidgets.QStyleOptionButton()
self.initStyleOption(opt)
r = opt.rect
h = r.height()
w = r.width()
iconSize = max(min(h, w) - 2 * self.pad, self.minSize)
opt.iconSize = QtCore.QSize(iconSize, iconSize)
self.style().drawControl(QtWidgets.QStyle.CE_PushButton, opt, qp,self)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1090, 681)
MainWindow.setStyleSheet("")
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.pushButton_2 = TwoStateButton(
"images/Button5-3.png", "images/Button5-4.png", self.centralwidget
)
self.pushButton_2.setGeometry(QtCore.QRect(430, 310, 141, 81))
self.pushButton_2.setText("")
self.pushButton_2.setCheckable(False)
self.pushButton_2.setObjectName("pushButton_2")
self.horizontalLayout.addWidget(self.pushButton_2)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1090, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setStyleSheet(
"border-image: url(:/images/Button5-3.png)"
)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
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_())
Is it possible to create a switch button with pyqt5?
I'm designing a filtering tool in python using pyqt5. The user is supposed to be able to apply a filter or its complement on some data and even combine filters.
I display possible filters in qtablewidget where the user can choose filters to apply using checkboxes. In each row, checkboxes are exclusives i.e. the user can not select a filter and its complement at the same time.
But the problem is that once we select a checkbox in a row, we cannot deselect it unless we select its opposite.
In fact, when the filters are just loaded , all the boxes are unchecked (they are empty in a way) so I can choose which filter to apply but when I want to choose an other filter, one of the boxes of the precedent filter still checked (either box Filter or box Complement is checked) and I can not switch it off.
That's why I thought about adding a switch button in each row to disable a filter. By doing this I will be able to take in consideration or not the checked attribute.
Here is an example of what I want :
Below an reproductible example
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(550, 350)
MainWindow.setMinimumSize(QtCore.QSize(550, 350))
MainWindow.setMaximumSize(QtCore.QSize(550, 350))
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.tableWidget = QtWidgets.QTableWidget(self.centralwidget)
self.tableWidget.setGeometry(QtCore.QRect(20, 20, 500, 200))
self.tableWidget.setObjectName("tableWidget")
self.tableWidget.setColumnCount(3)
self.tableWidget.setRowCount(0)
item = QtWidgets.QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(0, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(1, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(2, item)
self.pushButton_Save = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_Save.setGeometry(QtCore.QRect(120, 250, 100, 50))
self.pushButton_Save.setMinimumSize(QtCore.QSize(100, 50))
self.pushButton_Save.setMaximumSize(QtCore.QSize(100, 50))
self.pushButton_Save.setObjectName("pushButton_Save")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 550, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
item = self.tableWidget.horizontalHeaderItem(0)
item.setText(_translate("MainWindow", "Name"))
item = self.tableWidget.horizontalHeaderItem(1)
item.setText(_translate("MainWindow", "Filter"))
item = self.tableWidget.horizontalHeaderItem(2)
item.setText(_translate("MainWindow", "complement"))
self.pushButton_Save.setText(_translate("MainWindow", "Save"))
self.pushButton_Save.clicked.connect(self.bindSave)
def bindSave(self):
numRows = self.tableWidget.rowCount()
self.tableWidget.insertRow(numRows)
groupButton = QtWidgets.QButtonGroup(self.tableWidget)
groupButton.setExclusive(True)
it1 = QtWidgets.QTableWidgetItem("filter "+str(numRows))
self.tableWidget.setItem(numRows, 0, it1)
ch_bx1 = QtWidgets.QCheckBox()
groupButton.addButton(ch_bx1)
self.tableWidget.setCellWidget(numRows, 1, ch_bx1)
ch_bx2 = QtWidgets.QCheckBox()
groupButton.addButton(ch_bx2)
self.tableWidget.setCellWidget(numRows, 2, ch_bx2)
if __name__ == "__main__":
if not QtWidgets.QApplication.instance():
app = QtWidgets.QApplication(sys.argv)
else:
app = QtWidgets.QApplication.instance()
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
Like others have suggested, you could enable/disable filters by adding a third column to the table and setting each item widget in this column to a like you did for ch_box1 and ch_box2. You can use slots and signals to manipulate the check buttons (for example enable/disable them depending on the state of the switch).
To create custom switches like in the picture in the OP, you could sub-class QPushButton and overriding paintEvent, e.g. (note that I've omitted the code that was the same as the code in the reproducable example in the original post)
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, QRect
import sys
class MySwitch(QtWidgets.QPushButton):
def __init__(self, parent = None):
super().__init__(parent)
print('init')
self.setCheckable(True)
self.setMinimumWidth(66)
self.setMinimumHeight(22)
def paintEvent(self, event):
label = "ON" if self.isChecked() else "OFF"
bg_color = Qt.green if self.isChecked() else Qt.red
radius = 10
width = 32
center = self.rect().center()
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.translate(center)
painter.setBrush(QtGui.QColor(0,0,0))
pen = QtGui.QPen(Qt.black)
pen.setWidth(2)
painter.setPen(pen)
painter.drawRoundedRect(QRect(-width, -radius, 2*width, 2*radius), radius, radius)
painter.setBrush(QtGui.QBrush(bg_color))
sw_rect = QRect(-radius, -radius, width + radius, 2*radius)
if not self.isChecked():
sw_rect.moveLeft(-width)
painter.drawRoundedRect(sw_rect, radius, radius)
painter.drawText(sw_rect, Qt.AlignCenter, label)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
....
self.tableWidget.setColumnCount(4)
....
item = QtWidgets.QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(3, item)
...
def retranslateUi(self, MainWindow):
...
item = self.tableWidget.horizontalHeaderItem(3)
item.setText(_translate("MainWindow", "status"))
def bindSave(self):
...
ch_bx3 = MySwitch()
ch_bx3.setChecked(True)
ch_bx3.clicked.connect(ch_bx1.setEnabled)
ch_bx3.clicked.connect(ch_bx2.setEnabled)
self.tableWidget.setCellWidget(numRows, 3, ch_bx3)
if __name__ == "__main__":
if not QtWidgets.QApplication.instance():
app = QtWidgets.QApplication(sys.argv)
else:
app = QtWidgets.QApplication.instance()
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
app.exec()
Screenshot:
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_())