{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"\n",
"# Кнопки \n",
"\n",
"Все кнопки наследуют от абстрактного базового класса [QAbstractButton](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QAbstractButton.html). Встроенные классы кнопок:\n",
"- [QToolButton](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QToolButton.html);\n",
"- [QRadioButton](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QRadioButton.html);\n",
"- [QPushButton](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QPushButton.html);\n",
"- [QCommandLinkButton](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QCommandLinkButton.html);\n",
"- [QCheckBox](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QCheckBox.html);\n",
"\n",
"## Свойство `Checkable`\n",
"\n",
"Кнопки могут быть переключаемыми (`checkable`) и нет. Поменять это можно методом [setCheckable](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QAbstractButton.html#PySide6.QtWidgets.PySide6.QtWidgets.QAbstractButton.setCheckable). Если кнопка переключаемая, то она может находиться в двух состояниях: включенном (`checked`) и выключенном, при этом каждое последующее нажатие меняет статус кнопки. Статус кнопки всегда можно узнать методом [isChecked](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QAbstractButton.html#PySide6.QtWidgets.PySide6.QtWidgets.QAbstractButton.isChecked). \n",
"\n",
"Проще всего объяснить разницу между переключаемыми и не переключаемыми кнопками на конкретных примерах `QPushButton` и `QCheckBox` ([флажок](https://ru.wikipedia.org/wiki/%D0%A4%D0%BB%D0%B0%D0%B6%D0%BE%D0%BA_(%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81))). \n",
"\n",
"```{figure} ../_static/lecture_specific/qt/checkable_1.png\n",
"```\n",
"\n",
"Кнопка `QPushButton` по умолчания не является `checkable`, после нажатия на неё, кнопка возвращается в исходное состояние. Кнопка `QCheckbox` по умолчанию `checkable`. Первое нажатие на кнопку приведёт к появлению галочки внутри неё. Повторное нажатие приведет к исчезновению это галочки. Ниже приводится код, воссоздающий окно из примера выше. \n",
"\n",
"```python\n",
"import sys\n",
"\n",
"from PySide6.QtWidgets import *\n",
"\n",
"\n",
"class MainWindow(QMainWindow):\n",
" def __init__(self):\n",
" super().__init__()\n",
" widget = QWidget()\n",
" layout = QHBoxLayout()\n",
" widget.setLayout(layout)\n",
" self.setCentralWidget(widget)\n",
"\n",
" # buttons\n",
" check_box = QCheckBox(\"Check box\")\n",
" push_button = QPushButton(\"Push button\")\n",
" layout.addWidget(check_box)\n",
" layout.addWidget(push_button)\n",
"\n",
"\n",
"app = QApplication(sys.argv)\n",
"main_window = MainWindow()\n",
"main_window.show()\n",
"app.exec()\n",
"```\n",
"\n",
"Тем не менее `QPushButton` тоже можно сделать `checkable`.\n",
"\n",
"## Сигналы кнопок\n",
"\n",
"- `clicked` --- нажатие на кнопку;\n",
"- `pressed` --- зажатие кнопки;\n",
"- `released` --- отпуск кнопки;\n",
"- `toggled` --- изменения состояние `checkable` кнопки.\n",
"\n",
"## `QPushButton`\n",
"\n",
"Кнопка [QPushButton](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QPushButton.html) уже встречалась и подробно здесь обсуждаться не будет. \n",
"\n",
"- основной сигнал --- `clicked`;\n",
"- методом [setIcon](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QAbstractButton.html#PySide6.QtWidgets.PySide6.QtWidgets.QAbstractButton.setIcon) можно установить иконку кнопки, методом [setIconSize](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QAbstractButton.html#PySide6.QtWidgets.PySide6.QtWidgets.QAbstractButton.setIconSize) можно поменять её размер.\n",
"- методом [setCheckable](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QAbstractButton.html#PySide6.QtWidgets.PySide6.QtWidgets.QAbstractButton.setCheckable) можно сделать её переключаемой;\n",
"- методом [setFlat](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QPushButton.html#PySide6.QtWidgets.PySide6.QtWidgets.QPushButton.setFlat) можно указать, показывать ли границу кнопки или нет;\n",
"\n",
"Код под спойлером ниже демонстрирует поведение этих методов на примере так называемой `toggle` кнопки.\n",
"\n",
"```{figure} ../_static/lecture_specific/qt/toggle_buttons.png\n",
"```\n",
"\n",
"\n",
"\n",
"Код.
\n",
"\n",
"```python\n",
"import sys\n",
"import os\n",
"\n",
"from PySide6.QtCore import QSize\n",
"from PySide6.QtGui import *\n",
"from PySide6.QtWidgets import *\n",
"\n",
"\n",
"class ToggleButton(QPushButton):\n",
" def __init__(self, *args, **kwargs):\n",
" super().__init__(*args, **kwargs)\n",
"\n",
" self.setCheckable(True)\n",
" self.setChecked(True)\n",
" self.setFlat(True)\n",
" self.clicked.connect(self.updateIcon)\n",
" self.icon_on = QIcon(os.path.join(\"..\", \"icons\", \"on-button.png\"))\n",
" self.icon_off = QIcon(os.path.join(\"..\", \"icons\", \"off-button.png\"))\n",
" self.updateIcon()\n",
"\n",
" def updateIcon(self):\n",
" if self.isChecked():\n",
" self.setIcon(self.icon_on)\n",
" else:\n",
" self.setIcon(self.icon_off)\n",
" self.setIconSize(QSize(40, 30))\n",
"\n",
"\n",
"class MainWindow(QMainWindow):\n",
" def __init__(self):\n",
" super().__init__()\n",
" widget = QWidget()\n",
" main_layout = QVBoxLayout()\n",
" widget.setLayout(main_layout)\n",
" self.setCentralWidget(widget)\n",
"\n",
" # top row\n",
" self.button = QPushButton(\"Button\")\n",
" self.label = QLabel(\"Label\")\n",
" self.label.setAlignment(Qt.AlignCenter)\n",
" self.edit = QLineEdit()\n",
"\n",
" # bottom row\n",
" self.enable_button = ToggleButton(\"Enable\")\n",
" self.show_button = ToggleButton(\"Show\")\n",
"\n",
" # layout\n",
" top_layout = QHBoxLayout()\n",
" top_layout.addWidget(self.button)\n",
" top_layout.addWidget(self.label)\n",
" top_layout.addWidget(self.edit)\n",
" main_layout.addLayout(top_layout)\n",
"\n",
" bottom_layout = QHBoxLayout()\n",
" bottom_layout.addWidget(self.enable_button)\n",
" bottom_layout.addWidget(self.show_button)\n",
" main_layout.addLayout(bottom_layout)\n",
"\n",
" # connections\n",
" self.enable_button.clicked.connect(self.on_button_enable_click)\n",
" self.show_button.clicked.connect(self.on_button_show_click)\n",
"\n",
" def on_button_show_click(self):\n",
" for widget in [self.button, self.label, self.edit]:\n",
" widget.setVisible(self.show_button.isChecked())\n",
"\n",
" def on_button_enable_click(self):\n",
" for widget in [self.button, self.label, self.edit]:\n",
" widget.setEnabled(self.enable_button.isChecked())\n",
"\n",
"app = QApplication(sys.argv)\n",
"main_window = MainWindow()\n",
"main_window.show()\n",
"app.exec()\n",
"``` \n",
"\n",
" \n",
"\n",
"## `QCheckBox`\n",
"\n",
"Флажки [QCheckBox](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QCheckBox.html) обычно представляют опции программы, которые могут быть включены и выключены без влияния на другие опции. Все `QCheckBox` изначально являются переключаемыми (`checkable`), а значит хранят состояние (поставлен флажок или нет), которое всегда можно узнать методом [isChecked](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QAbstractButton.html#PySide6.QtWidgets.PySide6.QtWidgets.QAbstractButton.isChecked).\n",
"\n",
"По умолчанию `QCheckBox` может находиться в двух состояния: флажок поставлен (`checked`) и флажок не поставлен (`unchecked`). Методом [setTristate](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QCheckBox.html#PySide6.QtWidgets.PySide6.QtWidgets.QCheckBox.setTristate) можно сделать `QCheckBox` с третьим возможным состоянием \"флажок поставлен частично\" (`partially checked`). В таком случае состояние флажка запрашивать надо методом [checkState](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QCheckBox.html#PySide6.QtWidgets.PySide6.QtWidgets.QCheckBox.checkState), который возвращает `enum` [Qt.CheckState](https://doc.qt.io/qtforpython/PySide6/QtCore/Qt.html#PySide6.QtCore.PySide6.QtCore.Qt.CheckState) с тремя возможными значениями: `Qt.Unchecked`, `Qt.PartiallyChecked` и `Qt.Checked`.\n",
"\n",
"Как и у всех кнопок у `QCheckBox` есть сигнал `clicked`, но лучше использовать специальный сигнал [stateChanged](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QCheckBox.html#PySide6.QtWidgets.PySide6.QtWidgets.QCheckBox.stateChanged), который излучается каждый раз, когда меняется состояние (ставится флажок, или наоборот). \n",
"\n",
"\n",
"```{figure} ../_static/lecture_specific/qt/checkboxes.png\n",
"```\n",
"\n",
"Код под спойлером ниже демонстрирует все перечисленные особенности `QCheckBox`.\n",
"\n",
"\n",
"Код.
\n",
"\n",
"```python\n",
"import sys\n",
"\n",
"from PySide6.QtGui import *\n",
"from PySide6.QtWidgets import *\n",
"\n",
"bistate_to_text = {\n",
" False: \"Unchecked\",\n",
" True: \"Checked\"\n",
"}\n",
"\n",
"tristate_to_text = {\n",
" Qt.CheckState.Unchecked: \"Unchecked\",\n",
" Qt.CheckState.PartiallyChecked: \"Partially Checked\",\n",
" Qt.CheckState.Checked: \"Checked\"\n",
"}\n",
"\n",
"\n",
"class MainWindow(QMainWindow):\n",
" def __init__(self):\n",
" super().__init__()\n",
" widget = QWidget()\n",
" main_layout = QVBoxLayout()\n",
" widget.setLayout(main_layout)\n",
" self.setCentralWidget(widget)\n",
"\n",
" # widgets\n",
" self.common_checkbox = QCheckBox(\"Common checkbox\")\n",
" self.tristate_checkbox = QCheckBox(\"Tristate checkbox\")\n",
" self.tristate_checkbox.setTristate()\n",
" self.label = QLabel(\"Unchecked and Unchecked\")\n",
"\n",
" # layout\n",
" main_layout.addWidget(self.common_checkbox)\n",
" main_layout.addWidget(self.tristate_checkbox)\n",
" main_layout.addWidget(self.label)\n",
"\n",
" # connections\n",
" self.common_checkbox.stateChanged.connect(self.update_label)\n",
" self.tristate_checkbox.stateChanged.connect(self.update_label)\n",
"\n",
" def update_label(self):\n",
" s1 = bistate_to_text[self.common_checkbox.isChecked()]\n",
" s2 = tristate_to_text[self.tristate_checkbox.checkState()]\n",
" self.label.setText(f\"{s1} and {s2}\")\n",
"\n",
"\n",
"app = QApplication(sys.argv)\n",
"main_window = MainWindow()\n",
"main_window.show()\n",
"app.exec()\n",
"```\n",
" \n",
"\n",
"## `QRadioButton`\n",
"\n",
"[QRadioButton](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QRadioButton.html) предназначен для создания [радиокнопок](https://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D0%B4%D0%B8%D0%BE%D0%BA%D0%BD%D0%BE%D0%BF%D0%BA%D0%B0), т.е. переключаемых кнопок, которые могут быть во включенном и выключенном состоянии. Обычно радиокнопки используются для предоставления доступа \"один из\", т.е. в группе радиокнопок только одна кнопка может быть активной в любой момент времени, нажатие на другую кнопку включает её, но выключает остальные.\n",
"\n",
"При изменении состояния радиокнопка излучает сигнал [toggled](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QAbstractButton.html#PySide6.QtWidgets.PySide6.QtWidgets.QAbstractButton.toggled). Узнать её состояние можно методом `isChecked`.\n",
"\n",
"Один экземпляр `QRadioButton` соответствует одной кнопке, т.е. одной альтернативе. По умолчанию все радиокнопки с общим родительским виджетом (`parent`) являются взаимоисключающими, но удобнее использовать виджет [QButtonGroup](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QButtonGroup.html) для этих целей. Этот виджет не предназначен для отрисовки каждой радиокнопки, а лишь следит за логическими связями между ними и за их состояниями. `QButtonGroup` создаётся пустым, методом [addButton](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QButtonGroup.html#PySide6.QtWidgets.PySide6.QtWidgets.QButtonGroup.addButton) в него можно добавлять кнопки. `QButtonGroup` сигнал [buttonClicked](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QButtonGroup.html#PySide6.QtWidgets.PySide6.QtWidgets.QButtonGroup.buttonClicked), если на одну из кнопок группы было произведено нажатие.\n",
"\n",
"Чтобы отображать кнопки одной группы удобно использовать виджет-контейнер [QGroupBox](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QGroupBox.html), который отображает группу любых виджетов (необязательно радиокнопки) в рамке с общим заголовком. Виджеты в нем располагаются так же, как и в `QWidget`, т.е. внутри макетов.\n",
"\n",
"```{figure} ../_static/lecture_specific/qt/radiobuttons.png\n",
"```\n",
"\n",
"Код под спойлером ниже создаёт группу взаимоисключающих радиокнопок, определяющих заголовок и иконку главного окна. Кнопки помещаются в `QButtonGroup` для достижения эффекта взаимоисключаемости и упрощения обработки сигналов кнопок. Для отрисовки используется `QGroupBox`. \n",
"\n",
"\n",
"Код.
\n",
"\n",
"```python\n",
"import sys\n",
"import os\n",
"\n",
"from PySide6.QtGui import *\n",
"from PySide6.QtWidgets import *\n",
"\n",
"\n",
"class ChooseIcon(QGroupBox):\n",
" def __init__(self):\n",
" super().__init__(\"Window icon and title\")\n",
"\n",
" # icons\n",
" self.python_icon = QIcon(os.path.join(\"..\", \"icons\", \"python.png\"))\n",
" self.qt_icon = QIcon(os.path.join(\"..\", \"icons\", \"qt.png\"))\n",
" self.phys_icon = QIcon(os.path.join(\"..\", \"icons\", \"phys.png\"))\n",
"\n",
" # buttons\n",
" self.python_button = QRadioButton(\"Python\")\n",
" self.python_button.setIcon(self.python_icon)\n",
"\n",
" self.qt_button = QRadioButton(\"Qt\")\n",
" self.qt_button.setIcon(self.qt_icon)\n",
"\n",
" self.phys_button = QRadioButton(\"Faculty of physics\")\n",
" self.phys_button.setIcon(self.phys_icon)\n",
"\n",
" # ButtonGroup\n",
" self.button_group = QButtonGroup()\n",
" self.button_group.addButton(self.python_button)\n",
" self.button_group.addButton(self.qt_button)\n",
" self.button_group.addButton(self.phys_button)\n",
"\n",
" # layout\n",
" layout = QVBoxLayout()\n",
" layout.addWidget(self.python_button)\n",
" layout.addWidget(self.qt_button)\n",
" layout.addWidget(self.phys_button)\n",
" self.setLayout(layout)\n",
"\n",
" # activating one button\n",
" self.python_button.setChecked(True)\n",
"\n",
"\n",
"class MainWindow(QMainWindow):\n",
" def __init__(self):\n",
" super().__init__()\n",
" self.setGeometry(100, 100, 250, 130)\n",
" self.choose_icon = ChooseIcon()\n",
" self.setCentralWidget(self.choose_icon)\n",
" self.update_bar()\n",
" self.choose_icon.button_group.buttonClicked.connect(self.update_bar)\n",
"\n",
" def update_bar(self):\n",
" active_button = self.choose_icon.button_group.checkedButton()\n",
" text = active_button.text()\n",
" icon = active_button.icon()\n",
" self.setWindowTitle(text)\n",
" self.setWindowIcon(icon)\n",
"\n",
"\n",
"app = QApplication(sys.argv)\n",
"main_window = MainWindow()\n",
"main_window.show()\n",
"app.exec()\n",
"```\n",
" "
]
}
],
"metadata": {
"language_info": {
"name": "python"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}