5. Buttons and Option Widgets

Non-interactive display widgets like QLabel can show text or images but cannot accept any user input. Buttons, on the other hand, are input widgets that can accept input from the user, and that input is one of two types:

  • None (sometimes called Unit) - the button signals that it was clicked with no additional data attached.
  • Boolean - the button caries a checked or unchecked state (True or False, on or off).

Not all buttons are checkable by default. QPushButton, for instance, behaves as a None-type button unless you explicitly call setCheckable(True). QCheckBox is always boolean, and additionally supports an optional tri-state mode with a third partially-checked state.

Qt provides several button classes, all inheriting from QAbstractButton:

Qt Button Class Purpose
QPushButton Lets the user send a command to the application. Supports text, icons, and optional toggling.
QCheckBox Used for non-mutually exclusive options (e.g., preferences). Supports binary or tri-state.
QRadioButton Mutually exclusive choices within a group. Automatically deselects other radio buttons in the group.
QToolButton For use in toolbars (e.g., “Save” icon). Supports popups for sub-menus.
QCommandLinkButton Mimics Windows Vista command link style.

5.1 QPushButton - Triggering an Action

QPushButton lets the user trigger an action in a Qt application.

An icon of a clipboard-list1

You are building a report generation panel for a finance application. Your task is to add a button that, when clicked, stamps a label with the current date and time to record when the report was last generated.

To use a QPushButton in your application:

 1 import sys
 2 from datetime import datetime
 3 from PySide6.QtCore import Qt
 4 from PySide6.QtCore import Slot
 5 from PySide6.QtWidgets import (QApplication,
 6     QPushButton, QLabel, QWidget, QVBoxLayout)
 7 
 8 
 9 class Window(QWidget):
10     
11     def __init__(self):
12 
13         super().__init__()
14         self.setWindowTitle('Report Timestamp')
15         self.resize(300, 120)
16         
17         layout = QVBoxLayout()
18         self.setLayout(layout)
19         
20         # 2. Create the button 
21         #    and add it to the window layout.
22         
23         button = QPushButton('Show timestamp')
24         
25         # 3. Connect the clicked event with the slot.
26         
27         button.clicked.connect(self.set_timestamp)
28         
29         self.label = QLabel()
30         self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
31         
32         layout.addWidget(button)
33         layout.addWidget(self.label)
34     
35     # 1. Create the slot.
36     
37     @Slot()
38     def set_timestamp(self):
39         now = datetime.now()
40         self.label.setText(now.strftime('%Y-%m-%d %H:%M:%S'))
41 
42 
43 if __name__ == '__main__':
44 
45     app = QApplication(sys.argv)
46     main_window = Window()
47     main_window.show()
48     sys.exit(app.exec())
  1. Create the the slot set_timestamp() - this method that will be called when the button is clicked. It reads the current date and time and updates the label’s text. The label is stored as self.label so it is accessible from outside __init__().

  2. Create a QPushButton and set the text to be shown on it.

  3. Connect the button’s clicked() signal to the slot.

After running the application and clicking the button you should see a window like this:

5.2 Checkable QPushButton - Persistent On/Off State

A QPushButton becomes a toggle button when you call setCheckable(True) on it. Unlike a regular push button, a checkable button retains its state after being clicked: once you press it, it stays pressed until you press it again.

An icon of a clipboard-list1

You are building an account view for a finance application. Your task is to add a button that switches a label between displaying a transaction amount in dollars and the equivalent in euros.

To use a checkable QPushButton in your application:

 1 import sys
 2 from PySide6.QtCore import Qt
 3 from PySide6.QtCore import Slot
 4 from PySide6.QtWidgets import (QApplication,
 5     QPushButton, QLabel, QWidget, QVBoxLayout)
 6 
 7 
 8 class Window(QWidget):
 9     
