Introduction
In my tutorial on Python GUI's with PyQt, I had many people bring up the fact that when modifying the GUI, as soon as pyuic5 is executed again to rebuild the Python file, all original changes will be lost.
In a sense, this is true. For demonstration proposes, I had put all code into the Python file generated, but a smarter way to add code would be have been to import the generated file so that when it changes (executed pyuic5 again to create an updated .py file), it would only have affected the imported file. This method also allows for separation of the GUI and logic.
In this tutorial, I am going to cover a method that allows you to import the .ui file generated by PyQt Designer directly in Python. Please be aware that there is a lot more effort when importing it this way and it can be a lot harder to find where errors are occurring.
Installing PyQt5
Go to my previous tutorial to learn how to install PyQt5. Generally, you can install it using python -m pip install pyqt5
regarding your environment is set up correctly.
If you haven't got the designer, you can use python -m pip install pyqt5-tools
to install tools that contain the designer. Finding the executable can be a bit tough to find if you don't know where packages install so I would recommend reading the other post to help you find it.
Generating the UI File
As covered in my original PyQt5 tutorial, install the designer, locate it and use it. When saving the GUI you have created, it will be saved as a .ui
. This .ui file is XML that contains the layout of the GUI and other things you may have set in the designer application. Here is a snippet of an example .ui file:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>367</width>
<height>339</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
contents...
</widget>
</widget>
<resources/>
<connections/>
</ui>
Regarding you know what XML is, this is pretty basic; knowing a little bit of XML is required for this.
Importing the UI File In Python
First we need to import the modules required. We need QtWidgets
from PyQt5 for the base widget and uic
from PyQt5 also to load the file. We also need sys
to access arguments.
from PyQt5 import QtWidgets, uic
import sys
Next we need to create a base class that will load the .ui file in the constructor. It will need to call the __init__
method of the inherited class, load the .ui file into the current object and then show the window.
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__() # Call the inherited classes __init__ method
uic.loadUi('basic.ui', self) # Load the .ui file
self.show() # Show the GUI
It is very important here to inherit the correct class. In this example I inherit QtWidgets.QMainWindow
because I created a new "Main Window" when selecting the form type when first creating the .ui file in PyQt Designer. If you look back to the source of the .ui file, we can actually identify the class we need to inherit.
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
This is a snippet of the XML from before. You can see "MainWindow" is the root widget as all the content is wrapped in the <widget class="QMainWindow" name="MainWindow"> ... </widget>
element. This tag has a class
attribute; in this example, the value is QMainWindow
, which explains why I used QtWidgets.QMainWindow
.
Your widget class may be different so be sure to double-check!
After this, we then need to create an instance of this class we just made and execute it.
app = QtWidgets.QApplication(sys.argv) # Create an instance of QtWidgets.QApplication
window = Ui() # Create an instance of our class
app.exec_() # Start the application
Putting this all together, we get:
from PyQt5 import QtWidgets, uic
import sys
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('basic.ui', self)
self.show()
app = QtWidgets.QApplication(sys.argv)
window = Ui()
app.exec_()
This assumes that the .ui file is called
basic.ui
Run the script to make sure everything runs, if the GUI that you created appears, congratulations! If not, look back over what you may have missed and READ THE ERROR (I cannot stress this enough).
A small check-list for things that may go wrong:
- PyQt5 isn't installed
- The .ui file you are importing does not exist (incorrect reference)
- You did not inherit the correct class (found in the XML)
Getting Widget Object Pointers
Once you have the GUI being imported, you now need to identify some pointers for the objects you want to use. For this example I am going to use the .ui linked below:
This GUI contains 5 widgets that we can see:
- Text input line
- A button to the right of this saying "Print Content"
- 3 buttons below the input saying "Mode", "Set" and "Clear"
When looking at the XML, we can see that there is a centralwidget
inside a QMainWindow
and inside the centralwidget
are the 5 widgets I created.
Giving Widgets Unique Names to Find Them With
The most important part of getting a pointer to one of these widgets is to give each widget a unique name, preferably something that is friendly to read. Open my .ui file in the Designer or in notepad to see the names I have given each widget. In the XML you can see the 5 widgets I created have friendly name
attributes; these names can help us identify the widgets.
To set these names, when clicking on an object in the designer, the property editor on the left provides a field called objectName
Set this to what you want the object to be called and you should see this in the XML when you save the file.
You don't have to look at the XML, seeing/modifying it in the designer is enough.
Using These Names to Find the Widgets
Now that each widget has a name attribute, we can get pointers of these objects. When I say pointers, I mean a variable that we can use to access this widget and modify it.
When uic.loadUi('basic.ui', self)
is called, the names of the widgets will be used to create pointers to the widgets themselves. This means to get our button named "printButton", we can access it using self.printButton
within the same class that uic.loadUi('basic.ui', self)
was called in after it has been called (technically we can call that on whatever self
is).
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('basic.ui', self)
# Set the print button text to "Text Changed"
self.printButton.setText('Text Changed')
# This should not throw an error as `uic.loadUi` would have created `self.printButton`
self.show()
Searching For A Pointer
In the case that the method above doesn't work for some widgets, you can still search for them. To find an object, we can use findChild
on any one of its parent objects while supplying the type of widget we are getting and the name.
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('basic.ui', self)
# Find the button with the name "printButton"
self.button = self.findChild(QtWidgets.QPushButton, 'printButton')
# We have now created `self.printButton` ourselves (will overwrite whatever was there if something existed already)
self.show()
Alternatively, you can leave the name out to find the first object of the type you provided. You can also call
findChildren
to find more than one object.
In the example above, I have searched for a QPushButton
object with the name "printButton". To find the object type you need to search for, each widget in the XML will have a class
attribute beside the name
attribute. If you look at the .ui file I provided and search for "printButton", you will see an attribute class
on the same line with a value QPushButton
; this is how I knew to use QtWidgets.QPushButton
.
<widget class="QWidget" name="centralwidget">
<widget class="QPushButton" name="printButton">
<property name="geometry">
To find the name of this class in the designer, click on a widget and look at the "Object Inspector" window to the right. In the image below you can see that the type of the widget named "input" is QLineEdit
; so I would then use QtWidgets.QLineEdit
.
The method above simply helps you get a pointer to the object. Now that the hardest part is done, you are free to do what you would normally do after you have located all your widgets you want to use.
Please note, this is not a full tutorial on PyQt5. I am simply demonstrating how to import .ui files
Connecting Buttons to Methods
To connect a button to a method, we need to get a pointer (as shown before) and then connect it like we normally would. Here is a full example:
from PyQt5 import QtWidgets, uic
import sys
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('basic.ui', self)
self.button = self.findChild(QtWidgets.QPushButton, 'printButton') # Find the button
self.button.clicked.connect(self.printButtonPressed) # Remember to pass the definition/method, not the return value!
self.show()
def printButtonPressed(self):
# This is executed when the button is pressed
print('printButtonPressed')
app = QtWidgets.QApplication(sys.argv)
window = Ui()
app.exec_()
Reading Inputs
To read inputs, I will use the button method from before to trigger an event and also get another pointer to the input widget.
from PyQt5 import QtWidgets, uic
import sys
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('basic.ui', self)
self.button = self.findChild(QtWidgets.QPushButton, 'printButton') # Find the button
self.button.clicked.connect(self.printButtonPressed) # Remember to pass the definition/method, not the return value!
self.input = self.findChild(QtWidgets.QLineEdit, 'input')
self.show()
def printButtonPressed(self):
# This is executed when the button is pressed
print('Input text:' + self.input.text())
app = QtWidgets.QApplication(sys.argv)
window = Ui()
app.exec_()
These are only the basics but are great to build up from.
This Seems Like A Lot Of Effort?
Yes, at first it will be a lot of effort, but once you have set up all the pointers, you no longer need to worry about the GUI changing (regarding you keep the names of widgets the same). This is a small price to pay in 'overhead' for more smooth development later.