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.
- PySide/PyQt Tutorial: Creating Your Own Signals and Slots
- PySide/PyQT Tutorial: QListView and QStandardItemModel
- PySide/PyQt Tutorial: The QListWidget
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):
PySide
@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)
PyQt
@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)
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
.
PySide
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_()
PyQt
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_()
When you run the application and click the button, it will print:
pressed released clicked
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:
PySide
@Slot() def show_greeting(self): self.greeting.setText('%s, %s!' % (self.salutations[self.salutation.currentIndex()], self.recipient.text()))
PyQt
@pyqtSlot() def show_greeting(self): self.greeting.setText('%s, %s!' % (self.salutations[self.salutation.currentIndex()], self.recipient.text()))
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:
self.build_button.clicked.connect(self.show_greeting)
Our final example, in total, then looks like this:
PySide
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()
PyQt
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()
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.