The GUIs you have created up until now have all focused mainly on function and less on appearance and customization. Creating a layout to organize widgets in a coherent manner is just as important as modifying the look and feel of each and every widget. By choosing the right styles, colors, and fonts, a user can also more easily navigate their way around a user interface.
In this chapter, we will be taking a look at why customizing the look of widgets, windows, and actions is also necessary for designing great GUIs.
In the final section of the chapter, we will take another look at event handling in PyQt and see how we can modify signals and slots to create custom signals to further improve the potential of applications.
Modify the appearance of widgets with Qt Style Sheets
Utilize new Qt widgets and classes, including QRadioButton, QGroupBox, and QTabWidget
Reimplement event handlers
Create custom signals using pyqtSignal and QObject
Changing GUI Appearances with Qt Style Sheets
When you use PyQt, the appearance of your applications is handled by Qt’s QStyle class. QStyle contains a number of subclasses that imitate the look of the system on which the application is being run. This makes your GUI look like a native MacOS, Linux, or Windows application. Custom styles can be made either by modifying existing QStyle classes, creating new classes, or using Qt Style Sheets.
This chapter will take a look at how to create custom styles by using style sheets. Qt Style Sheets provide a technique for customizing the look of widgets. The syntax used in Qt Style Sheets is inspired by HTML Cascading Style Sheets (CSS).
With style sheets, you can customize the look of a number of different widget properties, pseudostates, and subcontrols. Some of the properties that you can modify include background color, font size and font color, border type, width or style, as well as add padding to widgets. Pseudostates are used to define special states of a widget, such as when a mouse hovers over a widget or when a widget changes states from active to disabled. Subcontrols allow you to access a widget’s subelements and change their appearance, location, or other properties. For example, you could change the indicator of a QCheckButton to a different color when it is checked or unchecked.
Customizations can either be applied to individual widgets or to the QApplication object by using setStyleSheet().
Customizing Individual Widget Properties
Customizing the QApplication Style Sheet
The preceding code also demonstrates how to create a style_sheet variable that contains the different properties for each widget. To add a different type of class, simply include the widget type such as QCheckBox followed by the attributes to be changed.
Project 6.1 – Food Ordering GUI
Food delivery service apps are everywhere – on your phone, on the Internet, and even on kiosks when you go to actual restaurants themselves. They simplify the ordering process while also giving the user a feeling of control over their choices, asking us to select our own foods and items as we scroll through a list of organized categories.
These types of GUIs may possibly need to contain hundreds of different items that fit into multiple groups. Rather than just throwing all of the products into the interface and letting the user waste their own time sorting through the items, goods are usually placed into categories often differentiated by tabs. These tabs contain titles for the products that can be found on those corresponding pages, such as Frozen Foods or Fruits/Vegetables.
Design the Food Ordering GUI
This application consists of two main tabs (displayed in Figure 6-3), but more could be easily added. Each tab consists of a QWidget that acts as a container for all of the other widgets. The first tab, Pizza, contains an image and text to convey the purpose of the tab to the user. This is followed by two QGroupBox widgets that each consist of a number of QRadioButton widgets. While the radio buttons in the “Crust” group box are mutually exclusive, the ones in the “Toppings” group box are not, so that the user can select multiple options at one time.
The second tab, Wings, is set up in a similar fashion with the “Flavor” radio buttons being mutually exclusive.
Before we look at the code for the food ordering GUI, let’s take a moment to learn about the new Qt classes in this project – QGroupBox, QRadioButton, and QTabWidget.
The QRadioButton Widget
The QRadioButton class allows you to create option buttons that can be switched on when checked or off when unchecked. Each radio button consists of a round button and a corresponding label or icon. Radio buttons are generally used for situations where you need to provide a user with multiple choices, but only one choice can be checked at a time. As the user selects a new radio button, the other radio buttons are unchecked.
When you place multiple radio buttons in a parent widget, those buttons become auto-exclusive, meaning they automatically become exclusive members of that group. If one radio button is checked inside of the parent, all of the other buttons will become unchecked. To change this functionality, you can set the setAutoExclusive() attribute to False.
Also, if you want to place multiple exclusive groups of radio buttons into the same parent widget, then use the QButtonGroup class to keep the different groups separate. Refer back to Chapter 4 for information about QButtonGroup.
Radio buttons are similar to the QCheckBox class when emitting signals. A radio button emits the toggled() signal when checked on or off and can be connected to this signal to trigger an action.
An example of creating QRadioButton widgets can be seen in Listing 6-1.
The QGroupBox Class
The QGroupBox widget provides a container for grouping other widgets with similar purposes together. A group box has a border with a title on the top. The title can also be checkable so that the child widgets inside the group box can be enabled or disabled when the checkbox is checked or unchecked.
For an example of another type of container in PyQt, check out the QFrame class in Chapter 7.
The QTabWidget Class
Sometimes you may need to organize related information onto separate pages rather than create a cluttered GUI. The QTabWidget class provides a tab bar (created from the QTabBar class) with an area under each tab (referred to as a page) to present information and widgets related to each tab. Only one page is displayed at a time, and the user can view a different page by clicking the tab or by using a shortcut (if one is set for the tab).
There are a few different ways to interact with and keep track of the different tabs. For example, if the user switches to a different tab, the currentChanged() signal is emitted. You can also keep track of a current page’s index with currentIndex(), or the widget of the current page with currentWidget(). A tab can also be enabled or disabled with the setTabEnabled() method.
If you want to create an interface with multiple pages, but without the tab bar, then you should consider using a QStackedWidget. However, if you do use QStackedWidget, then you will need to provide some other means to switch between the windows, such as a QComboBox or a QListWidget, since there are no tabs.
Example that shows how to use QTabWidget, QRadioButton, and QGroupBox classes
Explanation
Let’s take a look at how to set up the tab widget and its child widgets in this example.
We begin by importing the necessary classes, including QRadioButton, QTabWidget, and QGroupBox from the QtWidgets module. Next, we set up the ContactForm class and initialize the window’s size and title.
The next step is to set up the tab widget and each page in the setupTabs() method . The process to use QTabWidget is to first create an instance of the tab widget. Here we create tab_bar. Then, create a QWidget object for each page in the tab bar. There are two pages for this project, profile_details_tab and background_tab.
Insert the two pages into the tab widget using addTab(). Give each tab an appropriate label.
Finally, create the child widgets for each page and use layouts to arrange them. Two separate methods are created, profileDetailsTab() and backgroundTab(), to organize the two different pages. The labels and line edit widgets are set up like normal. For the QRadioButton objects, they are added to group boxes on their respective pages. In the backgroundTab() method , a for loop is used to instantiate each radio button and add it to the page’s layout.
Food Ordering GUI Solution
Code for food ordering GUI
When finished, your GUI should look similar to the one in Figure 6-2.
Explanation
Let’s first import the modules we need for this project. Next, the properties for the widgets in this application are prepared in the style_sheet variable. We will get to how this works shortly.
Create the structure for the tabs and layout for the main window in setupTabsAndLayout(). Set up instances of the QTabWidget and QWidget objects that will be used for the pages of the tabs. The two tabs are the pizza_tab, to display choices for building your own pizza, and the wings_tab, to show choices for wings flavors.
The side_widget is used to give feedback to users of their choices and can be seen even if the user switches tabs. All of the child widgets for side_widget are then arranged in a nested layout and added to the main QHBoxLayout.
The pizzaTab() method creates and arranges the child widgets for the first tab, pizza_tab. The top of the first page gives users information about the purpose of the tab using images and text. The wingsTab() method is set up in a similar manner.
If users press the add_to_order_button on either page, the text from the selected radio buttons is displayed in the side_widget. A Python try-except clause is used to ensure that the user has selected radio buttons.
Applying the Style Sheet
In the beginning of the program, you will notice the style_sheet variable that holds all of the different style specifications for the different widgets.
The final GUI with customized colors, borders, and fonts can be seen in Figure 6-2.
Event Handling in PyQt
The concept of signals and slots in PyQt was briefly introduced in Chapter 3. Event handling in PyQt uses signals and slots to communicate between objects. Signals are typically generated by a user’s actions, and slots are methods that are executed in response to the signal. For example, when a QPushButton is pushed, it emits a clicked() signal. This signal could be connected to the PyQt slot close() so that a user can quit the application when the button is pressed.
The clicked() signal is but one of many predefined Qt signals. The type of signals that can be emitted differs according to the widget class. PyQt delivers events to widgets by calling specific, predefined handler functions. These can range from functions related to window operations, such as show() or close(), to GUI appearances with setStyleSheet(), to mouse press and release events, and more.
The way in which event handlers deal with events can also be reimplemented. You saw an example of this back in Chapter 3 when the closeEvent() function was modified to display dialog boxes before closing the application.
Code to demonstrate how to modify event handlers
Explanation
Whenever a user presses a key on the keyboard, it sends a signal to the computer. If you want to give certain keys abilities, then you will need to use the keyPressEvent().
The keyPressEvent() function checks for events, which in this case are the signals being sent from keys. If the key pressed is the Escape key, then the application calls the close() function to quit the application.
Of course, you can check for any type of key with the keyPressEvent() and cause it to perform any number of actions.
Creating Custom Signals
We have taken a look at some of PyQt’s predefined signals and slots. For many of the projects in previous chapters, we have also created custom slots to handle the signals emitted from widgets.
Creating a custom signal to change the background color of a QLabel widget
This example creates a simple GUI with a QLabel widget as the central widget of the main window.
Explanation
The pyqtSignal factory and QObject classes are imported from the QtCore module. The QtCore module and QObject classes provide the mechanics for signals and slots.
This signal is connected to the changeBackground() slot which updates the color of the label by calling the setStyleSheet() method.
It works in a similar fashion when the down key is pressed.
Summary
In this chapter, we saw how to use Qt Style Sheets to modify the appearance of widgets to better fit the purpose and look of an application. Applying customizations in a consistent and attentive manner can greatly influence the usability of a user interface.
Allowing the user to also have some control over the look of the window can improve the user’s experience. This can be done in a number of ways – through the menu or toolbar, using a context menu, or even through simple presses of keys on the keyboard.
Chapter 7 will introduce Qt Designer, a tool that will make the process for designing GUIs much simpler.