Proper way of managing multiple windows in PySide? - python-3.x

I have this app where I have several settings windows that open when buttons from the main window are clicked. The windows are application modal, so only one is open at a time. I have two ideas as to how to manage them, but I'm not sure which one would be the proper way to do it. I don't particularly care how the values are stored, as long as I can pass them to other windows in the app and do stuff with them.
MainWindow class Option 1:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
central = QWidget()
layout = QVBoxLayout()
button = QPushButton('Show window')
layout.addWidget(button)
window = OtherWindow()
button.clicked.connect(window.show)
# I can pull the settings and pass them on to other windows if needed.
self.setCentralWidget(central)
MainWindow class Option 2:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.other_settings = {}
button = QPushButton('Show window')
button.clicked.connect(self.show_other)
def show_other(self):
other_window = OtherWindow()
if other_window.exec():
self.other_settings.update(other_window.settings)
OtherWindow class:
class OtherWindow(QDialog):
def __init__(self):
super().__init__()
self.settings = {}
# widgets
box = QSpinBox(objectName='spinbox')
box.valueChanged.connect(self.save_settings)
# and so on ...
def save_settings(self):
sender = self.sender()
self.settings[sender.objectName()] = sender.value()

If i've understood your question correctly, look at this link which might be useful.
Small addition from me is, look here.
from PyQt5.QtWidgets import QDialog
from ui_imagedialog import Ui_ImageDialog
class ImageDialog(QDialog):
def __init__(self):
super(ImageDialog, self).__init__()
# Set up the user interface from Designer.
self.ui = Ui_ImageDialog()
self.ui.setupUi(self)
# Make some local modifications.
self.ui.colorDepthCombo.addItem("2 colors (1 bit per pixel)")
# Connect up the buttons.
self.ui.okButton.clicked.connect(self.accept)
self.ui.cancelButton.clicked.connect(self.reject)
Once you've got your .ui files it is a good practice to use (second link, second example - mentoned above) pyside6-uic or pyuic5 - depending of your needs.
Then you're creating class which role is to setup desired QWidget ui converted python module.
So in QtDesigner you're creating a QWidget:
screen_qt_designer
then do all your stuff, that it would like to do - place over QLineEdit, QPushButtons, etc., then save that file in your project's folder. After that all you have to do is use pyuic or pyside6-uic with proper settings (look at --help to set the output filename).
Then all you have is a .ui file (which is used in case you would like to add some widget, or change the widget all all) and your generated python class that inherits QObject, eg:
class Ui_Form(object):
def setupUi(self, Form):
if not Form.objectName():
Form.setObjectName(u"Form")
Form.resize(800, 600)
self.verticalLayout = QVBoxLayout(Form)
self.verticalLayout.setObjectName(u"verticalLayout")
self.label = QLabel(Form)
self.label.setObjectName(u"label")
Then create a another Python file, that inherits this generated class, eg.:
from PySide6.QtWidgets import QWidget
from CreateNewDatabase_ui import Ui_Form
class NewDatabaseWidget(QWidget):
def __init__(self):
super().__init__()
self.ui = Ui_Form()
self.ui.setupUi(self)
That's all. You have your prepared QWidget for adding it into a QStackedWidget. I use it in my own program and it fit me very well.
Edit:
I've forgot about 2 important notes:
Never ever edit your file generated by pyuic or pyside6-uic program. Whenever you wish to change the .ui file, all changes made to autogenerated code will be lost.
It's better to keep all signals/slots, logic mechanism in your QMainWindow class. It's better approach in case of code readability.

Related

PySide2 change QLabel text by clicking QPushButton

