Кнопки

Все кнопки наследуют от абстрактного базового класса QAbstractButton. Встроенные классы кнопок:

Свойство Checkable

Кнопки могут быть переключаемыми (checkable) и нет. Поменять это можно методом setCheckable. Если кнопка переключаемая, то она может находиться в двух состояниях: включенном (checked) и выключенном, при этом каждое последующее нажатие меняет статус кнопки. Статус кнопки всегда можно узнать методом isChecked.

Проще всего объяснить разницу между переключаемыми и не переключаемыми кнопками на конкретных примерах QPushButton и QCheckBox (флажок).

../_images/checkable_1.png

Кнопка QPushButton по умолчания не является checkable, после нажатия на неё, кнопка возвращается в исходное состояние. Кнопка QCheckbox по умолчанию checkable. Первое нажатие на кнопку приведёт к появлению галочки внутри неё. Повторное нажатие приведет к исчезновению это галочки. Ниже приводится код, воссоздающий окно из примера выше.

import sys

from PySide6.QtWidgets import *


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        widget = QWidget()
        layout = QHBoxLayout()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        # buttons
        check_box = QCheckBox("Check box")
        push_button = QPushButton("Push button")
        layout.addWidget(check_box)
        layout.addWidget(push_button)


app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()

Тем не менее QPushButton тоже можно сделать checkable.

Сигналы кнопок

  • clicked — нажатие на кнопку;

  • pressed — зажатие кнопки;

  • released — отпуск кнопки;

  • toggled — изменения состояние checkable кнопки.

QPushButton

Кнопка QPushButton уже встречалась и подробно здесь обсуждаться не будет.

  • основной сигнал — clicked;

  • методом setIcon можно установить иконку кнопки, методом setIconSize можно поменять её размер.

  • методом setCheckable можно сделать её переключаемой;

  • методом setFlat можно указать, показывать ли границу кнопки или нет;

Код под спойлером ниже демонстрирует поведение этих методов на примере так называемой toggle кнопки.

../_images/toggle_buttons.png
Код.
import sys
import os

from PySide6.QtCore import QSize
from PySide6.QtGui import *
from PySide6.QtWidgets import *


class ToggleButton(QPushButton):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setCheckable(True)
        self.setChecked(True)
        self.setFlat(True)
        self.clicked.connect(self.updateIcon)
        self.icon_on = QIcon(os.path.join("..", "icons", "on-button.png"))
        self.icon_off = QIcon(os.path.join("..", "icons", "off-button.png"))
        self.updateIcon()

    def updateIcon(self):
        if self.isChecked():
            self.setIcon(self.icon_on)
        else:
            self.setIcon(self.icon_off)
        self.setIconSize(QSize(40, 30))


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        widget = QWidget()
        main_layout = QVBoxLayout()
        widget.setLayout(main_layout)
        self.setCentralWidget(widget)

        # top row
        self.button = QPushButton("Button")
        self.label = QLabel("Label")
        self.label.setAlignment(Qt.AlignCenter)
        self.edit = QLineEdit()

        # bottom row
        self.enable_button = ToggleButton("Enable")
        self.show_button = ToggleButton("Show")

        # layout
        top_layout = QHBoxLayout()
        top_layout.addWidget(self.button)
        top_layout.addWidget(self.label)
        top_layout.addWidget(self.edit)
        main_layout.addLayout(top_layout)

        bottom_layout = QHBoxLayout()
        bottom_layout.addWidget(self.enable_button)
        bottom_layout.addWidget(self.show_button)
        main_layout.addLayout(bottom_layout)

        # connections
        self.enable_button.clicked.connect(self.on_button_enable_click)
        self.show_button.clicked.connect(self.on_button_show_click)

    def on_button_show_click(self):
        for widget in [self.button, self.label, self.edit]:
            widget.setVisible(self.show_button.isChecked())

    def on_button_enable_click(self):
        for widget in [self.button, self.label, self.edit]:
            widget.setEnabled(self.enable_button.isChecked())

app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()

QCheckBox

Флажки QCheckBox обычно представляют опции программы, которые могут быть включены и выключены без влияния на другие опции. Все QCheckBox изначально являются переключаемыми (checkable), а значит хранят состояние (поставлен флажок или нет), которое всегда можно узнать методом isChecked.

По умолчанию QCheckBox может находиться в двух состояния: флажок поставлен (checked) и флажок не поставлен (unchecked). Методом setTristate можно сделать QCheckBox с третьим возможным состоянием “флажок поставлен частично” (partially checked). В таком случае состояние флажка запрашивать надо методом checkState, который возвращает enum Qt.CheckState с тремя возможными значениями: Qt.Unchecked, Qt.PartiallyChecked и Qt.Checked.

Как и у всех кнопок у QCheckBox есть сигнал clicked, но лучше использовать специальный сигнал stateChanged, который излучается каждый раз, когда меняется состояние (ставится флажок, или наоборот).

