We already mentioned a few things about QT in this lesson. It is a widely used cross-platform library written in C++, modern and under very active development. In addition to the GUI functionality, the library provides support for internationalization, Unicode, database and network access, XML and JSON code processing, thread management, and more. That’s why it is also called an application framework, not just a GUI library. QT was originally developed by the company Trolltech and its initial release was in 1995. KDE, one of the early GUIs for the Linux operating system, was based on QT and that triggered a lot of discussion and changes to the license and organization QT was published under. These days, the company developing QT is called The QT Company, a successor of Trolltech, and QT is published in four different editions, including the Community edition that is available under different open source licenses GPL 3.0, LGPL 3.0, and LPGL 2.1 with a special QT exception. QT is very commonly used for both open source and commercial software, and if you have worked with QT in one programming language, it is typically relatively easy to learn to use it in a different language. QT5 was released in 2012 and the current version of QT at the time of this writing is 5.10.
You may wonder why there exist two different Python wrappers for QT and how different they are? The short answer is that the reason lies mainly in license related issues and that PyQt and PySide are actually very similar, so similar that the code below for a QT based version of the miles-to-kilometers converter works with both PyQt and PySide. For PySide you only have to replace the import line at the beginning.
PyQt is significantly older than PySide and, partially due to that, has a larger community and is usually ahead when it comes to adopting new developments. It is mainly developed by Riverbank Computing Limited and distributed under GPL v3 and a commercial license. Releases follow a regular schedule and the software is generally considered very robust, mature, and well supported.
PySide is developed by Nokia and had its initial release in 2009, in a time when Nokia was the owner of QT. As can be read on the PySide web page [1], PySide has been developed and published in response to a lack of a QT wrapper for Python that has a suitable license for FOSS and proprietary software development. Without going too much into the details of the different license models involved, if you want to develop a commercial application, PyQt requires you to pay fees for a commercial license, while the LGPL license of PySide permits application in commercial projects.
From an educational perspective, it doesn’t really matter whether you use PySide or PyQt. As we already indicated, the programming interfaces have over the recent years converged to be very similar, at least for the basic GUI based applications we are going to develop in this course. However, we have some specific reasons to continue with PyQt that will be listed at the end of the next section. If you are interested to learn more about the differences between PyQt and PySide and when to pick which of the two options, the following blog post could serve as a starting point:
Since in contrast to tkinter, PyQt5 is not part of the Python standard library, we may need to install the PyQt5 package before we can use it from our code. We are currently using the Python installation that comes with ArcGIS Pro. Therefore, we will use the conda installation manager from within ArcGIS Pro to check whether PyQt5 is installed and if not, install it with all the packages it depends on. This will also automatically install the binary QT5 library that the PyQt5 package is a wrapper for.
Go ahead and open the package manager in Pro (Project -> Python) and check the Installed Packages list to see if "pyqt" is installed. If not, go to Add Packages and install "pyqt"; the process is identical to our installation of spyder back in Lesson 1 [3].
You probably will now have version 5.9.2 or later of pyqt installed. Next, try to run the test code on the next page. If this code gives you an error of...
This application failed to start because it could not find or load the Qt platform plugin "windows".
...then you will need to come back to this page and set the QT_QPA_PLATFORM_PLUGIN_PATH environmental variable to the path of the plugin folder of PyQt5 (as explained in the blog post Developing Python GUI in ArcGIS Pro with PyQt [4]). This can be done with the Windows tool for setting environmental variables by following the instructions below:
Here is how the code for our miles-to-kilometers conversion tool looks when using PyQt5 instead of tkinter. You will see that there are some differences but a lot also looks very similar. We kept the names of the variables the same even though the widgets are named a little differently now. Since you now have PyQt5 installed, you can immediately run the code yourself and check out the resulting GUI. The result should look like the figure below.
Important note: When you run PyQt5 code in Spyder directly (here or in later sections), you may run into the situation that the program won't run anymore when you start it a second time and instead you get the error message "Kernel died, restarting" in the Spyder Python window. This can be resolved by going into the Spyder Preferences and under "Run" select the option "Remove all variables before execution" to make sure that everything from the previous run is completely cleaned up before the script code is executed again.
Source code:
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QGridLayout, QLineEdit, QPushButton def convert(): """Takes miles entered, converts them to km, and displays the result""" miles = float(entryMiles.text()) entryKm.setText(str(miles * 1.60934)) app = QApplication([]) rootWindow = QWidget() rootWindow.setWindowTitle("Miles to kilometers") rootWindow.resize(500, 200) gridLayout = QGridLayout(rootWindow) labelMiles = QLabel('Distance in miles:') gridLayout.addWidget(labelMiles, 0, 0) labelKm = QLabel('Distance in kilometers:') gridLayout.addWidget(labelKm, 2, 0) entryMiles = QLineEdit() gridLayout.addWidget(entryMiles, 0, 1) entryKm = QLineEdit() gridLayout.addWidget(entryKm, 2, 1) convertButton = QPushButton('Convert') gridLayout.addWidget(convertButton, 1, 1) convertButton.clicked.connect(convert) rootWindow.show() app.exec_()
Let’s look at the main differences between this code and the tkinter based code from Section 2.5.1 [5].
Obviously, we are now importing classes from the module PyQt5.QtWidgets and the widgets are named differently (all starting with ‘Q’).
While with tkinter, we only created one object for the application and root window together and then called its mainloop() method to start the execution of the event processing loop, the application and its main window are two different things in QT. In line 8, we create the application object and then at the very end we call its exec_() method to start the event processing loop. The window is created separately in line 10, and before we call exec_(), we invoke its show() method to make sure it is visible on the screen.
The creation of the widgets looks very similar in both versions. However, with tkinter, we didn’t have to create a grid layout explicitly; it was already available after the main window had been created. With PyQt5, we create the grid layout for the root window explicitly in line 14. To add widgets to the grid layout, we call the addWidget(…) method of the layout providing numbers for the row and column as paramters.
In the tkinter version, we had to set up a special variable to change the content of the entryKm line input field. This is not required with PyQt5. We can simply change the text displayed by the corresponding QLineEdit widget by calling its setText(…) method from the convert() function in line 6.
Finally, connecting the “clicked” event of the button with our convert() event handler function happens as a separate command in line 31 rather than via a parameter when creating the button object. By writing "convertButton.clicked.connect(convert)" we are saying, in QT terminology, that the “clicked” signal of convertButton should be connected to our convert() function.
It seems fair to say that from the perspective of the code, the differences between tkinter and PyQt5 are rather minor with, in some cases, one of them needing a bit more code, and in other cases, the other. However, this is partially due to this example being very simple and not involving more advanced and complex widgets and layouts.
When you tried out both versions of our little tool or just closely compared the two figures above with screenshots of the produced GUIs, you may also have noticed that, in addition to the differences in the code, there are some differences in the produced layout and behavior. We didn’t make use of all available options to make the two versions appear very similarly and it is certainly possible to do so, but our personal impression is that just based on the default look and behavior, the layout produced by PyQt5 is a bit more visually appealing. However, the main reason why we are going to continue with QT5/PyQt5 for the remainder of this lesson are the following:
As a final note, if you want to run the converter tool code with PySide, you have to replace the import line with the following line:
from PySide2.QtWidgets import QApplication, QWidget, QLabel, QGridLayout, QLineEdit, QPushButton
Of course, you will first have to install the PySide2 package in the ArcGIS Pro package manager to be able to run the code.
Links
[1] https://pypi.org/project/PySide/
[2] https://coderslegacy.com/pyside-vs-pyqt-difference/
[3] https://www.e-education.psu.edu/geog489/l1_p5.html
[4] https://tereshenkov.wordpress.com/2017/11/26/developing-python-gui-in-arcgis-pro-with-pyqt/
[5] https://www.e-education.psu.edu/geog489/node/2223