Throughout this book, we have aimed to take a practical approach for creating applications in order to help you learn the fundamentals of GUI development. As you continue to use PyQt6 and Python, you will find yourself learning about other modules and classes that will also prove useful.
In some cases, this book has only scratched the surface of what you can do with PyQt. With so many modules, classes, and possibilities for customization provided by Qt, the potential for building GUIs is endless. To expand your experience with PyQt, this chapter takes a look at some additional Qt classes that we couldn’t fit into earlier chapters.
- 1.
Displaying directories and files using QFileSystemModel and QTreeView
- 2.
Making a GUI that takes photos using QCamera and demonstrates how to make custom dialogs using QDialog
- 3.
Creating a simple clock GUI with QDate and QTime
- 4.
Exploring the QCalendarWidget class
- 5.
Building Hangman with QPainter and other PyQt classes
- 6.
Building the framework for a web browser using the QtWebEngineWidgets module
- 7.
Creating tri-state QComboBox widgets
The explanations for each project will not go into great lengths of detail. Rather, they will focus on explaining the key points of each program and leave it up to you to research the details that you are unsure about, either by finding the answers in a different chapter or by searching online for help.
Project 16.1 – Directory Viewer GUI
For every operating system, there needs to be some method for a user to access the data and files located within it. The drives, directories, and files are stored in a hierarchical file system and presented to the user so that they only view the files that they are interested in seeing.
Whether you use a command line interface or a graphical user interface, there needs to be some way to create, remove, and rename files and directories. However, if you are already interacting with one interface, it may be more convenient to locate files or directories that you need in the application’s main window rather than opening new windows or other programs.
Explanation for the Directory Viewer GUI
Code for the directory viewer GUI
The QFileSystemModel class provides the model we need to access data on the local file system. For PyQt6, this class is now located in QtGui. While not included in this project, you could also use QFileSystemModel to rename or remove files and directories, create new directories, or use it with other display widgets as part of a browser.
The QTreeView class will be used to display the contents of the model in a hierarchical tree view.
Create an instance of the QFileSystemModel class, model, and set the directory to the root path by passing an empty string to setRootPath(). You can set a different directory by passing a different path to setRootPath().
Finally, let’s set the model for the tree object to show the contents of the file system using setModel(). To choose a different directory, the user can select Open Directory… from the menu, and a file dialog will appear. A new directory can then be selected in the chooseDirectory() slot and set as the new root path to be displayed in the tree object using the QTreeView method setRootIndex().
If a new directory has been selected, you can use the slot, returnToRootDirectory(), that is triggered by root_act to redisplay the root directory.
Project 16.2 – Camera GUI
Use pip instead of pip3 on Windows. You can use the Python help() function to get a list of all of the PyQt6 modules. Look through them and you should see QtMultimedia listed among the different modules.
For those readers using macOS, you may have issues running this application if you are using the Z shell, also known as zsh. The bash shell used to be the default on macOS until recently. If you run into problems due to zsh, you can switch to using bash by entering chsh -s /bin/bash/ in the command line. If you want to switch back to zsh when you are finished, you will need to enter the command chsh -s /bin/zsh. Just be aware that when you do switch between shells, you will also need to install PyQt6 from PyPI or edit the paths in bash to locate PyQt6 and your other Python packages.
Explanation for the Camera GUI
Let’s take a look at how to use the multimedia classes to create a GUI for taking photos in Listing 16-2. To begin, we’ll use basic_window.py from Chapter 1.
To build a custom dialog that will display the images taken using the webcam, we’ll need QDialog and QDialogButtonBox from QtWidgets. The widget QDialogButtonBox is used to easily create and arrange standard buttons in dialog boxes. Have a look in the Appendix at the “QDialog” subsection for more information about button types.
There have been numerous updates when it comes to the multimedia classes in PyQt6. The QtMultimedia module provides access to a number of multimedia tools that can handle audio, videos, and cameras. The QCamera class provides the interface to work with camera devices. We can use QImageCapture to record or take pictures of media objects, such as QCamera. QMediaDevices supplies information about available cameras or audio devices.
Example code that shows how to use the QCamera class and build custom dialogs
In order to create a customized dialog, you’ll need to create a new class that inherits QDialog. An instance of ImageDialog will display the image taken from the camera on a QLabel. The argument id refers to the id value returned by QImageCapture when a picture is taken. Since QImageCapture also returns a QImage object, we’ll need to change image to a pixmap using the QPixmap method fromImage() before setting the picture on the label.
The button_box instance is a QDialogButtonBox that contains Save and Cancel keys. The two buttons are added to button_box and separated by a pipe key, |. When a button is clicked, it emits a signal. Generally, those signals are accepted or rejected when working with dialog buttons. We’ll attach those signals to built-in slots, accept() and reject(). While these are standard slots, you could also connect the accepted or rejected signals to custom slots and perform other operations. We’ll do just that for accept() but use the default functionality for reject().
For this example, we’ll reimplement accept() to save the pixmap to a folder called images. The file extension, .png, still needs to be included to avoid issues with saving images (especially on Windows).
The last step is to arrange the widgets in a layout just like you normally do with other windows.
Moving on to setUpMainWindow() in the MainWindow class, let’s first create the images directory for saving images if it does not already exist. The window consists of a label for providing instructions and a QVideoWidget object for showing the camera’s contents.
QMediaDevices can be used to specify a camera to use or provide a list of possible devices to the user. For cameras, we’ll need to use QCameraDevice to detect available cameras. The QMediaDevices method defaultVideoInput() locates a computer’s default QCameraDevice. You can then pass that camera device to QCamera when creating the camera instance.
Using QImageCapture, the user is able to take pictures. The imageCaptured signal is used to detect when a picture is taken. Image capturing for this GUI is handled by keyPressEvent(). When the space bar is pressed, QImageCapture.capture() takes a picture, thereby emitting the imageCaptured signal. This calls ViewImage(), where the picture’s id and QImage object, preview, are passed to an ImageDialog instance. The open() method is used to open the dialog. If the user clicks the Save button, the image is converted to a pixmap and saved in the images folder (handled in the accept() slot of ImageDialog).
Back in setUpMainWindow(), QMediaCaptureSession will manage the capturing of the camera that is displayed in video_widget.
Project 16.3 – Simple Clock GUI
PyQt6 also provides classes for dealing with dates, QDate, or time, QTime. The QDateTime class supplies functions for working with both dates and time. All three of these classes include methods for handling time-related features.
In PyQt6, you can also use the enum Qt.DateFormat to utilize standard date and time format types. These include ISO 8601 format (using the flag ISODate) and RFC 2822 (using the flag RFC2822Date). The toString() method returns the date and time as a string. QDateTime also handles daylight saving time, different time zones, and the manipulation of times and dates such as adding or subtracting months, days, or hours.
If you only need to work with the individual dates and times, QDate and QTime also provide similar functions as you shall see in the following example.
Explanation for the Clock GUI
We’ll use the basic_window.py from Chapter 1 as the base for this program. Start by importing the necessary modules, including QDate, QTime, and QTimer from the QtCore module in Listing 16-3.
Code for the clock GUI
In order to get the current date and time, the values are retrieved using the currentDate() and currentTime() methods in the getDateTime() method. These are then returned and set as the current_date and current_time.
The values for date and time are both set using a sequence of characters to create a format string. For date, we’ll present the full month’s name (MMMM), the day (dd), and the full year (yyyy). The time instance will display hours (hh), minutes (mm), seconds (ss), and AM or PM (AP).
The labels that will display the date and time are then instantiated, styled, and added to the layout in setUpMainWindow(). The values of the labels are updated using the updateDateTime() slot that is connected to timer.
Project 16.4 – Calendar GUI
The QCalendarWidget class provides a calendar that already has a number of other useful widgets and functions built-in. For example, the calendar already includes a horizontal header that includes widgets for changing the month and the year and a vertical header that displays the week number. The class also includes signals that are emitted whenever the dates, months, and years on the calendar are changed. The look of your calendar will vary depending upon the platform that you are using to run the application.
The QDateEdit widget is used in this application to restrict the date range a user can select, specified by minimum and maximum values.
Explanation for the Calendar GUI
The calendar GUI code
Next, we set a few of the calendar object’s parameters. Setting setGridVisible() to True will make the grid lines visible. In order to specify the date range that a user can select in the calendar, we set the minimum and maximum date values using the QCalendar methods setMinimumDate() and setMaximumDate().
Whenever a date is selected in the calendar widget, it emits a selectionChanged signal. This signal is connected to the newDateSelection() slot that updates the date on current_label and in current_date_edit. Selecting a value in current_date_edit widget will also change the other values.
The QCalendarWidget class also has a number of functions that allow you to configure its behaviors and appearance. For this project, we create three QDateEdit widgets that will allow the user to change the minimum and maximum values for the date range, as well as the current date selected in the calendar. These widgets can be seen on the right side of the GUI in Figure 16-6.
When a date is changed in a date edit widget, it generates a dateChanged signal. Each one of the QDateEdit widgets is connected to a corresponding slot that will update the calendar’s minimum, maximum, or current date values depending upon which date edit widget is changed. The method for changing the dates is adapted from the Qt document website.1
Finally, the label and date edit widgets are arranged in a QGroupBox, added to a QGridLayout instance, and nested into the main window’s layout in setUpMainWindow().
Project 16.5 – Hangman GUI
For this application, the player can select from one of the 26 English letters to guess a letter in an unknown word. As each letter is chosen, they will become disabled in the window. If the letter is correct, it will be revealed to the player. Otherwise, a part of the Hangman figure’s body is drawn on the screen. If all of the letters are correctly guessed, then the player wins. There are a total of six turns. Whether or not the player wins or loses, a dialog will be displayed to inform the player and allow them to quit or to continue playing.
Be sure to download words.txt from the files folder in the GitHub repository before beginning this project.
Explanation for the Hangman GUI
Code for the Hangman GUI
This program contains two classes: DrawingLabel and Hangman.
Creating the Drawing Class
The DrawingLabel class inherits from QLabel and handles the different paint events that will be drawn on the label object in the main window. The paintEvent() function is called in a class that inherits from QLabel so that way the paint events occur on the label and are not covered up by the main window.
The paintEvent() function sets up QPainter and handles the two painting methods: drawHangmanBackground(), which draws the gallows of the Hangman game onto the label, and drawHangmanBody(), which only draws the body parts if they are contained in the part_list.
Creating the Main Window Class
The Hangman class starts by initializing the GUI window and calling the newGame() method. First, the Hangman board is created as an instance of the DrawingLabel class. Then, setUpBoard() selects a random word from the words.txt file. The labels that will represent the letters of the chosen word are replaced with underscore characters, appended to the labels list, and added to the horizontal layout of the word_frame object.
Finally, we need to set up the keyboard push buttons, layouts, and the game logic in setUpBoard(). Three rows of push buttons that represent the letters of the alphabet are controlled by one QButtonGroup object, keyboard_bg.
When one button is pushed, it generates a signal that calls the buttonPushed() slot. When a push button is pressed, it is disabled by passing False to setEnabled().
The list of body parts, body_parts_list, contains the six body part names. If the player guesses an incorrect letter, the name is appended to the wrong_parts_list and checked for in the DrawingLabel method drawHangmanBody() function. Using this method ensures that all necessary parts are drawn with their different styles when paintEvent() is called. Otherwise, the labels are updated to display the correct letters in the appropriate positions if the player guesses correctly.
If the player wins or loses, a QMessageBox will appear and allow the user to close the application or continue. If Yes is selected, newGame() is called.
Project 16.6 – Web Browser GUI
A web browser is a graphical user interface that allows access to information on the World Wide Web. A user can enter a Uniform Resource Locator (URL) into an address bar and request content for a website from a web server to be displayed on their local device, including text, image, and video data. URLs are generally prefixed with http, a protocol used for fetching and transmitting requested web pages, or https, for encrypted communication between browsers and websites.
Qt provides quite a few classes for network communication, WebSockets, support for accessing the World Wide Web, and more. This project introduces PyQt’s classes for adding web integration into GUIs.
For the following project, we will take a look at Qt’s WebEngine core classes, specifically the QtWebEngineWidgets module for creating widget-based web applications. The WebEngine core classes provide a web browser engine that can be used to embed web content into your applications. The QtWebEngineCore module uses Chromium as its back end. Chromium is an open source software from Google that can be used to create web browsers.
Ability to open multiple windows and tabs, either by using the application’s menu or shortcut hotkeys
A navigation bar that is made up of back, forward, refresh, stop and home buttons, and the address bar for entering URLs
The web engine view widget created using QWebEngineView
A status bar
A progress bar that relays feedback to the user about loading web pages
You will need to install the QtWebEngineWidgets module. To do so, enter the following command into the command line: pip3 install PyQt6-WebEngine (use pip for Windows).
In addition, make sure that you download the icons folder from this chapter’s GitHub repository.
Explanation for Web Browser GUI
Code for the web browser GUI
Before calling initializeUI(), we need to instantiate a few lists that will contain the new windows, web pages viewed, and URLs for each tab. This project also calls setWindowIcon() to include an application icon, but it will not be displayed on macOS due to system guidelines.
There are several methods that are called in initializeUI(). The first one is sizeMainWindow(), which demonstrates how to use QApplication to access information about the computer’s screen size. The second, createToolbar(), sets up the toolbar for navigating web pages. Methods createActions() and createMenu() set up the main menu. The menu includes actions and shortcuts for creating new windows and new tabs and closing the application. The application’s status bar is created in setUpMainWindow(), along with the QTabWidget for managing the open web pages.
The current index of the tab we are viewing is stored in tab_index. The back() method is then called on the web_view object for that current tab. If the tab_index is not 0, then the user can navigate back through previously viewed web pages. The back() method is but one of several functions included in the QWebEngineView class. Other methods for navigation include forward(), reload(), and stop(), and these are also utilized for the other tool_bar buttons.
When the user enters a web address in the QLineEdit widget and presses the return key, we check to see if the URL begins with the correct scheme (such as http, https, or file) in searchForUrl(). If a valid scheme is not present, http is appended to the beginning of the URL. If the URL conforms to standard encoding rules, a request is then sent to load() the website.
Creating Tabs for the Web Browser
The setUpMainWindow() is used to handle creating the tab widget and the web view objects. First, we need to create the QTabWidget that will display each individual tab’s web view. Refer back to Chapter 6 for more details on setting up tab widgets.
A few of the tab _bar widget’s parameters are changed so that each tab includes a close button, and if only one tab remains, then the tab bar will not be displayed. This helps to make sure that there is always at least one tab in the main window. If a tab is closed, the closeTab() slot is called. The corresponding URLs and web view items for that tab are also removed from the list_of_urls and list_of_web_pages lists.
The first tab, main_tab, is created, added to the tab_bar, and then passed to the setUpTab() method. The tab_bar widget is set as the central widget for the main window. To set up a tab to display a web page, we first need to create a web view object.
Creating the Web View
Once the web page has loaded, the urlChanged signal connected to updateUrl() changes the URL displayed in address_line. We can use the loadFinished() signal to tell the current tab to update its title using the updateTabTitle() slot and return the web_view widget.
Next, create the layout to hold the web view widget, append the current tab’s URL and web_page object to the list_of_urls and list_of_web_pages lists, and set the layout for the current tab’s page. The web_page object is the web_view widget that is returned from setUpWebView() and displayed in the page in setUpTab().
Finally, to handle when a user switches between tabs, QTabWidget has the currentChanged signal. If a different tab is selected, the connected slot, updateUrl(), will change the displayed URL in address_line.
Adding a QProgressBar to the Status Bar
In setUpWebView(), a progress bar and label are also created that will be used to display the loading progress of a web page in the browser’s status bar. When the loadProgress signal is generated, the updateProgressBar() slot is called.
When a page is finished loading, we call removeWidget() to remove the progress bar and the label. An example of the progress bar can be seen at the bottom of Figure 16-8.
Creating a web browser is a very extensive task. There are many topics that are not included in this project, such as accessing HTTP cookies with Qt WebEngine Core, working with the browser history with QWebEngineHistory, managing connections and client certificates, proxy support with QNetworkProxy, working with JavaScript, downloading content from websites, and others. You are definitely encouraged to research these topics if you need to use Qt WebEngine for more advanced projects.
Project 16.7 – Tri-state QComboBox
If all of the child elements are selected, the parent check box is checked. If some of the children are selected, then the parent is partially checked.
Explanation for the Tri-state QComboBox
Code for the tri-state QComboBox
If tristate_cb is checked, we’ll use the value of state that is passed with the stateChanged signal to check all of the check boxes in checkButtonState(). Otherwise, the widgets are all unchecked.
Next, we’ll create the rest of the window, instantiate the children QCheckBox objects, and arrange them in a QButtonGroup. The QButtonGroup signal buttonToggled is emitted whenever any of the widgets are checked or unchecked. If the state of one of the check boxes in the button group changes, the slot checkButtonState() is used to find out which buttons are checked or unchecked. We can access all of the buttons in QButtonGroup using the buttons() method.
Those values are then added to the button_states list. It is here that we take care of updating the parameters of tristate_cb. If all values are True, setCheckState() is used to ensure that tristate_cb only has two states. If all of the buttons are False, then tristate_cb is unchecked. Finally, if there is a mix of True and False values in button_states, tristate_cb is set to tri-state mode using setCheckState() and the PartiallyChecked flag.
Summary
In this chapter, you saw different GUI applications that build the structure for larger projects, such as the camera GUI or the web browser GUI. Other projects introduced components that you may be able to include in other programs, such as the directory viewer GUI, the clock GUI, and the calendar GUI. In the case of the Hangman GUI, we demonstrated how an understanding of QPainter is useful for drawing and customizing the look of widgets. Finally, tri-state QComboBox widgets are useful for managing child elements.
We have explored a variety of topics for designing graphical user interfaces using PyQt6 and Python throughout this book – different types of widgets, classes, and layouts. We saw how to stylize your interfaces, how to add menus, and how to make an application simpler with Qt Designer. Advanced topics such as working with the clipboard, SQL, and multithreaded applications were also covered.
The Appendix will fill in more details about some of the PyQt6 classes used in this book as well as a few other classes that were not included in previous chapters.
Your feedback and questions are always welcome. Thank you so much for joining me on this journey and allowing me to share my knowledge about GUI development with you.