../_images/checkboxes.png

Код под спойлером ниже демонстрирует все перечисленные особенности QCheckBox.

Код.
import sys

from PySide6.QtGui import *
from PySide6.QtWidgets import *

bistate_to_text = {
    False: "Unchecked",
    True: "Checked"
}

tristate_to_text = {
    Qt.CheckState.Unchecked: "Unchecked",
    Qt.CheckState.PartiallyChecked: "Partially Checked",
    Qt.CheckState.Checked: "Checked"
}


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        widget = QWidget()
        main_layout = QVBoxLayout()
        widget.setLayout(main_layout)
        self.setCentralWidget(widget)

        # widgets
        self.common_checkbox = QCheckBox("Common checkbox")
        self.tristate_checkbox = QCheckBox("Tristate checkbox")
        self.tristate_checkbox.setTristate()
        self.label = QLabel("Unchecked and Unchecked")

        # layout
        main_layout.addWidget(self.common_checkbox)
        main_layout.addWidget(self.tristate_checkbox)
        main_layout.addWidget(self.label)

        # connections
        self.common_checkbox.stateChanged.connect(self.update_label)
        self.tristate_checkbox.stateChanged.connect(self.update_label)

    def update_label(self):
        s1 = bistate_to_text[self.common_checkbox.isChecked()]
        s2 = tristate_to_text[self.tristate_checkbox.checkState()]
        self.label.setText(f"{s1} and {s2}")


app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()

QRadioButton

QRadioButton предназначен для создания радиокнопок, т.е. переключаемых кнопок, которые могут быть во включенном и выключенном состоянии. Обычно радиокнопки используются для предоставления доступа “один из”, т.е. в группе радиокнопок только одна кнопка может быть активной в любой момент времени, нажатие на другую кнопку включает её, но выключает остальные.

При изменении состояния радиокнопка излучает сигнал toggled. Узнать её состояние можно методом isChecked.

Один экземпляр QRadioButton соответствует одной кнопке, т.е. одной альтернативе. По умолчанию все радиокнопки с общим родительским виджетом (parent) являются взаимоисключающими, но удобнее использовать виджет QButtonGroup для этих целей. Этот виджет не предназначен для отрисовки каждой радиокнопки, а лишь следит за логическими связями между ними и за их состояниями. QButtonGroup создаётся пустым, методом addButton в него можно добавлять кнопки. QButtonGroup сигнал buttonClicked, если на одну из кнопок группы было произведено нажатие.

Чтобы отображать кнопки одной группы удобно использовать виджет-контейнер QGroupBox, который отображает группу любых виджетов (необязательно радиокнопки) в рамке с общим заголовком. Виджеты в нем располагаются так же, как и в QWidget, т.е. внутри макетов.

../_images/radiobuttons.png

Код под спойлером ниже создаёт группу взаимоисключающих радиокнопок, определяющих заголовок и иконку главного окна. Кнопки помещаются в QButtonGroup для достижения эффекта взаимоисключаемости и упрощения обработки сигналов кнопок. Для отрисовки используется QGroupBox.

Код.
import sys
import os

from PySide6.QtGui import *
from PySide6.QtWidgets import *


class ChooseIcon(QGroupBox):
    def __init__(self):
        super().__init__("Window icon and title")

        # icons
        self.python_icon = QIcon(os.path.join("..", "icons", "python.png"))
        self.qt_icon = QIcon(os.path.join("..", "icons", "qt.png"))
        self.phys_icon = QIcon(os.path.join("..", "icons", "phys.png"))

        # buttons
        self.python_button = QRadioButton("Python")
        self.python_button.setIcon(self.python_icon)

        self.qt_button = QRadioButton("Qt")
        self.qt_button.setIcon(self.qt_icon)

        self.phys_button = QRadioButton("Faculty of physics")
        self.phys_button.setIcon(self.phys_icon)

        # ButtonGroup
        self.button_group = QButtonGroup()
        self.button_group.addButton(self.python_button)
        self.button_group.addButton(self.qt_button)
        self.button_group.addButton(self.phys_button)

        # layout
        layout = QVBoxLayout()
        layout.addWidget(self.python_button)
        layout.addWidget(self.qt_button)
        layout.addWidget(self.phys_button)
        self.setLayout(layout)

        # activating one button
        self.python_button.setChecked(True)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setGeometry(100, 100, 250, 130)
        self.choose_icon = ChooseIcon()
        self.setCentralWidget(self.choose_icon)
        self.update_bar()
        self.choose_icon.button_group.buttonClicked.connect(self.update_bar)

    def update_bar(self):
        active_button = self.choose_icon.button_group.checkedButton()
        text = active_button.text()
        icon = active_button.icon()
        self.setWindowTitle(text)
        self.setWindowIcon(icon)


app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()