"
This article is part of in the series
Last Updated: Wednesday 29th December 2021

In the last installment, we learned how to create and set up interactive widgets, as well as how to arrange them into simple and complex layouts using two different methods. Today, we're going to discuss the Python/Qt way of allowing your application to respond to user-triggered events: signals and slots.

When a user takes an action — clicking on a button, selecting a value in a combo box, typing in a text box — the widget in question emits a signal. This signal does nothing, by itself; it must be connected to a slot, which is an object that acts as a recipient for a signal and, given one, acts on it.

Connecting Built-In PySide/PyQt Signals

Qt widgets have a number of signals built in. For example, when a QPushButton is clicked, it emits its clicked signal. The clicked signal can be connected to a function that acts as a slot (excerpt only; more code is needed to make it run):



[python]
@Slot()
def clicked_slot():
''' This is called when the button is clicked. '''
print('Ouch!')

# Create the button
btn = QPushButton('Sample')

# Connect its clicked signal to our slot
btn.clicked.connect(clicked_slot)
[/python]



[python]
@pyqtSlot()
def clicked_slot():
''' This is called when the button is clicked. '''
print('Ouch!')

# Create the button
btn = QPushButton('Sample')

# Connect its clicked signal to our slot
btn.clicked.connect(clicked_slot)
[/python]


Note the use of the @Slot() decorator above the definition of clicked_slot; though not strictly necessary, it provides the C++ Qt library hints on how clicked_slot should be called. (For more information on decorators, see the Python Decorators Overview article.) We'll see more information on the @Slot macro later. For now, know that when the button is clicked, it will emit the clicked signal, which will call the function to which it is connected; having a juvenile sense of humor, it will print, 'Ouch!'.

For a less puerile (and actually executable) example, let's look at how a QPushButton emits its three relevant signals, pressed, released, and clicked.



[python]
import sys
from PySide.QtCore import Slot
from PySide.QtGui import *

# ... insert the rest of the imports here
# Imports must precede all others ...

# Create a Qt app and a window
app = QApplication(sys.argv)

win = QWidget()
win.setWindowTitle('Test Window')

# Create a button in the window
btn = QPushButton('Test', win)

@Slot()
def on_click():
''' Tell when the button is clicked. '''
print('clicked')

@Slot()
def on_press():
''' Tell when the button is pressed. '''
print('pressed')

@Slot()
def on_release():
''' Tell when the button is released. '''
print('released')

# Connect the signals to the slots
btn.clicked.connect(on_click)
btn.pressed.connect(on_press)
btn.released.connect(on_release)

# Show the window and run the app
win.show()
app.exec_()
[/python]



[python]
import sys
from PyQt4.QtCore import pyqtSlot
from PyQt4.QtGui import *

# ... insert the rest of the imports here
# Imports must precede all others ...

# Create a Qt app and a window
app = QApplication(sys.argv)

win = QWidget()
win.setWindowTitle('Test Window')

# Create a button in the window
btn = QPushButton('Test', win)

@pyqtSlot()
def on_click():
''' Tell when the button is clicked. '''
print('clicked')

@pyqtSlot()
def on_press():
''' Tell when the button is pressed. '''
print('pressed')

@pyqtSlot()
def on_release():
''' Tell when the button is released. '''
print('released')

# connect the signals to the slots
btn.clicked.connect(on_click)
btn.pressed.connect(on_press)
btn.released.connect(on_release)

# Show the window and run the app
win.show()
app.exec_()
[/python]


When you run the application and click the button, it will print:

[shell]
pressed
released
clicked
[/shell]

The pressed signal is emitted when the button is pressed down, the released signal when it is released, and finally, when both those actions are complete, the clicked signal is fired.

Completing Our Example Application

Now, it's easy to complete our example application from the previous installment. We'll add a slot method to the LayoutExample class that will show the constructed greeting:



[python]
@Slot()
def show_greeting(self):
self.greeting.setText('%s, %s!' %
(self.salutations[self.salutation.currentIndex()],
self.recipient.text()))
[/python]


[python]
@pyqtSlot()
def show_greeting(self):
self.greeting.setText('%s, %s!' %
(self.salutations[self.salutation.currentIndex()],
self.recipient.text()))
[/python]

Note that we use the recipient QLineEdit's text() method to retrieve the text the user entered there, and the salutation QComboBox's currentIndex() method to get the index of the salutation the user selected. We also used the Slot() decorator to indicate that show_greeting will be used as a slot.

Then, we can simply connect the build button's clicked signal to that method:

[python]
self.build_button.clicked.connect(self.show_greeting)
[/python]

Our final example, in total, then looks like this:



[python]
import sys
from PySide.QtCore import Slot
from PySide.QtGui import *

# Every Qt application must have one and only one QApplication object;
# it receives the command line arguments passed to the script, as they
# can be used to customize the application's appearance and behavior
qt_app = QApplication(sys.argv)

class LayoutExample(QWidget):
''' An example of PySide absolute positioning; the main window
inherits from QWidget, a convenient widget for an empty window. '''

def __init__(self):
# Initialize the object as a QWidget and
# set its title and minimum width
QWidget.__init__(self)
self.setWindowTitle('Dynamic Greeter')
self.setMinimumWidth(400)