10     def __init__(self):
11 
12         super().__init__()
13         self.setWindowTitle('Currency Toggle')
14         self.resize(300, 120)
15         
16         self.amount = 5000
17         self.exchange_rate = 0.86
18         
19         layout = QVBoxLayout()
20         self.setLayout(layout)
21         
22         # 2. Create the button and make it checkable. 
23         
24         self.button = QPushButton('Convert to Euros')
25         self.button.setCheckable(True)
26         
27         # 3. Connect the toggled signal with the slot.
28         
29         self.button.toggled.connect(self.display_transaction)
30         
31         self.label = QLabel()
32         self.label.setAlignment(Qt.AlignmentFlag.AlignRight)
33 
34         layout.addWidget(self.button)
35         layout.addWidget(self.label)
36         
37         # Init the label
38         
39         self.display_transaction(self.button.isChecked())
40     
41     # 1. Create the slot.
42     
43     @Slot(bool)
44     def display_transaction(self, checked):
45         if checked:
46             amount = self.amount * self.exchange_rate
47             self.label.setText(f'€{amount:,.2f}')
48             self.button.setText('Convert to Dollars')
49         else:
50             self.label.setText(f'${self.amount:,.2f}')
51             self.button.setText('Convert to Euros')
52 
53 
54 if __name__ == '__main__':
55 
56     app = QApplication(sys.argv)
57     main_window = Window()
58     main_window.show()
59     sys.exit(app.exec())
  1. Create the slot display_transaction(). It accepts a checked argument and updates both the label text and the button text to reflect the current currency. The button is stored as self.button so that the slot can update its text alongside the label.

  2. Create the button and make it checkable with setCheckable(True).

  3. Connect the toggled signal to the slot, then calldisplay_transaction() directly with the button’s initial state to populate the label before the user interacts with the UI.

5.3 QCheckBox - Independent Options

A QCheckBox consists of a graphical check box and a label. it can be checked, unchecked, or (if you set its tristate property to True) partially checked. Checkboxes are used to represent options that are not mutually exclusive - more than one checkbox in a group can be checked at the same time.

An icon of a clipboard-list1

You are building a transaction filter panel for a finance application. Your task is to add checkboxes that let the user control which transaction types (Income, Expenses, Transfers) are visible, updating a label to reflect the current selection.

To use QCheckBoxes in your application:

 1 import sys
 2 from PySide6.QtCore import Slot
 3 from PySide6.QtWidgets import (QApplication,
 4     QWidget, QVBoxLayout, QCheckBox, QLabel)
 5 
 6 
 7 class Window(QWidget):
 8     
 9     def __init__(self):
10 
11         super().__init__()
12         self.setWindowTitle('Transaction Filter')
13         self.resize(300, 120)
14 
15         layout = QVBoxLayout()
16         self.setLayout(layout)
17         
18         # 2. Create the checkboxes and add them to the layout.
19         
20         self.income_checkbox = QCheckBox('Income')
21         self.expenses_checkbox = QCheckBox('Expenses')
22         self.transfers_checkbox = QCheckBox('Transfers')
23 
24         layout.addWidget(self.income_checkbox)
25         layout.addWidget(self.expenses_checkbox)
26         layout.addWidget(self.transfers_checkbox)
27         
28         self.label = QLabel()
29         layout.addWidget(self.label)
30         
31         # 3. Connect each checkbox's
32         #    checkStateChanged to the slot.
33 
34         self.income_checkbox.checkStateChanged.connect(
35             self.update_filter)
36         self.expenses_checkbox.checkStateChanged.connect(
37             self.update_filter)
38         self.transfers_checkbox.checkStateChanged.connect(
39             self.update_filter)
40         
41         self.update_filter()
42     
43     # 1. Create the slot.
44 
45     @Slot()
46     def update_filter(self):
47 
48         active = []
49         if self.income_checkbox.isChecked():
50             active.append('Income')
51         if self.expenses_checkbox.isChecked():
52             active.append('Expenses')
53         if self.transfers_checkbox.isChecked():
54             active.append('Transfers')
55         
56         self.label.setText(
57             ', '.join(active) if active else 'No filter active.')
58 
59 
60 if __name__ == '__main__':
61 
62     app = QApplication(sys.argv)
63     main_window = Window()
64     main_window.show()
65     sys.exit(app.exec())
  1. Create the slot update_filter(). It checks the state of each checkbox and builds the label text from whichever are currently checked. It is also called once at the end of __init__() to initialize the label before the user interacts with anything.

  2. Create the checkboxes and add them to the layoit.

  3. Connect each checkbox’s checkStateChanged signal to update_filter(). The signal is emitted whenever a checkbox is checked or unchecked.

5.4 QRadioButton - Mutually Exclusive Choices

QRadioButton is a widget that presents the user with a set of mutually exclusive options. When you check a radio button all other radio buttons with the same parent widget are automatically unchecked.

An icon of a clipboard-list1

You are building a reporting panel for a finance application. Your task is to add radio buttons that let the user select a reporting period (‘This Week’, ‘This Month’, or ‘This Year’) and update a label to show the active selection.

