I am building a graphical interface for an application using PySide2. My main window is a QMainWindow and I am trying to open a pop-up window, which is a QDialog, whenever a specific action is performed on the main window.
The pop-up opens perfectly fine. However, after it is open, the main window is no longer responsive. I believe the problem is that my application is overwriting the main window with the popup window.
The error message whenever I try to change the main window's stackedWidget index is:
AttributeError: 'Ui_popupHideSuccess' object has no attribute 'stackedWidget'
The code I am using to open the main window is the following:
if __name__ == '__main__':
app = QApplication(sys.argv)
myWindow = MainWindow()
myWindow.show()
sys.exit(app.exec_())
And the code I am using to open the pop-up window is the following:
def showPopupSuccessHide(self):
self.window = QDialog()
self.ui = Ui_popupHideSuccess()
self.ui.setupUi(self.window)
self.window.show()
The code for the windows themselves are on other files (as I am using QtDesigner for developing them). I believe it to be unnecessary for resolving this issue, but I can provide it if needed. What am I doing wrong here? I need to open pop-ups and still interact with the main window after.
I have no idea how to actually resolve this. I believe my error to be in the code I am using to open the pop-up window, but I'm not sure how to tweak it for it to work properly.
TL;DR
Do not overwrite self.ui.
Explanation
How uic composition works
One of the common ways of properly using pyuic generated files is to use composition (as opposed to multiple inheritance):
from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog
from ui_mainWindow import Ui_MainWindow
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.myLineEdit.setText('some text')
This is perfectly fine, and makes sense: the concept is that an instance of the pyuic class (sometimes called "form class") is created and then the actual window is "set up" using that instance, with the self.ui object containing references to all widgets.
Note that making the ui persistent (using an instance attribute) is actually not a strict requirement, but it is usually necessary in order to be able to directly access the widgets, which is normally important to create signal connections or read properties.
But, if that's not required, it will work anyway: the widgets are automatically "reparented" to the main window (or their direct parents), and the garbage collection is not an issue as Qt will keep its own references internally (in Qt terms, "the window takes ownership").
Technically speaking, this is completely valid:
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
Ui_MainWindow().setupUi(self)
Then, we can still access the widgets using findChild and their object names (those set in Designer):
self.findChild(QLineEdit, 'myLineEdit').setText('some text')
Obviously, it is not very practical.
Creating "child" windows
When there is the need to create a child window (usually, a dialog), it's normally suggested to use an instance attribute to avoid garbage collection:
def createWindow(self):
self.window = QDialog()
self.window.show()
If that dialog also has a Designer file, we need to do something similar to what done at the beginning. Unfortunately, a very common mistake is to create the ui instance by using the same name:
def createWindow(self):
self.window = QDialog()
self.ui = Ui_Dialog()
self.ui.setupUi(self.window)
self.ui.anotherLineEdit.setText('another text')
self.window.show()
This is theoretically fine: all works as expected. But there's a huge problem: the above overwrites self.ui, meaning that we lose all references to the widgets of the main window.
Suppose that you want to set the text of the line edit in the dialog based on the text written in that of the main window; the following will probably crash:
def createWindow(self):
self.window = QDialog()
self.ui = Ui_Dialog()
self.ui.setupUi(self.window)
self.ui.anotherLineEdit.setText(self.ui.myLineEdit.text())
self.window.show()
This clearly shows an important aspect: it's mandatory to always think before assigning attributes that may already exist.
In the code here above, this was actually done twice: not only we overwrote the self.ui we created before, but we also did it for window(), which is an existing function of all Qt widgets (it returns the top level ancestor window of the widget on which it was called).
As a rule of thumb, always take your time to decide how to name objects, use smart names, and consider that most common names are probably already taken: remember to check the "List of all members, including inherited members" link in the documentation of the widget type you're using, until you're experienced enough to remember them.
Solutions
The obvious solution is to use a different name for the ui of the dialog:
def createWindow(self):
self.dialog = QDialog()
self.dialog_ui = Ui_Dialog()
self.dialog_ui.setupUi(self.dialog)
self.dialog_ui.anotherLineEdit.setText(self.ui.myLineEdit.text())
self.dialog.show()
A better solution is to create a subclass for your dialog instead:
class MyDialog(QDialog):
def __init__(self, parent=None)
super().__init__(parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
class MyWindow(QMainWindow):
# ...
def createWindow(self):
self.dialog = MyDialog()
self.dialog.ui.anotherLineEdit.setText(self.ui.myLineEdit.text())
self.dialog.show()
Also remember that another common (and, to my experience, simpler and more intuitive) method is to use multiple inheritance instead of composition:
class MyDialog(QDialog, Ui_Dialog):
def __init__(self, parent=None)
super().__init__(parent)
self.setupUi(self)
class MyWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.myLineEdit.setText('some text')
def createWindow(self):
self.dialog = MyDialog()
self.dialog.anotherLineEdit.setText(self.myLineEdit.text())
self.dialog.show()
The only issue of this approach is that it may inadvertently overwrite names of functions of the "main" widget: for instance, if you created a child widget in Designer and renamed it "window". As said above, if you always think thoroughly about the names you assign to objects, this will probably never happen (it doesn't make a lot of sense to name a widget "window").
Related
This question already has answers here:
Benefits of using pyuic vs uic.loadUi
(2 answers)
Closed 1 year ago.
Is there an advantage to:
Converting it to python
pyside6-uic mainwindow.ui > ui_mainwindow.py
and then
import sys
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtCore import QFile
from ui_mainwindow import Ui_MainWindow
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
vs. loading it directly like so: ?
ui_file = QFile("mainwindow.ui")
ui_file.open(QFile.ReadOnly)
loader = QUiLoader()
window = loader.load(ui_file)
window.show()
I suppose the app will start faster/run faster if converted beforehand.
Is there anything else to consider?
There are two main differences:
in terms of loading, QUiLoader theoretically adds a bit of overhead because it has to build the ui everytime, meaning that it has to parse the XML file, create the node structure, and then create the UI with all its contents; the uic file, instead, directly creates the UI, skipping the first two steps above;
QUiLoader can only create a new widget based on the UI file, while the uic method allows to use an already existing base widget, and the child widgets can be added to it;
The latter point is probably the most important: using QUiLoader you cannot directly use subclassing for the loaded UI.
For instance, if you create a main window in Designer, QUiLoader will return a new QMainWindow. You cannot (or, at least, you should not) do the following:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
ui_file = QFile("mainwindow.ui")
ui_file.open(QFile.ReadOnly)
loader = QUiLoader()
window = loader.load(ui_file, self)
And you should not even try to make the returned object as central widget, like the following:
self.setCentralWidget(window)
because the result would be to have a QMainWindow inside a QMainWindow, which is discouraged and unsupported, and might also create problems when using the standard features of a QMainWindow (typically, docks and toolbars).
The only alternative would be to create a basic form widget in Designer and use that as central widget, with the downside that menus, docks and toolbars have to be created by code.
For PySide, the only possibility that allows full subclassing is to use the pyside-uic method and then eventually use the multiple inheritance (but that's not a requirement, as composition is a valid alternative anyway):
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
On the other hand, PyQt provides the loadUi function that actually does what setupUi does, since the second argument is not the parent widget, but the widget itself, and the contents of the ui will be loaded into it:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
uic.loadUi("mainwindow.ui", self)
As far as I know, PySide doesn't provide anything similar yet.
Note that loading the ui at runtime has two issues anyway, and for both bindings:
there is no prior sanity checking, if the UI file is corrupted or invalid, or has unsupported features/properties due to version mismatch, it might not load properly or even crash;
when using an IDE, there's no code completion for ui objects, since they are only loaded at runtime;
Those are not major issues, but it's important to be aware of them anyway.
I am trying to develop a small gui using PySide and the QT Creator.
As the base implementation, I have choosen a QMainWindow.
The problem is that adding any elements to that MainWindow inside the Editor results in an empty window when I run the code.
The initially generated python code looks like this:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.load_ui()
def load_ui(self):
loader = QUiLoader()
path = os.path.join(os.path.dirname(__file__), "form.ui")
ui_file = QFile(path)
ui_file.open(QFile.ReadOnly)
loader.load(ui_file, self)
ui_file.close()
if __name__ == "__main__":
app = QApplication([])
widget = MainWindow()
widget.show()
sys.exit(app.exec_())
I know that loader.load(ui_file, self) returns a widget. My solution was:
def load_ui(self):
loader = QUiLoader()
path = os.path.join(os.path.dirname(__file__), "form.ui")
ui_file = QFile(path)
ui_file.open(QFile.ReadOnly)
self.window = loader.load(ui_file, self)
self.window.show()
ui_file.close()
if __name__ == "__main__":
app = QApplication([])
widget = MainWindow()
#widget.show() <<<<<<--- removing this
sys.exit(app.exec_())
This works for me but this clearly cannot be the way this is supposed to work.
I am confused on why QT Creator gives me this non-working templaet.
Furthermore I am curious on how custom functions should be implemented.
When clicking on the clicked() slot for a button, it tells me
No ui_form.h found! (translated)
I ended up writing the functions into my MainWindow class like this:
self.window.menuopen_button.clicked.connect(lambda x: self.menu_animation(x))
self.window.minimise_button.clicked.connect(lambda x: self.menu_buttons('minimise'))
Your initial code only loads the UI file into the QUiLoader object - you don't do anything else with it. When you call widget.show(), you're calling the QMainWindow's show method which is a default window (and, hence, empty) since you haven't added the loaded widget to it.
Your solution similarly loads the UI but displays it via the widget object's show method. However, you're not amending it to your main window, so you're basically tossing your main window and just using the widget.
There are a couple ways to correctly accomplish what you want:
One way would be to, as #musicamante suggested, add the loaded UI widget to your MainWindow object (and by the way, this is supported by PySide2: https://doc.qt.io/archives/qtforpython-5.12/PySide2/QtUiTools/QUiLoader.html - the example in the detailed description shows exactly how to do this).
Or the other way, which I prefer, would be to use the uic utility, PySide2-uic, in the build process to generate a UI Python class based on the UI file.
The Qt documentation has tutorials showing both methods. The linked documentation shows PySide6 as support for PySide2 has been discontinued with the release of Qt6. The API for PySide2, PySide6, and PyQt5 are all very similar, so if you're migrating from either PySide2 or PyQt then the change should be relatively painless. I recommend using PySide6 as this is the Python binding officially supported by Qt.
This question already has an answer here:
How to use QScrollArea to make scrollbars appear
(1 answer)
Closed 2 years ago.
I'm trying to turn a widget into a scrollable window. However, all I've accomplished is opening a QScrollarea that's completely independent from my central widget.
class Ui_MainWindow(object):
def mainscreen(self, MainWindow):
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.scrollArea = QtWidgets.QScrollArea()
self.scrollArea.setWidget(self.centralwidget)
MainWindow.setCentralWidget(self.centralwidget)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.mainscreen(MainWindow)
MainWindow.show()
app.exec_()
This just opens up my MainWindow on its own, only way I get an scrollarea open is if I do self.scrollarea.show . I cant figure out how QScrollArea works, how am I supposed to do it?
Simple explanation: the scroll area has no relation with the main window, and then it's not shown when the main window is.
Never, ever modify (or imitate) files generated by pyuic
You are making some confusion around the object structure, Qt parent/ownership and how a scroll area is set up.
This is also caused by the fact that you're clearly trying to mimic the structure and behavior of a file generated by pyuic, which should never be done. Those files are intended to be directly used as they are, without any modification, and imported in the main script of your program.
Their peculiar structure is intended only for that purpose, and the way they're built is not to be emulated, as creation of UI by code should only be done by subclassing a QWidget subclass (QMainWindow, QDialog, etc); trying to imitate what they do is not only unnecessary, but most times leads to confusion, bugs and unexpected behavior, exactly like in your case.
The only valid reason to open (and just read) a file generated by pyuic is to learn how widgets are created in the setupUi, keeping in mind that the setupUi argument (usually, MainWindow for QMainWindows, Form for QWidgets, Dialog for QDialogs, unless the name of the top level widget is changed in the object inspector of designer) is the main widget on which the ui is built upon.
Manually editing those files should only be done if one really knows what she/he is doing. There are few, very rare cases for which this is considered necessary, and it's usually to workaround very specific known bugs in the uic module, that normally rise on very specific conditions and situations.
What is happening?
Then, when a QWidget instance is created with an existing widget as argument, the result is that the new widget becomes a child of the existing one.
Widget can be then reparented in various ways: by setting a widget for a scroll area using setWidget(), or by setting the widget as a central widget to a QMainWindow.
What happens with your code is that you created a new QWidget that becomes a child of the QMainWindow with the following line:
self.centralwidget = QtWidgets.QWidget(MainWindow)
Then you create a scroll area (without any parent):
self.scrollArea = QtWidgets.QScrollArea()
Widgets that are not created with a parent are normally considered top level windows, unless they are added to a layout (or manually reparented using setParent(), but that's another story), which causes the parent (the widget on which the layout is set) to take ownership of the new child widget. Otherwise, if you try to show the "parent-less" widget, it will be shown as a top level widget, meaning it will have its own window, and as any top level widget, they can only be shown by manually calling show() or setVisible(True) (and that's your case).
Then you try to set the centralwidget of the scroll area, which will result in reparenting it:
self.scrollArea.setWidget(self.centralwidget)
Finally, you're setting that widget as the central widget, which will reparent it back again to the main window, with the result that the scroll area will not have a widget anymore (QObjects, from which QWidget inherits, can only have a single parent).
MainWindow.setCentralWidget(self.centralwidget)
The correct approach
The solution is to correctly create a QWidget (or QMainWindow in your case) subclass, set the scroll area as the central widget (if you want it as that) and create a new widget for its contents:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.scrollArea = QtWidgets.QScrollArea()
self.setCentralWidget(self.scrollArea)
self.scrollArea.setWidgetResizable(True)
self.contents = QtWidgets.QWidget()
self.scrollArea.setWidget(self.contents)
# create a layout for the scroll area contents; using the target widget
# as argument of the layout constructor automatically sets the layout on
# the specified widget
layout = QtWidgets.QVBoxLayout(self.contents)
# the same as:
# layout = QtWidgets.QVBoxLayout()
# self.contents.setLayout(layout)
for row in range(20):
button = QtWidgets.QPushButton('button {}'.format(row + 1))
layout.addWidget(button)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
I can't get the GUI for my application to run in the manner that I need it to. My question is, given the below criteria, how do I go about setting up and running the GUI properly. The lack of good documentation that I have found is insanely frustrating (maybe I'm looking in the wrong places?).
I have a main window in a file called MainCustomerWindow.py containing a class by the same name. This is where all the code from the qt designer is. I have another class file called GUIController. The GUIController class does just that, controls the multiple GUI windows. It is in this GUIController class that I am trying to instantiate and run the MainCustomerWindow. Here is the code I have been trying.
def setup_window(self):
APP = QtWidgets.QApplication(sys.argv)
Window = MainCustomerWindow()
Window.setupUi(QtWidgets.QMainWindow)
Window.show()
sys.exit(APP.exec_())
Just as a side note, I come from JavaFX and Swing, and don't fully understand the workflow for pyqt5. So if someone could add an explanation for that as well it would be greatly appreciated.
The class generated by Qt Designer is not a widget, it is a class used to fill an existing widget, so you must create an object in the window, assuming you have used the "Main Window" template, then the widget must be QMainWindow (if it is another maybe you should use QDialog or QWidget), then you have to create another class that belongs to the design, and using the method setupUi() you must pass the widget to fill it:
def setup_window(self):
app = QtWidgets.QApplication(sys.argv)
# create window
window = QtWidgets.QMainWindow()
ui = MainCustomerWindow()
# fill window
ui.setupUi(window)
window.show()
sys.exit(app.exec_())
Although a better option is to create a new class and have it inherit from both:
class MainWindow(QtWidgets.QMainWindow, MainCustomerWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
If you want to get detailed information I recommend you read the following:
http://pyqt.sourceforge.net/Docs/PyQt5/designer.html
You can try taking the code you have there and adding it to the main statement at the end of your app's script. You can also have this statement instantiate your class where the init method contains the setupui() call. For example:
if __name__ == '__main__':
app = QWidgets.QApplication(sys.argv)
window = QMainWindow()
main_window = MainCustomerWindow()
window.show()
sys.exit(app.exec())
This code first sets up the PyQt app as an instance of QApplication. Next it instantiates an instance of QMainWindow so that PyQt knows what to display as the main app starts. In my experience, I've put setupui() in the init method of the app class. In your case, in the init method of MainCustomerWindow Finally, window.show() tells PyQt to begin rendering the main window.
Let's assume I want to create a dialog box, a child of my main program:
from PyQt4 import QtGui, QtCore
class WizardJournal(QtGui.QDialog):
def __init__(self, parent):
super(WizardJournal, self).__init__(parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.wizard = QtGui.QWidget()
self.ok_button = QtGui.QPushButton("OK", self)
self.vbox_global = QtGui.QVBoxLayout(self)
self.vbox_global.addWidget(self.ok_button)
self.paret.wizard.setLayout(self.vbox_global)
self.parent.wizard.show()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
parent = QtGui.QWidget()
obj = WizardJournal(parent)
sys.exit(app.exec_())
This dialog box will be opened and closed by my main program. What is better regarding memory consumption:
self.ok_button = QtGui.QPushButton("OK", self)
self.ok_button = QtGui.QPushButton("OK")
Basically, I would like to know if I should mention the parent widget when I create a widget. When I will close this dialog box, will the OK button be released from memory if I didn't mention the parent widget when I created it ?
Given the way your example is currently structured, neither the dialog nor any of its child widgets will be deleted when it is closed.
You can see this by changing the end of the example to look like this:
app.exec_()
print('\n'.join(repr(w) for w in app.allWidgets()))
which will give output like this (once the dialog is closed):
<__main__.WizardJournal object at 0x7fcd850f65e8>
<PyQt4.QtGui.QPushButton object at 0x7fcd850f6708>
<PyQt4.QtGui.QWidget object at 0x7fcd850f6558>
<PyQt4.QtGui.QDesktopWidget object at 0x7fcd850f6828>
<PyQt4.QtGui.QWidget object at 0x7fcd850f6678>
In PyQt, you have to be aware that there may be two kinds of reference held for a object: one on the Python side (the PyQt wrapper object) and one on the C++ side (the underlying Qt object). So to fully delete an object, you need to remove all of these references.
In general, Qt does not delete objects unless you explictly tell it to do so. This is something you need to be aware of when creating dialogs with a parent, because it is very easy to produce a memory leak otherwise. It is common to see code written like this:
def openDialog(self):
dialog = MyDialog(self)
dialog.show()
Which looks harmless at first glance - but the method will create a new dialog every time it is called, and Qt will end up holding on to every single one of them (because of the parent reference on the C++ side). One way to avoid this is to re-write the method so that it only keeps a reference on the Python side:
def openDialog(self):
self.dialog = MyDialog()
self.dialog.show()
But what to do about a modal dialog, which must have a parent? In that case, you could initialise the dialog class like this:
class MyDialog(QtGui.QDialog):
def __init__(self, parent):
super(MyDialog, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
And now Qt will automatically delete the dialog when it is closed, and also recursively delete all of its child objects as well. This will just leave behind an empty PyQt wrapper object, which will (eventually) be removed by the Python garbage-collector.
So for your particular example, I think I would re-write it to look something like this:
import sys
from PyQt4 import QtGui, QtCore
class WizardJournal(QtGui.QDialog):
def __init__(self, parent):
super(WizardJournal, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.initUI()
def initUI(self):
self.ok_button = QtGui.QPushButton("OK", self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.ok_button)
self.show()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
parent = QtGui.QWidget()
obj = WizardJournal(parent)
app.exec_()
print('\n'.join(repr(w) for w in app.allWidgets()))
The dialog class is now completely self-contained, and there is only one external python reference to the instance of it. (If you need to access the parent widget from within the dialog class, you can use self.parent()).
PS: when widgets are added to a layout, they will be automatically re-parented to whatever top-level widget eventually contains the layout. So, strictly speaking, it is not necessary to explicitly set a parent for such widgets in your code.