I'm trying to change QLabel text by clicking QPushbutton.
I installed PySide2==5.15.2.1
Python 3.7.6 32bit in Windows 10 environment.
First, I made UI class (well, I didn't used Qt designer so I don't have *.ui file)
import PySide2.QtWidgets as qtw
...
class UI_MainWindow(object):
def setupUI(self, MainWindow):
window = qtw.QWidget()
someButton = qtw.QPushButton("button")
someLabel = qtw.QLabel("---")
...
MainWindow.setCentralWidget(window)
And there is some other class who has functions and connects as below
import PySide2.QtWidgets as qtw
from uiSomething import UI_MainWindow #uiSomething is a name of the file of the code above, who has UI_MainWindow class.
...
class something(qtw.QMainWindow, UI_MainWindow):
def __init__(self):
super(something, self).__init__()
self.ui = UI_MainWindow()
self.ui.setupUI(self)
self.someButton.clicked.connect(self.function) # not working. ui.someButton... also not working.
#if I put this connect in UI_MainWindow, at least connect works.
def function(self):
self.ui.someLabel.setText("change text") # not working. ui.someLabel...also not working.
and the other file, I put main function
import Something
import PySide2.QtWidgets as qtw
...
if __name__ == "__main__":
app = qtw.QApplication()
MainWindow = Something.something()
MainWindow.show()
app.exec__()
However, it just give me an error message as below when I click the button.
AttributeEror: 'UI_MainWindow' object has no attribute 'someLabel'
I thought it's okay to define every widgets in setupUI function but probably not..?
Please let me know if there is any idea.
Thank you in advance!
Best wishes,
JESuh
Because someButton and someLabel are not declared as class attributes. These variables are only accessible within the function(setupUI). Objects created from UI_MainWidow cannot call or modify them. Not even if you create a subclass of UI_MainWindow.
Change
import PySide2.QtWidgets as qtw
...
class UI_MainWindow(object):
def setupUI(self, MainWindow):
window = qtw.QWidget()
someButton = qtw.QPushButton("button")
someLabel = qtw.QLabel("---")
...
MainWindow.setCentralWidget(window)
To
import PySide2.QtWidgets as qtw
...
class UI_MainWindow(object):
def setupUI(self, MainWindow):
window = qtw.QWidget()
self.someButton = qtw.QPushButton("button")
self.someLabel = qtw.QLabel("---")
...
MainWindow.setCentralWidget(window)
#blaqICE's answer is a good start but I'm noticing a handful of other things that are contributing to your issues.
The main things I'm seeing are your use of class inheritance and instance attributes. Overall organization seems to be working against you as well. The way you're doing it is workable but it gets confusing pretty quickly. I'm not sure how familiar you are with creating classes but I would spend some time in the python classes docs and looking at PySide example code.
If you want to fix your code the way it is all you need to do is fix the lines I commented on:
uiSomething.py
import PySide2.QtWidgets as qtw
class UI_MainWindow(object):
def setupUI(self, MainWindow): # consider using __init__ instead
self.window = qtw.QWidget() # make an instance attribute for later access with your class instance; not needed with __init__ and proper inheritance
self.main_layout = qtw.QVBoxLayout() # create a layout to hold everything
self.window.setLayout(self.main_layout) # set 'self.window' layout to the new 'self.main_layout'
self.someButton = qtw.QPushButton("button") # make an instance attribute for later access with your class instance
self.main_layout.addWidget(self.someButton) # add 'self.someButton' to 'self.main_layout'
self.someLabel = qtw.QLabel("---") # make an instance attribute for later access with your class instance
self.main_layout.addWidget(self.someLabel) # add 'self.someLabel' to 'self.main_layout'
MainWindow.setCentralWidget(self.window)
Something.py
from uiSomething import UI_MainWindow
class something(qtw.QMainWindow, UI_MainWindow):
def __init__(self):
super(something, self).__init__()
self.ui = UI_MainWindow()
self.ui.setupUI(self) # consider using __init__ in class definition instead; this line would not be needed
self.ui.someButton.clicked.connect(self.function) # 'self.ui' before 'someButton' to access instance's someButton
def function(self):
self.ui.someLabel.setText("change text") # this is fine
# this was not working because of the issue with someButton's signal connection
main.py
import Something
import PySide2.QtWidgets as qtw
# import sys
if __name__ == "__main__":
app = qtw.QApplication()
MainWindow = Something.something()
MainWindow.show()
app.exec_() # only one underscore; typically done as 'sys.exit(app.exec_())' to formally exit python interpreter
However, a better implementation would be:
uiSomething.py
import PySide2.QtWidgets as qtw
class UI_MainWidget(qtw.QWidget): # UI_MainWindow -> UI_MainWidget for clarity; inherit QWidget
def __init__(self, button_text="button", lable_click_text="change text", parent=None): # auto initialize object
super(UI_MainWidget, self).__init__(parent) # pass parent to inherited class __init__ function
# 'self.window' no longer needed due to __init__ and can be accessed simply through 'self'
self.parent = parent # set parent as instance variable for later use if needed
self.button_text = button_text # set custom arguments as instance variable for later use if needed
self.lable_click_text = lable_click_text # set custom arguments as instance variable for later use if needed
self.main_layout = qtw.QVBoxLayout() # create a layout to hold everything
self.setLayout(self.main_layout) # set the widget's layout to the new self.main_layout
self.someButton = qtw.QPushButton(self.button_text) # class argument for easy customization of other instances
self.someButton.clicked.connect(self.function) # moved to instantiation class for clarity and ease of access
self.main_layout.addWidget(self.someButton)
self.someLabel = qtw.QLabel("---") # placeholder text
self.main_layout.addWidget(self.someLabel)
# moved '.setCentralWidget(self)' to main window class for organization and ease of access
def function(self): # move to instantiation class for clarity, organization, and ease of access
# 'self.ui' no longer needed in instantiation class, use 'self' instead
self.someLabel.setText(self.lable_click_text) # class argument for easy customization of other instances
Something.py
import PySide2.QtWidgets as qtw # need to import module here as well due to inheritance
from uiSomething import UI_MainWidget
# for this class, double inheritance doesn't really have any effect. Stick with the main one it represents.
class UI_MainWindow(qtw.QMainWindow): # something -> UI_MainWindow for clarity; inherit QMainWindow only; PEP 8 naming
def __init__(self, parent=None): # added parent kwarg for parenting to other widgets if needed
super(UI_MainWindow, self).__init__(parent) # pass parent to inherited class __init__ function
self.ui = UI_MainWidget(parent=self)
# self.ui.setupUI(self) # no longer needed due to __init__; can delete
# using custom class keyword arguments
# self.ui = UI_MainWidget(button_text="new button name", lable_click_text="new button clicked", parent=self)
# self.ui = UI_MainWidget(button_text="another button name", lable_click_text="another button clicked", parent=self)
self.setCentralWidget(self.ui)
main.py
import Something
import PySide2.QtWidgets as qtw
import sys
if __name__ == "__main__":
app = qtw.QApplication()
MainWindow = Something.UI_MainWindow()
MainWindow.show()
sys.exit(app.exec_())

