{ "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 }