To use QRadioButtons in your application:

 1 import sys
 2 from PySide6.QtWidgets import (QApplication, 
 3     QWidget, QVBoxLayout, QRadioButton, QLabel)
 4 
 5 
 6 class Window(QWidget):
 7     
 8     def __init__(self):
 9 
10         super().__init__()
11         self.setWindowTitle('Reporting Period')
12         self.resize(300, 120)
13         
14         layout = QVBoxLayout()
15         self.setLayout(layout)
16         layout.addWidget(QLabel('Select a reporting period:'))
17         
18         # 2. Create the radio buttons 
19         #    and add them to the layout.
20         
21         week_radio = QRadioButton('This Week')
22         month_radio = QRadioButton('This Month')
23         year_radio = QRadioButton('This Year')
24         
25         layout.addWidget(week_radio)
26         layout.addWidget(month_radio)
27         layout.addWidget(year_radio)
28         
29         self.label = QLabel()
30         layout.addWidget(self.label)
31 
32         # 3. Connect the radio buttons' 
33         #    toggled signals to slots.
34         
35         week_radio.toggled.connect(
36             lambda checked: self.set_reporting_period(
37                 checked, 'this week'))
38         month_radio.toggled.connect(
39             lambda checked: self.set_reporting_period(
40                 checked, 'this month'))
41         year_radio.toggled.connect(
42             lambda checked: self.set_reporting_period(
43                 checked, 'this year'))
44         
45         week_radio.setChecked(True)
46     
47     # 1. Create a method to set the reporting period.
48     
49     def set_reporting_period(self, checked, period):
50         if checked:
51             self.label.setText(f'Selected period: {period}')
52 
53     
54 if __name__ == '__main__':
55 
56     app = QApplication(sys.argv)
57     main_window = Window()
58     main_window.show()
59     sys.exit(app.exec())
  1. Create the set_reporting_period() method. It accepts two arguments: checked, the new checked state of the radio button, and period, a string containing the selected reporting period. If checked is True the label is updated.

  2. Create the radio buttons and add them to the layout.

  3. Connect each radio button’s toggled signal to a lambda that passes the checked state and the period string to set_reporting_period(). The toggled signal is emitted twice on every click (once for the button being checked and once for the button being unchecked), so the if checked guard in the slot ensures the label is only updated for the active selection.

Radio buttons automatically form a an exclusive group based on their common parent widget. If you need more control (for example, multiple independent exclusive groups within the same parent, or exclusive behavior across different parents) you can use QButtonGroup[^3], introduced in the next section.

5.5 QButtonGroup - Managing Groups Explicitly

QButtonGroup provides a non-visual container for organizing checkable buttons and managing their states. A button group is exclusive by default: when a button is checked, all other buttons in the groups are unchecked automatically. To simplify the representation of enum values in a user interface, each button can be mapped to an integer ID.

QButtonGroup does not check any button by default. It is your responsability to set an intial checked state, otherwise the group starts with no button selected.

QButtonGroup provides a set of signals that let you respond to interactions with its member buttons:

Signal Emitted
buttonClicked When a button is clicked
buttonPressed When a button is pressed down
buttonReleased When a button is released
buttonToggled When a button’s checked state changes
idClicked When a button is clicked, emitting its ID
idPressed When a button is pressed down, emitting its ID
idReleased When a button is released, emitting its ID
idToggled When a button’s checked state changes, emitting its ID
An icon of a clipboard-list1

You are building a toolbar for sorting the transaction list in your finance application. Your task is to add two groups of sort buttons, one for sort key (Date, Recipient) and one for the sort order (Ascending, Descending), where selecting a button in each group deselects the other, with labels showing the current sort settings.

To use a QButtonGroup in your application:

 1 import sys
 2 from PySide6.QtCore import Slot
 3 from PySide6.QtWidgets import (QApplication, QButtonGroup,
 4     QPushButton, QLabel, QWidget, QVBoxLayout)
 5 
 6 
 7 class Window(QWidget):
 8 
 9     def __init__(self):
