Since GUIs need to perform tasks, the widgets, windows, and other aspects of the application need to be able to react to the events that occur. Whether caused by the user or by the underlying system, the events, and possibly data, need to be delivered to their appropriate locations and handled accordingly.
Find out more about signals, slots, and event handling
Learn how to modify key press and mouse and enter event handlers
Explore how to create custom signals using pyqtSignal
This chapter is all about handling events and modifying the behaviors of the built-in functions in PyQt.
Event Handling in PyQt
Events in Qt are objects created from the QEvent class . The event objects describe different types of interactions that can occur in a GUI as a result of what happens, either caused by a user or by some kind of system activity outside of the application. These events begin once the application’s main event loop starts.
Most events, whether the press of a key, click of a mouse, resizing of a window, or dragging and dropping of a widget or data, have their own subclass of QEvent that generates an event object and passes it on to the appropriate QObject by calling the event() method, which in turn is handled by the suitable event handler. (Recall that QWidget inherits QObject.) The response from the event is used to determine whether it was accepted or disregarded.
More information about event handling can be found at https://doc.qt.io/qt-6/eventsandfilters.html.
Let’s take a look at signals and slots and event handlers in the following subsections and think about their purposes and their differences.
Using Signals and Slots
The concept of signals and slots in PyQt was briefly introduced in Chapter 3. Widgets in PyQt use signals and slots to communicate between objects. Just like events, signals can be generated by a user’s actions or by the internal system. Slots are methods that are executed in response to the signal. For example, when a QPushButton is pressed, it emits a clicked signal. This signal could be connected to a built-in PyQt slot, such as close() to allow a user to quit an application, or to a custom-made slot, which is typically a Python function. Signals are also useful because they can be used to send additional data to a slot and provide more information about an event.
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 event handling 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.
Have a look at www.riverbankcomputing.com/static/Docs/PyQt6/signals_slots.html for more information about signals and slots in PyQt6.
Using Event Handlers to Handle Events
Event handlers are the functions that respond to an event. While a QEvent subclass is created to deliver the event, a corresponding QWidget method will actually handle the event. If you remember in Chapter 3, the closeEvent() event handler was used to close windows. The class that creates the close event object is QCloseEvent.
You may not always be able to handle all of the functionality in an event handler that you modify. When this is the case, you can use an if-else statement. In the if condition, specify how to react to the event, and in the else clause, call the base class’s implementation. So for QCloseEvent, you would include super().closeEvent(event) in the else clause. This portion will take care of any default behaviors you did not implement or may have missed.
Difference Between Signals and Slots and Event Handlers
While there is some overlap between the two, signals and slots are typically used for communication between the different widgets and other PyQt classes. Events are generated by an outside activity and delivered through the event loop by QApplication.
Another important difference is that you are notified when a signal is emitted and take action accordingly. Events need to be handled whenever they occur.
Finally, we can use signals with widgets to improve their capabilities, but you will need to reimplement event handlers when modifying a widget’s functionalities.
In many cases, you will use signals and slots and event handlers together to complete tasks.
The following section shows a simple example of how to reimplement the keyPressEvent() function.
Handling Key Events
keyPressEvent() – Handles a key event when the key is pressed
keyReleaseEvent() – Handles a key event when the key is released
Some key names include Key_Escape, Key_Return, Key_Up, Key_Down, Key_Space, Key_0, Key_1, and so on. A full list of Qt.Key enum keyboard codes can be found at https://doc.qt.io/qt-6/qt.html#Key-enum.
Explanation for Handling Key Events
Code to demonstrate how to modify key event handlers
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. Different keys can be accessed using Qt.Key, and you can use those different keys to perform any number of actions.
Handling Mouse Events
Mouse events are handled by the QMouseEvent class. For mouse events, we need to be able to find out when a mouse button is pressed, released, and double-clicked and when the mouse moves while clicked. There is also an event class, QEnterEvent, that is useful for finding out if the mouse has entered or left the window or a particular widget. Enter events are also useful for collecting information about the mouse cursor’s position.
mousePressEvent() – Handles events when the mouse button is pressed.
mouseReleaseEvent() – Handles events when the mouse button is released.
mouseMoveEvent() – Handles events when the mouse button is pressed and moved. Turn on mouse tracking to enable move events even if a mouse button is not pressed with QWidget.setMouseTracking(True).
mouseDoubleClickEvent() – Handles events when the mouse button is double-clicked.
enterEvent() – Handles when the mouse cursor enters a widget
leaveEvent() – Handles when the mouse cursor leaves a widget
Be sure to download the images folder from the GitHub repository for this example.
Explanation for Handling Mouse Events
Code for setting up the main window in the modifying mouse event handlers example
The addStretch() method is used before and after image_label in main_h_box to make sure the images stay centered in the window.
Code for the enterEvent() and leaveEvent() event handlers
button() – Returns which button caused the event.
buttons() – Returns the state of the buttons, giving access to which combination of buttons caused the event using an OR operator.
globalPosition() – Returns the point coordinates of the event on the computer screen.
position() – Returns the current point coordinates of the mouse relative to the widget that caused the event. The values returned refer to points within the window or widget.
Code that demonstrates how to modify mouse event handlers
After seeing how to modify event handlers, now is a good time to learn how to create your own signals.
Creating Custom Signals
We have taken a look at some of PyQt’s predefined signals and slots in previous chapters. For many of those applications, we have also seen how to create custom slots to handle the signals emitted from widgets. The custom slots were simply Python functions or methods.
Now let’s see how we can create custom signals using pyqtSignal to change a widget’s style sheet. Using pyqtSignal, new signals can be defined for a class. Just like predefined signals, you can also pass types of information, such as Python strings, integers, dictionaries, or lists, as arguments to the pyqtSignal you create.
Explanation for Creating Custom Signals
This example creates a simple GUI with a QLabel object as the central widget of the main window. The pyqtSignal factory and QObject classes are imported from the QtCore module. The QtCore module and QObject class provide the mechanics for signals and slots.
Creating a custom signal to change the background color of a QLabel widget
Code for the setUpMainWindow() method
The rest of setUpMainWindow() instantiates the two QLabel widgets and creates a list of colors that are used by label to specify the background in its style sheet.
This signal will be emitted whenever the user presses either the up arrow key or the down arrow key in keyPressEvent().
Code for handling keyPressEvent() and the slot for changing the background color
It works in a similar fashion when the down key is pressed. Remember that custom signals can take data types as arguments, so don’t worry if you need to pass along information to your other widgets or classes.
Summary
Handling events is a critical component of GUI development. With PyQt, this can be accomplished either through signals and slots or by the event classes and their corresponding event handlers. Either way, you may often find yourself extending the abilities of a widget class by creating custom signals using pyqtSignal or reimplementing the base functionality provided by Qt’s various event handler methods.
We took a look at both these concepts in this chapter, changing the behaviors of key press and mouse event handlers and creating a custom signal to modify the appearance of a label.
In the next chapter, we’ll take a look at using the application Qt Designer to create PyQt applications and simplifying the process for arranging widgets in a GUI window.