GEOG 489
Advanced Python Programming for GIS

4.9 Inheritance in GUI Programming

PrintPrint

Inheritance also plays an important role in GUI programming. For instance, the widget classes of a GUI library are typically organized in a class hierarchy with some basic class like QWidget towards the top and more specialized widgets like buttons and dialog boxes derived from it. Other parts of the GUI library like the event system are also typically organized hierarchically. Have a quick look at this QT class chart and see how, for instance, the QPushButton is a subclass of QWidget with an intermediate class QButton in between from which also other types of buttons like QCheckbox and QRadioButton are derived. This chart is for version 3 of QT; the chart for version 5 has unfortunately somehow disappeared but the relation between these classes is still the same in QT5.

Let’s think back to the GUI programming sections from Lesson 2: there, we often created widgets, stored them in a variable, and then made changes to the widgets like changing their properties and adding child widgets from the main part of the code. For instance, in the miles-to-kilometers conversion tool from Section 2.5.2.3, we created a QWidget for the main window and then changed its properties and added the child widgets for the other GUI elements like this:

rootWindow = QWidget() 
rootWindow.setWindowTitle("Miles to kilometers") 
rootWindow.resize(500, 200) 

gridLayout = QGridLayout(rootWindow) 

labelMiles = QLabel('Distance in miles:') 
gridLayout.addWidget(labelMiles, 0, 0) 

… and so on. We mainly took this approach because at that point we hadn’t covered the fundamentals of object-oriented programming and inheritance yet and our examples were still rather simple. Typically, what one would rather do is use inheritance to create a new widget class derived from an existing widget class. This new class then implements some specialized behavior compared to its base class and encapsulates everything related to this kind of widget in a single class definition. For instance, for the conversion tool, it makes sense to define a new class that is derived from QWidget like this:

from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QGridLayout, QLineEdit, QPushButton 

class ConverterWidget(QWidget): 

	def __init__(self): 
		super(ConverterWidget,self).__init__() 

 		self.setWindowTitle("Miles to kilometers") 
		self.resize(500, 200) 

		self.gridLayout = QGridLayout(self) 

		self.labelMiles = QLabel('Distance in miles:') 
		self.gridLayout.addWidget(self.labelMiles, 0, 0) 

		self.labelKm = QLabel('Distance in kilometers:') 
		self.gridLayout.addWidget(self.labelKm, 2, 0) 

		self.entryMiles = QLineEdit() 
		self.gridLayout.addWidget(self.entryMiles, 0, 1) 

		self.entryKm = QLineEdit() 
		self.gridLayout.addWidget(self.entryKm, 2, 1) 

		self.convertButton = QPushButton('Convert') 
		self.gridLayout.addWidget(self.convertButton, 1, 1) 

		self.convertButton.clicked.connect(self.convert) 

	def convert(self): 
         miles = float(self.entryMiles.text()) 
         self.entryKm.setText(str(miles * 1.60934)) 

app = QApplication([]) 
converter = ConverterWidget() 

converter.show() 
app.exec_() 

In line 3, we say that our new class ConverterWidget should be derived from the PyQT5 class QWidget, meaning it will inherit all instance variables and methods (like setWindowTitle(…) and resize(…)) from QWidget. In the constructor of our class, we first call the constructor of the base class (line 6) and then set up the GUI of our widget similar to how we did this before from the main part of the code. However, now we store the different child widgets in instance variables (e.g., self.gridLayout) and invoke methods as self.setWindowTitle(…), for instance, because these are now inherited methods of this new class. The convert() event handler function has become a method of our new class and we connect it to the “clicked” signal of the button in line 28 using the prefix “self.” because it is a method of the class we are defining here. The main code of the program following the class definition has become very simple now: we just create an instance of our new class ConverterWidget in variable converter in line 35 and then call its show() method (inherited from QWidget) to make the widget show up on the screen.

As a result of defining a new widget class via inheritance, we now have everything related to our conversion widget nicely encapsulated in the class definition, which also helps in keeping the main code of our script as simple and clean as possible. If we need a conversion widget as part of another project, all we would need to move over to this project is the class definition of ConverterWidget. Another advantage that is not immediately obvious in this toy example is the following: think of situations in which you might need several instances of the widget. In the original version you would have to repeat the code for producing the converter widget. Here you can simply create another instance of the ConverterWidget class by repeating the command from line 35 and store the created widget in a different variable.