PyQt Update Child Widget Property

I would like to update a TextBrowser widget in another window from the main window. The goal is to simply write strings to the widget to act as a pseudo console. When I inspect the QTextBrowser object, I can see that it supports setText() -- but I can not find a way to access setText() from the main window object (which I suspect is because I'm not calling it properly). Using a TextBrowser widget is not a requirement.
The examples I've seen talk about using signals and slots for such operations but that seems to be overkill for what I'm trying to accomplish. Is it a requirement to use signals for something so simple, or can I somehow setText() (or something similar) from the main window?
scratch.py
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
from PyQt5 import QtWidgets
import sys
from log_dialog import Ui_dialog
class Application(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(Application, self).__init__(*args, **kwargs)
self.show()
# ===================================================
self.log_widget = QtWidgets.QDialog()
ui = Ui_dialog()
ui.setupUi(self.log_widget)
self.log_widget.exec_()
# Write some text to log_widget <-----
# ===================================================
qApp = QtWidgets.QApplication(sys.argv)
application_window = Application()
sys.exit(qApp.exec_())
log_dialog.py
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'log_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.3
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_dialog(object):
def setupUi(self, dialog):
dialog.setObjectName("dialog")
dialog.resize(398, 307)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(dialog.sizePolicy().hasHeightForWidth())
dialog.setSizePolicy(sizePolicy)
self.textBrowser = QtWidgets.QTextBrowser(dialog)
self.textBrowser.setGeometry(QtCore.QRect(9, 9, 381, 291))
self.textBrowser.setObjectName("textBrowser")
self.retranslateUi(dialog)
QtCore.QMetaObject.connectSlotsByName(dialog)
def retranslateUi(self, dialog):
_translate = QtCore.QCoreApplication.translate
dialog.setWindowTitle(_translate("dialog", "Log"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
dialog = QtWidgets.QDialog()
ui = Ui_dialog()
ui.setupUi(dialog)
dialog.show()
sys.exit(app.exec_())
All objects created by the "form class" (the Ui_dialog) are members of that form class instance.
If you look at the setupUi() code, you'll see that all widgets are created as members of the Ui_dialog class instance (self.textBrowser, etc).
Since ui is the instance, self.someObject becomes accessible as ui.someObject.
ui = Ui_dialog()
ui.setupUi(self.log_widget)
ui.textBrowser.setText('Hello there!')
self.log_widget.exec_()
For very simple situations this could be fine, but using the ui "form class" as a standalone object is usually not very convenient, especially if you're not creating a persistent reference to the ui: in your case as soon as the __init__ of the main window returns (when the dialog is closed), you won't have have no more reference to the ui, as it's just an unreferenced variable that exists only within the scope of __init__.
Nonetheless, you will still be able to access the dialog and execute it again, and the only way to access any of its children is through:
self.log_widget.findChild(QtWidgets.QTextBrowser, 'textBrowser')
Not very convenient, uh?
A possibility is to make the ui a member of the dialog instance:
self.log_widget.ui = Ui_dialog()
self.log_widget.ui.setupUi(self.log_widget)
self.log_widget.ui.textBrowser.setText('Hello there!')
Better, but not wonderful: it's just 3 characters, but it really doesn't provide a lot of benefits. Also, it really doesn't make a lot of sense to have objects that are direct children of self.log_widget but are only accessible through self.log_widget.ui.
The more common and suggested approach is the multiple inheritance method, which allows to always have direct access to the objects. The only downside is that even for simple widgets and dialogs you always need to create a subclass.
class LogWidget(QtWidgets.QDialog, Ui_dialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
class Application(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(Application, self).__init__(*args, **kwargs)
# ...
self.log_widget = LogWidget()
self.log_widget.textBrowser.setText('hello there!')
self.log_widget.exec_()
As you can see, it works almost in the same way, except from the fact that it's more simple, concise and readable.

pyqt5 mainwindow close event - recheck if user wants to close window [duplicate]

I'm using Qt Designer for design GUI to use in python, after designing my desired UI in Qt Designer, convert it to python code and then I changed generated code to do some action in my python code, but if I changed the UI with Qt Designer and convert it to python code again, I lost my previous changes on my code.
how can I solve the problem?
can we Spreading a Class Over Multiple Files in python to write code in other files?
To avoid having these problems it is advisable not to modify this file but to create a new file where we implement a class that uses that design.
For example, suppose you have used the MainWindow template in the design.ui file, then convert it to Ui_Design.py like to the following structure:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
[...]
def retranslateUi(self, MainWindow):
[...]
Then we will create a new file that we will call logic.py where we will create the file that handles the logic and that uses the previous design:
class Logic(QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
QMainWindow.__init__(self, *args, **kwargs)
self.setupUi(self)
So even if you modify the design and generate the file again .py you will not have to modify the file of the logic.
To generalize the idea we must have the following rules but for this the logic class must have the following structure:
class Logic(PyQtClass, DesignClass):
def __init__(self, *args, **kwargs):
PyQtClass.__init__(self, *args, **kwargs)
self.setupUi(self)
PyQtClass: This class depends on the design chosen.
Template PyQtClass
─────────────────────────────────────────────
Main Window QMainWindow
Widget QWidget
Dialog with Buttons Bottom QDialog
Dialog with Buttons Right QDialog
Dialog with Without Buttons QDialog
DesignClass: The name of the class that appears in your design.
The advantage of this implementation is that you can implement all the logic since it is a widget, for example we will implement the solution closing pyqt messageBox with closeevent of the parent window :
class Logic(QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
QMainWindow.__init__(self, *args, **kwargs)
self.setupUi(self)
def closeEvent(self, event):
answer = QtWidgets.QMessageBox.question(
self,
'Are you sure you want to quit ?',
'Task is in progress !',
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if answer == QtWidgets.QMessageBox.Yes:
event.accept()
else:
event.ignore()
The easiest way is to use the *.ui file directly in the python code, you don't need convert to *.py file every time you change the ui.
you can use this pseudo code in your project.
# imports
from PyQt5 import uic
# load ui file
baseUIClass, baseUIWidget = uic.loadUiType("MainGui.ui")
# use loaded ui file in the logic class
class Logic(baseUIWidget, baseUIClass):
def __init__(self, parent=None):
super(Logic, self).__init__(parent)
self.setupUi(self)
.
.
.
.
def main():
app = QtWidgets.QApplication(sys.argv)
ui = Logic(None)
ui.showMaximized()
sys.exit(app.exec_())

Why doesn't this custom QWidget display correctly

I'm retrying this question with a much better code example.
The code below, in its current form, will display a green shaded QWidget in a window, which is what I want. However, when commenting out the line:
self.widget = QWidget(self.centralwidget)
and uncommenting,
self.widget = Widget_1(self.centralwidget)
the green box doesn't display. The Widget_1 class is a simple subclass of QWidget, so I'm trying to wrap my head around where the breakdown is occurring. There are no error messages, and the print("Test") line within the Widget_1 class is outputting just fine, so I know everything is being called properly.
I'm not looking to use any type of automated layouts for reasons I don't need to go into here. Can you help me to understand why the green rectangle isn't displaying, and what correction I would need to make in order to utilize the Widget_1 class?
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget
from PyQt5.QtCore import QRect
import sys
class Main_Window(object):
def setupUi(self, seating_main_window):
seating_main_window.setObjectName("seating_main_window")
seating_main_window.setEnabled(True)
seating_main_window.resize(400, 400)
self.centralwidget = QWidget(seating_main_window)
self.centralwidget.setObjectName("centralwidget")
########### The following two lines of code are causing the confusion #######
# The following line, when uncommented, creates a shaded green box in a window
self.widget = QWidget(self.centralwidget) # Working line
# The next line does NOT create the same shaded green box. Where is it breaking?
# self.widget = Widget_1(self.centralwidget) # Non-working line
self.widget.setGeometry(QRect(15, 150, 60, 75))
self.widget.setAutoFillBackground(False)
self.widget.setStyleSheet("background: rgb(170, 255, 0)")
self.widget.setObjectName("Widget1")
seating_main_window.setCentralWidget(self.centralwidget)
class Widget_1(QWidget):
def __init__(self, parent=None):
super().__init__()
self.setMinimumSize(10, 30) # I put this in thinking maybe I just couldn't see it
print("Test") # I see this output when run when Widget_1 is used above
class DemoApp(QMainWindow, Main_Window):
def __init__(self):
super().__init__()
self.setupUi(self)
if __name__ == '__main__': # if we're running file directly and not importing it
app = QApplication(sys.argv) # A new instance of QApplication
form = DemoApp() # We set the form to be our ExampleApp (design)
form.show() # Show the form
app.exec_() # run the main function
Accoriding to this Qt Wiki article:
How to Change the Background Color of QWidget
you must implement paintEvent in a custom QWidget subclass in order to use stylesheets. Also, since the widget is not part of a layout, you must give it a parent, otherwise it will not be shown. So your Widget_1 class must look like this:
from PyQt5.QtWidgets import QStyleOption, QStyle
from PyQt5.QtGui import QPainter
class Widget_1(QWidget):
def __init__(self, parent=None):
super().__init__(parent) # set the parent
print("Test")
def paintEvent(self, event):
option = QStyleOption()
option.initFrom(self)
painter = QPainter(self)
self.style().drawPrimitive(QStyle.PE_Widget, option, painter, self)
super().paintEvent(event)

add pyqtgraph (plot) into QApplication

hy,
soooo, i created the MainWindow.ui file with the QTDesigner. Then i import this gui with following command into my .py file:
form_class = uic.loadUiType("ess_project.ui")[0]
What is the difference if i compile this .ui file with pyuic4 ?
(every time i compiled my .ui file i got following error:
RuntimeError: the sip module implements API v11.0 to v11.1 but the PyQt4.QtCore module requires API v10.1
The MainWindow create the first window, where all buttons etc. are placed.
class MainWindow(QtGui.QMainWindow, form_class):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
PlotWindow.__init__(self)
self.setupUi(self)
self.pb_send.clicked.connect(self.pb_send_clicked)
self.pb_open.clicked.connect(self.pb_open_clicked)
self.pb_exit.clicked.connect(self.pb_exit_clicked)
self.comboBox.currentIndexChanged.connect(self.combo_box_changed)
furthermore i have a second class named "PlotWindow". This class looks like this:
class PlotWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.w = QtGui.QMainWindow()
self.cw = pg.GraphicsLayoutWidget()
self.w.show()
self.w.resize(900,600)
self.w.setCentralWidget(self.cw)
self.w.setWindowTitle('pyqtgraph: G-CODE')
self.p = self.cw.addPlot(row=0, col=0)
now as you can see, the PloWindow - class create a second Window.
How can i implement the pg.GraphicsLayoutWidget() into the MainWindow - class ??
not sure if this can help you ?!? :
def main():
app = QtGui.QApplication([])
myWindow = MainWindow(None)
myWindow.show()
app.exec_()
if __name__ == '__main__':
main()
I am using python3 !!!
feel FREE to comment :)
thanks !
To place any pyqtgraph widgets inside your application, you add a placeholder widget and "promote" it to the pg class that you want. See: http://www.pyqtgraph.org/documentation/how_to_use.html#embedding-widgets-inside-pyqt-applications

Resources