# Create the QVBoxLayout that lays out the whole form
self.layout = QVBoxLayout()

# Create the form layout that manages the labeled controls
self.form_layout = QFormLayout()

self.salutations = ['Ahoy',
'Good day',
'Hello',
'Heyo',
'Hi',
'Salutations',
'Wassup',
'Yo']

# Create and fill the combo box to choose the salutation
self.salutation = QComboBox(self)
self.salutation.addItems(self.salutations)
# Add it to the form layout with a label
self.form_layout.addRow('&Salutation:', self.salutation)

# Create the entry control to specify a
# recipient and set its placeholder text
self.recipient = QLineEdit(self)
self.recipient.setPlaceholderText("e.g. 'world' or 'Matey'")

# Add it to the form layout with a label
self.form_layout.addRow('&Recipient:', self.recipient)

# Create and add the label to show the greeting text
self.greeting = QLabel('', self)
self.form_layout.addRow('Greeting:', self.greeting)

# Add the form layout to the main VBox layout
self.layout.addLayout(self.form_layout)

# Add stretch to separate the form layout from the button
self.layout.addStretch(1)

# Create a horizontal box layout to hold the button
self.button_box = QHBoxLayout()

# Add stretch to push the button to the far right
self.button_box.addStretch(1)

# Create the build button with its caption
self.build_button = QPushButton('&Build Greeting', self)

# Connect the button's clicked signal to show_greeting
self.build_button.clicked.connect(self.show_greeting)

# Add it to the button box
self.button_box.addWidget(self.build_button)

# Add the button box to the bottom of the main VBox layout
self.layout.addLayout(self.button_box)

# Set the VBox layout as the window's main layout
self.setLayout(self.layout)

@Slot()
def show_greeting(self):
''' Show the constructed greeting. '''
self.greeting.setText('%s, %s!' %
(self.salutations[self.salutation.currentIndex()],
self.recipient.text()))

def run(self):
# Show the form
self.show()
# Run the qt application
qt_app.exec_()

# Create an instance of the application window and run it
app = LayoutExample()
app.run()
[/python]



[python]
import sys
from PyQt4.QtCore import pyqtSlot
from PyQt4.QtGui import *

# Every Qt application must have one and only one QApplication object;
# it receives the command line arguments passed to the script, as they
# can be used to customize the application's appearance and behavior
qt_app = QApplication(sys.argv)

class LayoutExample(QWidget):
''' An example of PySide absolute positioning; the main window
inherits from QWidget, a convenient widget for an empty window. '''

def __init__(self):
# Initialize the object as a QWidget and
# set its title and minimum width.
QWidget.__init__(self)
self.setWindowTitle('Dynamic Greeter')
self.setMinimumWidth(400)

# Create the QVBoxLayout that lays out the whole form
self.layout = QVBoxLayout()

# Create the form layout that manages the labeled controls
self.form_layout = QFormLayout()

self.salutations = ['Ahoy',
'Good day',
'Hello',
'Heyo',
'Hi',
'Salutations',
'Wassup',
'Yo']

# Create and fill the combo box to choose the salutation
self.salutation = QComboBox(self)
self.salutation.addItems(self.salutations)
# Add it to the form layout with a label
self.form_layout.addRow('&Salutation:', self.salutation)

# Create the entry control to specify a
# recipient and set its placeholder text
self.recipient = QLineEdit(self)
self.recipient.setPlaceholderText("e.g. 'world' or 'Matey'")

# Add it to the form layout with a label
self.form_layout.addRow('&Recipient:', self.recipient)

# Create and add the label to show the greeting text
self.greeting = QLabel('', self)
self.form_layout.addRow('Greeting:', self.greeting)

# Cdd the form layout to the main VBox layout
self.layout.addLayout(self.form_layout)

# Add stretch to separate the form layout from the button
self.layout.addStretch(1)

# Create a horizontal box layout to hold the button
self.button_box = QHBoxLayout()

# Add stretch to push the button to the far right
self.button_box.addStretch(1)

# Create the build button with its caption
self.build_button = QPushButton('&Build Greeting', self)

# Connect the button's clicked signal to show_greeting
self.build_button.clicked.connect(self.show_greeting)

# Add it to the button box
self.button_box.addWidget(self.build_button)

# Add the button box to the bottom of the main VBox layout
self.layout.addLayout(self.button_box)

# Set the VBox layout as the window's main layout
self.setLayout(self.layout)

@pyqtSlot()
def show_greeting(self):
''' Show the constructed greeting. '''
self.greeting.setText('%s, %s!' %
(self.salutations[self.salutation.currentIndex()],
self.recipient.text()))

def run(self):
# Show the form
self.show()
# Run the qt application
qt_app.exec_()

# Create an instance of the application window and run it
app = LayoutExample()
app.run()
[/python]


Run it, and you will get the same window as before, except now it actually generates our greeting when the Build button is pressed. (Note that the same methods could be added to our absolute-positioning example from last time with the same effect.)

Now that we have an idea how to connect built-in signals to slots that we create, we are ready for our next installment, in which we will learn how to create our own signals and connect them to slots.

About The Author