PyQt is a comprehensive set of Python bindings for the Qt framework. However, PyQt 5 is not backward compatible with PyQt 4. It is noteworthy that PyQt 5 does not support any part of the Qt API that are marked as deprecated or obsolete in Qt v5.0. However, it is possible that some of these are included accidentally. If included, they are considered bugs and will be removed when found.
If you are familiar with Qt 4 or have read the first edition of this book, one thing to note is that the signals and slots of are no longer supported. Therefore, the following are not implemented in PyQt 5:
- QtScript
- QObject.connect()
- QObject.emit()
- SIGNAL()
- SLOT()
Also, there is a modification in disconnect() as it no longer takes arguments and will disconnect all connections to the QObject instance when invoked.
However, new modules have been introduced, such as the following:
- QtBluetooth
- QtPositioning
- Enginio
Let's start with a very simple example—calling a window. Again for best performance, copy the code, paste it in a file, and run the script on the Terminal. Our codes are optimized for running on a Terminal only:
#sys.argv is essential for the instantiation of QApplication! import sys #Here we import the PyQt 5 Widgets from PyQt5.QtWidgets import QApplication, QWidget #Creating a QApplication object app = QApplication(sys.argv) #QWidget is the base class of all user interface objects in PyQt5 w = QWidget() #Setting the width and height of the window w.resize(250, 150) #Move the widget to a position on the screen at x=500, y=500 coordinates w.move(500, 500) #Setting the title of the window w.setWindowTitle('Simple') #Display the window w.show() #app.exec_() is the mainloop of the application #the sys.exit() is a method to ensure a real exit upon receiving the signal of exit from the app sys.exit(app.exec_())
The syntax is very similar to what you see in Chapter 5, Embedding Matplotlib in GTK+3. Once you know how to use one particular GUI library fairly well (such as GTK+3), it is very easy to adapt to a new one readily. The code is very similar to that in GTK+3 and the logic follows as well. QApplication manages the GUI application's control flow and main settings. It's the place where the main event loop is executed, processed and dispatched. It is also responsible for application initialization and finalization and handling most of the system-wide and application-wide settings. Since QApplication handles the entire initialization phase, it must be created before any other objects related to the UI are created.
The qApp.exec_() command enters the Qt main event loop. Once exit() or quit() is called, it returns the relevant return code. Until the main loop is started, nothing is displayed on the screen. It's necessary to call this function as the main loop handles all events and signals coming from both the application widgets and the window system; essentially, no user interaction can take place before it is called.
Readers may wonder why there is an underscore in exec_();. The reason is simple: exec() is a reserved word in Python hence the addition of the underscore to the exec() Qt method. Wrapping it inside sys.exit() allows the Python script to exit with the same return code, informing the environment how the application ended (whether successfully or not).
For more experienced readers, you will find something abnormal in the preceding code. While we were instantiating the QApplication class, we were required to parse sys.argv (an empty list in this case) to the constructor of QApplication. At least I found it unexpected when I first used PyQt, but this is required as the instantiation invokes the constructor of the C++ class QApplication, and it uses sys.argv to initialize the Qt application. Parsing sys.argv during QApplication instantiation is a convention in Qt and it is something be aware of. Also every PyQt 5 application must create an application object.
Again, let's try to another one in OOP style:
#Described in earlier examples import sys from PyQt5.QtWidgets import QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QApplication #Here we create a class with the "base" class from QWidget #We are inheriting the functions of the QWidget from this case class Qtwindowexample(QWidget): #Constructor, will be executed upon instantiation of the object def __init__(self): #Upon self instantiation, we are calling constructor of the QWidget #to set up the bases of the QWidget's object QWidget.__init__(self) #Resizing, moving and setting the window self.resize(250, 150) self.move(300, 300) self.setWindowTitle('2 Click buttons!') #Here we create the first button - print1button #When clicked, it will invoke the printOnce function, and print "Hello world" in the terminal self.print1button = QPushButton('Print once!', self) self.print1button.clicked.connect(self.printOnce) #Here we create the second button - print5button #When clicked, it will invoke the printFive function, and print "**Hello world" 5 times in the terminal self.print5button = QPushButton('Print five times!', self) self.print5button.clicked.connect(self.printFive) #Something very familiar! #It is the vertical box in Qt5 self.vbox=QVBoxLayout() #Simply add the two buttons to the vertical box self.vbox.addWidget(self.print1button) self.vbox.addWidget(self.print5button) #Here put the vertical box into the window self.setLayout(self.vbox) #And now we are all set, show the window! self.show() #Function that will print Hello world once when invoked def printOnce(self): print("Hello World!") #Function that will print **Hello world five times when invoked def printFive(self): for i in range(0,5): print("**Hello World!") #Creating the app object, essential for all Qt usage app = QApplication(sys.argv) #Create Qtwindowexample(), construct the window and show! ex = Qtwindowexample() #app.exec_() is the mainloop of the application #the sys.exit() is a method to ensure a real exit upon receiving the signal of exit from the app sys.exit(app.exec_())
The preceding code creates two buttons, and each button will invoke an individual function—print Hello world once or print Hello World five times in the Terminal. Readers should be able to grasp the event handling system from the code easily.
Here is the output:
This is another implementation of the two buttons example from Chapter 5, Embedding Matplotlib in GTK+3, and the goal of this example is to demonstrate the signal handling approach in PyQt 5 in comparison with GTK+3. Readers should find this fairly similar as we intentionally write it in a way more similar to the example in GTK+3.
Let's try to embed a Matplotlib figure in a Qt window. Be aware that unlike the example in the previous chapter, this figure will be refreshed every second! Therefore, we also use the QtCore.QTimer() function in here and invoke the update_figure() function as an event-action pair:
#Importing essential libraries import sys, os, random, matplotlib, matplotlib.cm as cm from numpy import arange, sin, pi, random, linspace #Python Qt5 bindings for GUI objects from PyQt5 import QtCore, QtWidgets # import the Qt5Agg FigureCanvas object, that binds Figure to # Qt5Agg backend. from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure #The class DynamicCanvas contains all the functions required to draw and update the figure #It contains a canvas that updates itself every second with newly randomized vecotrs class DynamicCanvas(FigureCanvas): #Invoke upon instantiation, here are the arguments parsing along def __init__(self, parent=None, width=5, height=4, dpi=100): #Creating a figure with the requested width, height and dpi fig = Figure(figsize=(width,height), dpi=dpi) #The axes element, here we indicate we are creating 1x1 grid and putting the subplot in the only cell #Also we are creating a polar plot, therefore we set projection as 'polar self.axes = fig.add_subplot(111, projection='polar') #Here we invoke the function "compute_initial_figure" to create the first figure self.compute_initial_figure() #Creating a FigureCanvas object and putting the figure into it FigureCanvas.__init__(self, fig) #Setting this figurecanvas parent as None self.setParent(parent) #Here we are using the Qtimer function #As you can imagine, it functions as a timer and will emit a signal every N milliseconds #N is defined by the function QTimer.start(N), in this case - 1000 milliseconds = 1 second #For every second, this function will emit a signal and invoke the update_figure() function defined below timer = QtCore.QTimer(self) timer.timeout.connect(self.update_figure) timer.start(1000) #For drawing the first figure def compute_initial_figure(self): #Here, we borrow one example shown in the matplotlib gtk3 cookbook #and show a beautiful bar plot on a circular coordinate system self.theta = linspace(0.0, 2 * pi, 30, endpoint=False) self.radii = 10 * random.rand(30) self.plot_width = pi / 4 * random.rand(30) self.bars = self.axes.bar(self.theta, self.radii, width=self.plot_width, bottom=0.0) #Here defines the color of the bar, as well as setting it to be transparent for r, bar in zip(self.radii, self.bars): bar.set_facecolor(cm.jet(r / 10.)) bar.set_alpha(0.5) #Here we generate the figure self.axes.plot() #This function will be invoke every second by the timeout signal from QTimer def update_figure(self): #Clear figure and get ready for the new plot self.axes.cla() #Identical to the code above self.theta = linspace(0.0, 2 * pi, 30, endpoint=False) self.radii = 10 * random.rand(30) self.plot_width = pi / 4 * random.rand(30) self.bars = self.axes.bar(self.theta, self.radii, width=self.plot_width, bottom=0.0) #Here defines the color of the bar, as well as setting it to be transparent for r, bar in zip(self.radii, self.bars): bar.set_facecolor(cm.jet(r / 10.)) bar.set_alpha(0.5) #Here we generate the figure self.axes.plot() self.draw() #This class will serve as our main application Window #QMainWindow class provides a framework for us to put window and canvas class ApplicationWindow(QtWidgets.QMainWindow): #Instantiation, initializing and setting up the framework for the canvas def __init__(self): #Initializing of Qt MainWindow widget QtWidgets.QMainWindow.__init__(self) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) #Instantiating QWidgets object self.main_widget = QtWidgets.QWidget(self) #Creating a vertical box! vbox = QtWidgets.QVBoxLayout(self.main_widget) #Creating the dynamic canvas and this canvas will update itself! dc = DynamicCanvas(self.main_widget, width=5, height=4, dpi=100) #adding canvas to the vertical box vbox.addWidget(dc) #This is not necessary, but it is a good practice to setFocus on your main widget self.main_widget.setFocus() #This line indicates that main_widget is the main part of the application self.setCentralWidget(self.main_widget) #Creating the GUI application qApp = QtWidgets.QApplication(sys.argv) #Instantiating the ApplicationWindow widget aw = ApplicationWindow() #Set the title aw.setWindowTitle("Dynamic Qt5 visualization") #Show the widget aw.show() #Start the Qt main loop , and sys.exit() ensure clean exit when closing the window sys.exit(qApp.exec_())
Again, the figure in this example will randomize the data and update the figure every 1 second through QTimer, shown as follows: