2.5 GUI options for Python
2.5 GUI options for Python mjg8We mentioned in Section 2.4 that different options may exist for a GUI library in a given programming language. In addition to Python's standard library GUI package called Tkinter, there are 3rd party alternatives such as the PyQt and PySide wrappers for the Qt library, Kivy, Toga, wxPython, and quite a few more. Have a quick look at the overview table provided at this GUI Programming in Python page to get an idea of what’s out there. In contrast to Tkinter, these 3rd party libraries require the installation of additional packages.
There are quite a few other factors that affect the choice for a GUI library for a particular project including:
- For which platforms/operating systems is the library available?
- Does the library draw its own widgets and have its own style or use the operating system's native look & feel?
- How large is the collection of available widgets? Does it provide the more specialized widgets that are needed for the project?
- How easy is the library to use/learn?
- How easy is it to extend the library with our own widgets?
- How active is the development? How good is the available support?
- Is the library completely free to use? What are the license requirements?
We will quickly look at Tkinter and Qt individually, using the same GUI example of a tool to convert miles to kilometers. In the rest of this section, we will focus on Tkinter and Qt with its two Python wrappers PyQt and PySide and focus solely on writing GUI-based Python programs with PyQt.
2.5.1 Tkinter
2.5.1 Tkinter mrs110Tkinter, short for “Tk interface” is the standard GUI for Python, but only in the sense that it is a package in the Python standard library. It is available for all platforms without requiring any additional installation.
It is possible that you have not heard about Tk and Tcl before. Tk is one of the oldest free and open-source, cross-platform GUI toolkits (written in the Tcl scripting language and initially released in 1991) and has been adopted for building GUIs in many programming languages. Tkinter was written by Fredrik Lundh and is essentially a set of wrapper classes and functions that use a Tcl interpreter (embedded into the Python interpreter) to create and manage the Tk GUI widgets.
To get an impression of how Tkinter is used to build a GUI in Python, let us look at the example of creating a simple miles-to-kilometers conversion tool. The simple tool shown below has a single window with five different widgets. There is two label widgets, two widgets for entering or displaying single lines of text, and a button to start the execution. The user can enter a number of miles into the line input field at the top, then press the button to convert the entered number of miles and display the result as kilometers in the line input field at the bottom.

We are using a line input field to display the resulting distance in kilometers just to make things more symmetrical. Since we do not want the user to be able to enter anything into this text field, it has been disabled for input and we could just as easily have used another label widget. The Python code to create this tool with the help of tkinter is shown below and the explanation of the code follows.
from tkinter import Tk, Label, Entry, Button, DISABLED, StringVar
def convert():
"""Takes miles entered, converts them to km, and displays the result"""
miles = float(entryMiles.get())
kilometers.set(str(miles * 1.60934))
# create the GUI
rootWindow = Tk() # create main window
rootWindow.title("Miles to kilometers")
rootWindow.geometry('500x200+0+0')
rootWindow.grid_columnconfigure(1, weight = 1)
labelMiles = Label(rootWindow, text='Distance in miles:') # create label for miles field
labelMiles.grid(row=0, column=0)
labelKm = Label(rootWindow, text='Distance in kilometers:') # create label for km field
labelKm.grid(row=2, column=0)
entryMiles = Entry(rootWindow) # create entry field for miles
entryMiles.grid(row=0, column=1, sticky='w,e')
kilometers = StringVar() # create entry field for displaying km
entryKm = Entry(rootWindow, textvariable = kilometers, state=DISABLED)
entryKm.grid(row=2, column=1, sticky='w,e')
convertButton = Button(rootWindow, text='Convert', command = convert) # create button for running conversion
convertButton.grid(row=1, column=1)
# run the event processing loop
rootWindow.mainloop() Let us ignore the first few lines of Python code for a moment and first look at lines 10 to 29. This is where the GUI of our little program is produced starting with the root window widget in lines 10 to 13. The widget is created by calling the function Tk() defined in tkinter and the created object is stored in variable rootWindow. We then use different methods of the widget to set its title, initial size, and some properties for its grid layout that we are going to use to arrange the child widgets within the content area of the root window.
Next, the label saying “Distance in miles:” is created. The tkinter widget class for labels is called Label and we provide rootWindow as a parameter to Label(…), so that the widget knows what its parent widget is. As mentioned, we will be using a grid layout, namely one with three rows and two columns. We place the created label in the cell in the first row and first column of its parent by calling the grid(…) method with row = 0 and column = 0. We then take the exact same steps to create the other label and place it in the third row of the first column.
In the next steps, the two text input fields are created as widget objects of the tkinter Entry class. An additional parameter sticky=’w,e’ is used for placing these widgets in the grid. This parameter says that the widgets should expand horizontally (west and east) to fill the entire cell. This is required to make the layout fill out the window horizontally and have the text field grow and shrink when the window is resized. Moreover, the Entry widget for displaying the distance in kilometers is set to DISABLED so that the user cannot enter text into it, and it is associated with a variable kilometers of tkinter class StringVar which is needed for us to be able to change the text displayed in the widget from code.
Finally, the button is created as a widget of tkinter class Button. The ‘command’ parameter given to Button(…) in line 28 is saying that if this button is clicked, the function convert() should be executed. This is an example of connecting an event to an event handler function. convert() logic is very simple: With the help of the get() method, we get the current text from the Entry widget for the distance in miles, multiply it with a constant to convert it to kilometers, and then use the set() method of the StringVar object in variable kilometers to change the text displayed in the Entry widget to the distance in kilometers associated with that variable.
In the last line of the code, we call the mainloop() method of our root window to start the infinite event processing loop. The program execution will only return from this call when the user closes the root window, in which case the program execution will be terminated.
This is just a very simple implementation of a miles-to-kilometers conversion tool focusing on the GUI. We haven't implemented any sort of checking whether input values are valid nor any sort of error handling. It is therefore very easy to make the tool crash by entering something that is not a number into the field for distance in miles. If you haven’t already done so, we suggest you create a Python script with the code from above and try out the tool yourself and see how the layout adapts if you resize the window. Experiment with making small changes to the code, like adapting the text shown by the labels or adding another button widget to the currently still empty second row of the first column; then make the button call another event handler function you write to, such as simply print some message to the console.
Don’t worry if some of the details happening here don’t seem entirely clear at the moment. Here we just wanted to give you a general idea of how the different UI elements we discussed in Section 2.4 are created in Tkinter. Let’s move on and talk about Qt as an alternative to Tkinter, and see how the same tool looks like when produced with the PyQt instead of Tkinter.
2.5.2 Qt
2.5.2 Qt mrs110Qt 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. Currently, The Qt Company, a successor of Trolltech, develops and maintains Qt and publishes it 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. Qt6 was released in 2020 and the current version of Qt at the time of this writing is 6.8.
2.5.2.1 PyQt vs. PySide
2.5.2.1 PyQt vs. PySide mrs110You may wonder why there are two different Python wrappers for Qt and how different they are?
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. The short answer lies mainly in license related issues.
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, 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:
2.5.2.2 Installing PyQt6
2.5.2.2 Installing PyQt6 mrs110In contrast to tkinter, PyQt6 is not part of the Python standard library and may or may not be available to install through Pro's Package Manager. We can though, use the Package Manager to clone the default environment and use the Python Command Window to install the packages we need.
Create another clone of the default environment, giving it a name such as arcgispro-py3-pyqt and activate it. You can check the "Installed Packages" list to see if "pyqt" is installed at this time, but more likely it will not be installed. If not, go to Add Packages and type "pyqt". It may not be listed as a package to add in this curated list by esri and Pro's version of conda, so we will need to use the Python Command prompt to install it. There are two quick ways to start a command window in your py3-pyqt conda environment:
In your Windows start menu:
![]()
search for "Python Command Prompt" and it should result in a "Best match". After opening, be sure to verify that it opened the environment you want to work in (details below).

Or, you can navigate to it by clicking the "All" to switch to the application list view.

Scroll down the list and expand the ArcGIS folder to list all ArcGIS applications installed.

Scroll down and open the Python Command Prompt.

This is a shortcut to open a command window in the activated python environment. Once opened, you should see the environment name in parentheses followed by the full path to the python environment.

When you change your activated environment in Pro's Package Manager, this shortcut will also be updated to point to that activated environment.
Type 'pip install PyQt6 PyQt6-WebEngine PyQt6-tools' and hit enter.

Proceed through the prompts and if this does not install, please let your instructor know. When it completes, you probably now have version 6.8.0 or later of pyqt installed. Next, try to run the test code on the next page. Let your instructor know if you are not able to run the application.
2.5.2.3 Miles to kilometers with PyQt
2.5.2.3 Miles to kilometers with PyQt mrs110Here is how the code for our miles-to-kilometers conversion tool looks when using PyQt6 instead of tkinter. You will see that there are some differences, but 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 PyQt6 installed, you can immediately run the code yourself and check out the resulting GUI. The result should look like the figure below.

Source code:
from PyQt6.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.
We are now importing classes from the module PyQt6.QtWidgets and the widgets are named differently (all starting with ‘Q’).
In 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. In contract, 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 PyQt6, 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 parameters.
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 PyQt6. 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() slot (function).
From a coding perspective, the differences between tkinter and PyQt6 are relatively minor. Sometimes one requires slightly more code than the other, but this is largely due to the simplicity of the example used here, which doesn’t involve complex widgets or layouts.
If you tried both versions of our tool or closely compared the screenshots, you may have also noticed some differences in the layout and behavior of the GUIs. While we didn’t optimize the designs to look identical, it’s possible to do so. That said, based on default settings, PyQt6 tends to produce a more visually appealing layout. The main reasons we’ll continue using Qt6/PyQt6 for the rest of this lesson are:
- Qt6 is a modern and widely used cross-platform and cross-language library; knowledge and skills acquired with Qt can be applied in languages other than Python.
- Qt6 is efficient and smooth because of the compiled core library written in C++.
- Qt6 and PyQt6 provide a large collection of available widgets and can be expected to be under active development for the foreseeable future.
- There exists very good tool support for the combination of Qt6 and PyQt6
- Finally and very importantly: In lesson 4 we will continue with the GUI development started in this lesson in the context of QGIS 3.x. QGIS and its interface for plugins have been developed for PyQt6.
As a final note, if you want to run the converter tool code with PySide, it is very easy to do so. You will first have to install the PySide2 package in the Python Command Window and replace the import line with the following line:
from PySide2.QtWidgets import QApplication, QWidget, QLabel, QGridLayout, QLineEdit, QPushButtonYou will first have to install the PySide2 package in the ArcGIS Pro package manager to be able to run the code.