10 
11         super().__init__()
12         self.setWindowTitle('Transaction List Toolbar')
13         self.setFixedWidth(240)
14 
15         layout = QVBoxLayout()
16         self.setLayout(layout)
17         
18         # 2. Create the buttons and make them checkable.
19 
20         date_button = QPushButton('Date')
21         date_button.setCheckable(True)
22         recipient_button = QPushButton('Recipient')
23         recipient_button.setCheckable(True)
24 
25         ascending_button  = QPushButton('Ascending')
26         ascending_button.setCheckable(True)
27         descending_button = QPushButton('Descending')
28         descending_button.setCheckable(True)
29         
30         # 3. Create the button groups
31         #    and add the buttons to them.
32         
33         self.sort_key_group = QButtonGroup()
34         self.sort_key_group.addButton(date_button, 1)
35         self.sort_key_group.addButton(recipient_button, 2)
36         
37         self.sort_order_group = QButtonGroup()
38         self.sort_order_group.addButton(ascending_button, 1)
39         self.sort_order_group.addButton(descending_button, 2)
40         
41         # 4. Connect each group's idToggled signal to its slot.
42         
43         self.sort_key_group.idToggled.connect(self.set_sort_key)
44         self.sort_order_group.idToggled.connect(self.set_sort_order)
45 
46         self.sort_key_label = QLabel()
47         self.sort_order_label = QLabel()
48 
49         layout.addWidget(date_button)
50         layout.addWidget(recipient_button)
51         layout.addSpacing(20)
52         layout.addWidget(ascending_button)
53         layout.addWidget(descending_button)
54         layout.addSpacing(20)
55         layout.addWidget(self.sort_key_label)
56         layout.addWidget(self.sort_order_label)
57         
58         date_button.setChecked(True)
59         ascending_button.setChecked(True)
60         self.set_sort_key(1, True)
61         self.set_sort_order(1, True)
62     
63     # 1. Create the slots.
64     
65     @Slot(int, bool)
66     def set_sort_key(self, button_id, checked):
67         if checked:
68             if button_id == 1:
69                 self.sort_key_label.setText('Sort key: Date')
70             else:
71                 self.sort_key_label.setText('Sort key: Recipient')
72 
73     @Slot(int, bool)
74     def set_sort_order(self, button_id, checked):
75         if checked:
76             if button_id == 1:
77                 self.sort_order_label.setText('Sort order: Ascending')
78             else:
79                 self.sort_order_label.setText('Sort order: Descending')
80 
81 
82 if __name__ == '__main__':
83 
84     app = QApplication(sys.argv)
85     main_window = Window()
86     main_window.show()
87     sys.exit(app.exec())
  1. Create the slots set_sort_key() and set_sort_order(). Each slot accepts a button_id and a checked argument - the ID and new checked state of the toggled button. If checked is True, the corresponding label is updated to reflect the current sort setting.

  2. Create the buttons and make them checkable with setCheckable(True).

  3. Create two button groups and add the buttons with addButton(), passing a numeric ID alongside each button. The ID is used in the slots to identify which button was toggled without needing a reference to the button itself.

  4. Connect each group’s idToggled signal to its slot. idToggled is emitted with the button’s ID and its new checked state whenever a button in the group is toggled.

In the previous section we saw that adding radio buttons to the same parent widget them mutually exclusive. This is in some ways similiar to grouping buttons with a QButtonGroup, so let’s compare the two approaches:

Layout QButtonGroup
Purpose Arrange widgets visually Manage exclusive selection
Visual Yes No
Automatic exclusivity For QRadioButtons within the same parent For any checkable button
Signals None idToggled, buttonToggled, idClicked, buttonClicked
Widget can belong to two No - silently reparented No - undefined behavior
Can span multiple parents No Yes

It is not the layout that makes the radio buttons exclusive - it is a combination of two properties: autoExclusive, which is True for radio buttons by default, and checkable, which allows a button to retain its checked or unchecked state. Adding radio buttons to the same layout gives them the same parent widget, which is enough to trigger the auto-exclusive berhavior: only one can be checked at a time.

QButtonGroup makes the enum mapping explicit - each button is assigned a numeric ID, and the group’s signals emit that ID when the selection changes. As the Qt documentation notes, the purpose of this mechanism is to simplify the representation of enum values in a user interface.

This brings us to the full data type progression for this chapter:

Widget Data type
QPushButton (non-checkable) None
QPushButton (checkable), QCheckBox bool
Group of QCheckBoxes, non-exclusive QButtonGroup QFlags / bitmask
Group of QRadioButtons, exclusive QButtonGroup enum

The underlying principle is that the data type is determined by the exclusivity rule, not the widget type. A group of checkboxes and a non-exclusive QButtonGroup both produce flags. A group of radio buttons and an exclusive QButtonGroup both produce an enum value. The choice between them depends on what type of data your UI is collecting.