products:pyrog:tutorials:dev:main
Различия
Показаны различия между двумя версиями страницы.
| Следующая версия | Предыдущая версия | ||
| products:pyrog:tutorials:dev:main [2026/05/15 21:25] – создано ironmesh | products:pyrog:tutorials:dev:main [2026/06/05 00:54] (текущий) – ironmesh | ||
|---|---|---|---|
| Строка 1: | Строка 1: | ||
| - | ====== Руководства по разработке ====== | + | ====== Руководство по разработке ====== |
| - | <WRAP center round important 60%> | + | Для разработки собственного приложения нужно иметь минимум базовые навыки разработки на Python и PySide6. |
| - | Данный раздел находится в разработке | + | |
| - | </WRAP> | + | ==== 2.1 Скачивание исходников и подготовка IDE к работе ==== |
| + | |||
| + | Скачайте исходные файлы программы из [[https:// | ||
| + | |||
| + | Теперь, | ||
| + | |||
| + | Можете выключить ненужные плагины, | ||
| + | |||
| + | ==== 2.2 Анатомия плагина ==== | ||
| + | |||
| + | В Python пакетом является папка, которая содержит в себе файл с именем '' | ||
| + | |||
| + | <code -> | ||
| + | 📁 <Имя папки плагина> | ||
| + | ├── 📁translations | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | ├──📄 __init__.py | ||
| + | └──📄 manifest.json | ||
| + | </ | ||
| + | |||
| + | В файле '' | ||
| + | |||
| + | <code python> | ||
| + | from .plugin import MyPlugin as plugin | ||
| + | </ | ||
| + | |||
| + | Можно то же самое записать так | ||
| + | |||
| + | <code python> | ||
| + | from .plugin import MyPlugin | ||
| + | plugin = MyPlugin | ||
| + | </ | ||
| + | |||
| + | В файле manifest.json хранится информация о плагине, | ||
| + | |||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | ] | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Пояснение к атрибутам: | ||
| + | |||
| + | * **name** - имя плагина, | ||
| + | * **description** - короткое описание плагина; | ||
| + | * **project_page_url** - ссылка на страницу проекта в интернете; | ||
| + | * **manual_url** - ссылка на страницу с документацией в интернете; | ||
| + | * **version** - текущая версия плагина, | ||
| + | * **version_status** - статус версии, | ||
| + | * **init_release_date** - первый релиз плагина, | ||
| + | * **update_date** - дата релиза текущей версии, | ||
| + | * **developer** - имя разработчика ; | ||
| + | * **developer_email** - электронная почта разработчика; | ||
| + | * **developer_webpage** - веб-страница разработчика; | ||
| + | * **repository_url** - ссылка на git репозиторий плагина; | ||
| + | * **forum_url** - ссылка на форум; | ||
| + | * **dependencies** - набор пакетов, | ||
| + | * **source_language** - локаль оригинального языка, код языка в формате ISO639-1, и код страны по стандарту ISO 3166-2, например, | ||
| + | |||
| + | Папка '' | ||
| + | |||
| + | Двигаемся дальше. Заглянем в файл '' | ||
| + | |||
| + | <code python> | ||
| + | from typing import Optional | ||
| + | |||
| + | from PySide6.QtWidgets import QWidget | ||
| + | |||
| + | from PyUB.Types import Plugin | ||
| + | from PyUB.Types.Properties import PropertyContainer | ||
| + | from .model.settings import Settings | ||
| + | from .view.main_widget import MainWidget | ||
| + | |||
| + | class MyPlugin(Plugin): | ||
| + | |||
| + | @classmethod | ||
| + | def gui(cls) -> QWidget: | ||
| + | return MainWidget() | ||
| + | |||
| + | @classmethod | ||
| + | def settings(cls) -> Optional[PropertyContainer]: | ||
| + | | ||
| + | </ | ||
| + | |||
| + | Там имеется класс MyPlugin, который унаследован от Plugin (PyUB.Types). У него есть два метода: | ||
| + | |||
| + | - // | ||
| + | - // | ||
| + | |||
| + | Обратите внимание, | ||
| + | |||
| + | Следующая остановка '' | ||
| + | |||
| + | <code python> | ||
| + | from PySide6.QtWidgets import QWidget, QLabel, QVBoxLayout | ||
| + | |||
| + | class MainWidget(QWidget): | ||
| + | |||
| + | def __init__(self): | ||
| + | super().__init__() | ||
| + | layout = QVBoxLayout(self) | ||
| + | layout.addWidget(QLabel(" | ||
| + | </ | ||
| + | |||
| + | Здесь объявляется класс пользовательского интерфейса плагина, | ||
| + | |||
| + | Заглянем в файл с пользовательскими настройками '' | ||
| + | |||
| + | <code python> | ||
| + | from PyUB.Types.Properties import * | ||
| + | |||
| + | class Settings(PropertyContainer): | ||
| + | int_property = IntProperty(name=" | ||
| + | | ||
| + | float_prop = FloatProperty(name=" | ||
| + | </ | ||
| + | |||
| + | Все настройки помещаются в классе '' | ||
| + | |||
| + | * **name** - имя свойства, | ||
| + | * **default_value** - значение по умолчанию; | ||
| + | * **tooltip** - всплывающая подсказка с описанием, | ||
| + | * **show_reset_btn** - (может быть не у всех Свойств) задает видимость кнопки сброса значения Свойства, | ||
| + | * **widget_enabled** - задает активность виджета свойства, | ||
| + | |||
| + | ^ Имя класса свойства ^ Тип данных ^ Пояснение ^ | ||
| + | | IntProperty | int | Целое число | | ||
| + | | FloatProperty | float | Вещественное число | | ||
| + | | BoolListProperty | tuple[bool, ...] | Кортеж булевых значений | | ||
| + | | BoolProperty | bool | Булевое значение | | ||
| + | | ColorProperty | str | Строка с кодом цвета в формате HEX, например, | ||
| + | | FontProperty | | Кортеж с данными о шрифте (имя шрифта, | ||
| + | | ComboBoxProperty | int | Индекс выбранного элемента списка | | ||
| + | | StringProperty | str | Строка | | ||
| + | | PasswordStringProperty | str | Строка | | ||
| + | | StringListProperty | tuple[str, ...] | Кортеж строк | | ||
| + | | FilePathProperty | str | Строка, | ||
| + | | FilePathListProperty | tuple[tuple[str, | ||
| + | |||
| + | Чтобы использовать свойства в коде, просто импортируем класс Settings, и вызываем свойство '' | ||
| + | |||
| + | Итак, вы познакомились с устройством плагина и этих минимальных сведений достаточно для разработки своих программ, | ||
| + | |||
| + | Как вы видите для того чтобы работать с Pyrog нужно объявить специальный класс плагина, | ||
| + | |||
| + | ===== 3. Расширяем познания ===== | ||
| + | |||
| + | В предыдущем разделе мы познакомились с основами разработки плагинов, | ||
| + | |||
| + | * жизненном цикле плагина; | ||
| + | * как получать сигналы о действиях пользователя; | ||
| + | * какие есть утилитарные функции; | ||
| + | * как сделать мультиязычный интерфейс; | ||
| + | * как устроены Свойства и контейнер свойств; | ||
| + | * как сделать собственную реализацию Свойства; | ||
| + | * как сделать собственный вариант интерфейса пользовательских настроек; | ||
| + | * других нюансах и особенностях. | ||
| + | |||
| + | ==== 3.1 Жизненный цикл плагина ==== | ||
| + | |||
| + | Когда пользователь запускает Менеджер, | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | Когда загружается контейнер свойств, | ||
| + | |||
| + | Если пользователь деактивирует плагин, | ||
| + | |||
| + | ==== 3.2 Ваш универсальный помощник ==== | ||
| + | |||
| + | Для общения с Менеджером используйте класс '' | ||
| + | |||
| + | Данный класс предоставляет следующие методы: | ||
| + | |||
| + | ^ Метод ^ Выполняемые действия ^ | ||
| + | | save_settings_parameters | Сохраняет параметры Свойств, | ||
| + | | save_settings_values | Сохраняет значения Свойств, | ||
| + | | plugin_dir_abspath | Возвращает строку с абсолютным путем к папке плагина | | ||
| + | | plugin_localstorage_dir_abspath | Возвращает строку с абсолютным путем к индивидуальной папке в локальном хранилище Менеджера ('' | ||
| + | |||
| + | Helper также имеет ряд сигналов, | ||
| + | |||
| + | ^ Сигнал ^ О чем сообщает ^ | ||
| + | | plugin_language_changing | Пользователь изменил язык плагина. Нужно для того, чтобы произвести процедуру перевода интерфейса. \\ Возвращает строку с именем локали языка, например, | ||
| + | | plugin_deactivating | Пользователь деактивировал плагин. Отправляется до того, как код плагина будет удален. Полезен для безопасного завершения сессии. | | ||
| + | | app_closing | Пользователь закрыл Менеджер. Отправляется до того, как программа завершит работу. Полезен для безопасного завершения сессии. | | ||
| + | | settings_editing_starting | Пользователь открыл пользовательские настройки. Отправляется перед тем как окно редактирования будет выведено на экран. Полезно когда нужно подготовить программу к изменению настроек. | | ||
| + | | settings_editing_finished | Пользователь завершил редактирование настроек. \\ Возвращает булевое значение, | ||
| + | |||
| + | В любом случае вы можете отслеживать изменения значений Свойств на лету. | ||
| + | |||
| + | Весь код в Менеджере выполняется синхронно, | ||
| + | |||
| + | ==== 3.3 Делаем локализацию интерфейса ==== | ||
| + | |||
| + | Итак, для разблокировки механизма интернационализации нужно в файле манифеста указать исходный язык, например, | ||
| + | |||
| + | ==== 3.3.1 Языковые константы ==== | ||
| + | |||
| + | Языковые константы (ЯК) - это объекты, | ||
| + | |||
| + | <code python> | ||
| + | # файл tranlslations.py | ||
| + | from PyUB.Types import LangConstant | ||
| + | |||
| + | TRANSLATION = LangConstant(" | ||
| + | APPLE = LangConstant(" | ||
| + | |||
| + | class Errors: | ||
| + | Warning = LangConstant(" | ||
| + | </ | ||
| + | |||
| + | В коде выше мы импортируем класс LangConstant и объявляем три ЯК в теле модуля и класса, | ||
| + | |||
| + | * контекст | ||
| + | * текст константы | ||
| + | * (опционально) строка идентификатор, | ||
| + | |||
| + | Извлечь перевод (при его наличии, | ||
| + | |||
| + | <code python> | ||
| + | from . import tranlslations as tr | ||
| + | </ | ||
| + | |||
| + | У ЯК переопределен метод '' | ||
| + | |||
| + | <code python> | ||
| + | >>> | ||
| + | Привет | ||
| + | </ | ||
| + | |||
| + | либо можно преобразовать объект в строку явно | ||
| + | |||
| + | <code python> | ||
| + | >>> | ||
| + | Привет | ||
| + | </ | ||
| + | |||
| + | преобразование производится при вызове экземпляра как функции, | ||
| + | |||
| + | <code python> | ||
| + | >>> | ||
| + | Привет | ||
| + | </ | ||
| + | |||
| + | использовать метод '' | ||
| + | |||
| + | <code python> | ||
| + | >>> | ||
| + | Привет | ||
| + | </ | ||
| + | |||
| + | либо с помощью функции '' | ||
| + | |||
| + | <code python> | ||
| + | from PyUB.utils import get_lang_const_translation | ||
| + | >>> | ||
| + | Привет | ||
| + | </ | ||
| + | |||
| + | этой функции можно передать строку, | ||
| + | |||
| + | Языковые константы поддерживают перевод для множественных форм числительных, | ||
| + | |||
| + | <code python> | ||
| + | >>> | ||
| + | 5 яблоков | ||
| + | </ | ||
| + | |||
| + | <code python> | ||
| + | >>> | ||
| + | 2 яблока | ||
| + | </ | ||
| + | |||
| + | <code python> | ||
| + | >>> | ||
| + | 1 яблоко | ||
| + | </ | ||
| + | |||
| + | Обращаю ваше внимание, | ||
| + | |||
| + | ==== 3.3.2 Процедура перевода ==== | ||
| + | |||
| + | Теперь, | ||
| + | |||
| + | Рассмотрим подробнее, | ||
| + | |||
| + | 1. Пользователь изменил язык | ||
| + | 2. Менеджер загружает доступные словари для выбранного языка | ||
| + | 3. Менеджер через Helper отправляет сигнал plugin_language_changing плагину о том, что язык изменился и ему нужно провести необходимые процедуры | ||
| + | 4. Если плагин привязал обработчики к сигналу, | ||
| + | 5. Менеджер выгружает ранее установленные словари | ||
| + | |||
| + | Итак, нам как разработчикам плагинов нужно сосредоточиться на шаге 4, когда у нас есть окно возможностей между загрузкой и выгрузкой словарей, | ||
| + | |||
| + | <code python> | ||
| + | from PyUB.Types import | ||
| + | from PySide6.QtWidgets import QWidget | ||
| + | from . import lang_consts | ||
| + | from PyUB.utils import retranslate_nested_langconstants | ||
| + | |||
| + | class MainWidget(QWidget): | ||
| + | |||
| + | def __init__(self): | ||
| + | super().__init__() | ||
| + | self._helper = Helper() | ||
| + | self._helper.plugin_language_changing.connect(self._on_retranslate) # привязка обработчика | ||
| + | ... | ||
| + | |||
| + | def _on_retranslate(self, | ||
| + | retranslate_nested_langconstants(lang_consts) | ||
| + | self._file_list_input.set_file_filter(f" | ||
| + | self._toolBox.setItemText(0, | ||
| + | self._toolBox.setItemText(1, | ||
| + | self._output_paths.set_file_filter(f" | ||
| + | self._save_btn.setText(lang_consts.SAVE()) | ||
| + | self._load_btn.setText(lang_consts.LOAD()) | ||
| + | self._clear_btn.setText(lang_consts.CLEAR()) | ||
| + | self._generate_btn.setText(lang_consts.GENERATE()) | ||
| + | self._add_plural_forms_checkbox.setText(lang_consts.SAVE_PLURAL_FORMS()) | ||
| + | </ | ||
| + | |||
| + | В нем с помощью функции '' | ||
| + | |||
| + | Когда мы используем QtDesigner для создания форм интерфейса, | ||
| + | |||
| + | ==== 3.3.3 Подготовка словарей ==== | ||
| + | |||
| + | После того как работа над кодом плагина закончена, | ||
| + | |||
| + | Для создания TS файлов из исходников есть встроенная утилита TS generator. Перейдите в её настройки и установите адрес папки, в которой будут сохраняться временные файлы, и путь к файлу lupdate, он находится в папке, где устанавливаются пакеты Python, для Windows это '' | ||
| + | |||
| + | * Python файлы ('' | ||
| + | * Файл манифеста ('' | ||
| + | * Файлы форм, созданных в QtDesigner ('' | ||
| + | |||
| + | * утилита импортирует исходные файлы Python и выполняет их код для поиска в них языковых констант, | ||
| + | |||
| + | Итак, мы получили файлы с исходными текстами, | ||
| + | |||
| + | В разделе **Контекст** мы видим, что все текстовые данные сгруппированы в соответствии с тем текстом, | ||
| + | |||
| + | После завершения перевода нужно скомпилировать файлы словарей, | ||
| + | |||
| + | Также есть возможность скомпилировать файлы переводов утилитой lrelease, можно прочитать информацию по ней [[https:// | ||
| + | |||
| + | Сейчас можно не делать перевод полностью руками, | ||
| + | |||
| + | ==== 3.4 Система свойств ==== | ||
| + | |||
| + | ==== 3.4.1 Как работать со Свойствами ==== | ||
| + | |||
| + | Свойство в контексте Pyrog это программная единица, | ||
| + | |||
| + | Рассмотрим строение Свойства на конкретном примере '' | ||
| + | |||
| + | <code python> | ||
| + | def __init__(self, | ||
| + | </ | ||
| + | |||
| + | Список аргументов-параметров следующий | ||
| + | |||
| + | ^ Параметр ^ Тип ^ Описание | | | ||
| + | | default_value | float | значение по умолчанию | | | ||
| + | | name | str | LangConstant | имя Свойства, | ||
| + | | minimum | float | минимальное значение | | | ||
| + | | maximum | float | максимальное значение | | | ||
| + | | single_step | float | единичный шаг, шаг изменения значения при нажатии на кнопки виджета QDoubleSpinBox | | | ||
| + | | decimals | int | количество знаков после запятой | | | ||
| + | | tooltip | str | LangConstant | всплывающая подсказка с описание свойства | | ||
| + | | show_reset_btn | bool | флаг того, будет ли показана кнопка сброса значения до дефолтного рядом с виджетом, | ||
| + | |||
| + | Как ранее было сказано, | ||
| + | |||
| + | **Реакция значения Свойства на изменения параметров** Значение Свойства должно находиться в диапазоне, | ||
| + | |||
| + | **Как работает мягкая валидация** Вы уже видели, | ||
| + | |||
| + | **Сохранение параметров Свойств в базе данных Менеджера** Если по каким-то причинам вам нужно изменять значения параметров и при следующем запуске плагина их нужно восстановить, | ||
| + | |||
| + | **Как получить уведомление об изменении значения** Свойства поддерживают сигналы, | ||
| + | |||
| + | **Как получить доступ к графическому интерфейсу** Каждое Свойство имеет метод '' | ||
| + | |||
| + | ==== 3.4.2 Как работать с контейнером свойств ==== | ||
| + | |||
| + | По своей сути Контейнер свойств это обычный класс, унаследованный от '' | ||
| + | |||
| + | * пакетно извлекает/ | ||
| + | * рендерит пользовательский интерфейс для всех Свойств; | ||
| + | * имеет единый сигнал, | ||
| + | |||
| + | Заполняйте ваш контейнер нужными Свойствами | ||
| + | |||
| + | <code python> | ||
| + | from PyUB.Types.Properties import * | ||
| + | |||
| + | class Settings(PropertyContainer): | ||
| + | int_property = IntProperty(name=" | ||
| + | tooltip=" | ||
| + | |||
| + | float_property = FloatProperty(name=" | ||
| + | </ | ||
| + | |||
| + | Затем в любом месте кода импортируйте контейнер и можете обращаться к вложенным Свойствам | ||
| + | |||
| + | <code python> | ||
| + | from .settings import Settings | ||
| + | |||
| + | print(Settings.int_property.value) # вывести значение свойства | ||
| + | Settings.int_property.value = 7 # изменить значение свойства | ||
| + | |||
| + | print(Settings.int_property.default_value) # вывести дефольное значение | ||
| + | Settings.int_property.default_value = 9 # измениим дефолтное значение | ||
| + | </ | ||
| + | |||
| + | Чтобы получить уведомление об изменение значений используйте нотификатор | ||
| + | |||
| + | <code python> | ||
| + | from .settings import Settings | ||
| + | |||
| + | notificator = Settings.pc_notifier() # получить нотификатор | ||
| + | notificator.property_value_changed(< | ||
| + | </ | ||
| + | |||
| + | Сигнал '' | ||
| + | |||
| + | <code python> | ||
| + | def handler(prop: | ||
| + | match prop: | ||
| + | case Settings.int_property: | ||
| + | print(" | ||
| + | case Settings.float_property: | ||
| + | print(" | ||
| + | </ | ||
| + | |||
| + | Если нотификатор стал не нужен, то удалите его методом '' | ||
| + | |||
| + | **Как получить доступ к общему графическому интерфейсу Свойств** Контейнер свойств может отрендерить графический интерфейс, | ||
| + | |||
| + | Дефолтный интерфейс дает возможность фильтровать Свойства по имени, а также сбросить значения до дефолтных для всех Свойств. Данный интерфейс можно переопределить, | ||
| + | |||
| + | **Как пакетно извлечь/ | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | Чтобы установить значения и параметры используются методы: | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | ==== 3.4.3 Как разработать собственное Свойство ==== | ||
| + | |||
| + | Может так случиться, | ||
| + | |||
| + | **Шаг 1** Объявляем класс и создаем сигнал. Имя сигнала не изменять! | ||
| + | |||
| + | <code python> | ||
| + | class BoolListProperty(Property): | ||
| + | value_changed = Signal(tuple) | ||
| + | </ | ||
| + | |||
| + | **Шаг 2** Задание схемы параметров Свойства, | ||
| + | |||
| + | <code python> | ||
| + | _param_schema = { | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | } | ||
| + | }, | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Данный словарь представляет из себя схему валидации параметров по типу и значению. Как говорилось ранее, в отношении параметров работают строгие правила валидации. Рассмотрим одну запись | ||
| + | |||
| + | <code python> | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Данный параметр определяет дефолтное значение Свойства, | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | Рассмотрим функцию-валидатор | ||
| + | |||
| + | <code python> | ||
| + | lambda self, args: len(self) == len(args[0]) and all(isinstance(item, | ||
| + | </ | ||
| + | |||
| + | параметр '' | ||
| + | |||
| + | **Шаг 3** Создаем свойства для всех параметров, | ||
| + | |||
| + | <code python> | ||
| + | items = create_param_property(" | ||
| + | |||
| + | tooltip = create_param_property(" | ||
| + | |||
| + | name = create_param_property(" | ||
| + | |||
| + | show_reset_btn = create_param_property(" | ||
| + | |||
| + | default_value = create_param_property(" | ||
| + | </ | ||
| + | |||
| + | Функция '' | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | **Шаг 4** Пишем конструктор. В модуле '' | ||
| + | |||
| + | <code python> | ||
| + | from . import language_constants as tr | ||
| + | </ | ||
| + | |||
| + | <code python> | ||
| + | def __init__(self, | ||
| + | | ||
| + | super().__init__() | ||
| + | |||
| + | self.set_parameters_from_dict( | ||
| + | {' | ||
| + | ' | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | |||
| + | self.value = default_value | ||
| + | </ | ||
| + | |||
| + | Набор аргументов должен совпадать с набором параметров. | ||
| + | |||
| + | **Шаг 5** Пишем метод создания виджета ввода | ||
| + | |||
| + | <code python> | ||
| + | def get_input_widget(self) -> QListWidget: | ||
| + | if hasattr(self, | ||
| + | return self._widget | ||
| + | |||
| + | self._widget = Resetter(QListWidget()) | ||
| + | self._widget.child_widget.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Maximum, | ||
| + | self._widget.reset_requested.connect(self.reset_value) | ||
| + | self._widget.child_widget.itemChanged.connect(self._on_widget_value_changed) | ||
| + | self._update_widget_params() | ||
| + | self._update_widget_value() | ||
| + | |||
| + | return self._widget | ||
| + | </ | ||
| + | |||
| + | На первом шаге проверяем был ли ранее создан виджет, | ||
| + | |||
| + | **Шаг 6** Пишем методы обновления параметров и значения виджета | ||
| + | |||
| + | <code python> | ||
| + | def _update_widget_value(self) -> None: | ||
| + | self._widget.child_widget.blockSignals(True) | ||
| + | for index, value in enumerate(self.items): | ||
| + | item = self._widget.child_widget.item(index) | ||
| + | if item: | ||
| + | if item.text() != utils.get_lang_const_translation(value): | ||
| + | item.setText(utils.get_lang_const_translation(value)) | ||
| + | item_check_state = True if item.checkState() == Qt.CheckState.Checked else False | ||
| + | if self.value[index] != item_check_state: | ||
| + | item.setCheckState(Qt.CheckState.Checked if self.value[index] else Qt.CheckState.Unchecked) | ||
| + | else: | ||
| + | item = QListWidgetItem(utils.get_lang_const_translation(value)) | ||
| + | item.setFlags(item.flags() | Qt.ItemIsUserCheckable) | ||
| + | item_state = Qt.CheckState.Checked if self.value[index] else Qt.CheckState.Unchecked | ||
| + | item.setCheckState(item_state) | ||
| + | self._widget.child_widget.addItem(item) | ||
| + | |||
| + | if self._widget.child_widget.count() > len(self.items): | ||
| + | for i in range(len(self.items), | ||
| + | self._widget.child_widget.takeItem(i) | ||
| + | self._adjust_widget_height() | ||
| + | self._widget.set_reset_btn_visibility(self._value != self._default_value and self.show_reset_btn) | ||
| + | self._widget.child_widget.blockSignals(False) | ||
| + | |||
| + | def _update_widget_params(self) -> None: | ||
| + | if hasattr(self, | ||
| + | self._widget.child_widget.setToolTip(utils.get_lang_const_translation(self.tooltip)) | ||
| + | self._update_widget_value() | ||
| + | </ | ||
| + | |||
| + | В методе '' | ||
| + | |||
| + | В методе '' | ||
| + | |||
| + | Используем функцию '' | ||
| + | |||
| + | **Шаг 7** Пишем обработчик события: | ||
| + | |||
| + | <code python> | ||
| + | def _on_widget_value_changed(self) -> None: | ||
| + | self._set_value(tuple(True if (self._widget.child_widget.item(i).checkState() == Qt.CheckState.Checked) else False for i in range(self._widget.child_widget.count()))) | ||
| + | self._widget.set_reset_btn_visibility(self._value != self._default_value and self.show_reset_btn) | ||
| + | </ | ||
| + | |||
| + | Данный метод вызывается, | ||
| + | |||
| + | Альтернативный способ обновления видимости кнопки сброса; | ||
| + | |||
| + | <code python> | ||
| + | def get_input_widget(self) -> QListWidget: | ||
| + | ... | ||
| + | self._widget = Resetter(QListWidget(), | ||
| + | ... | ||
| + | self._widget.update_reset_btn_visibility() | ||
| + | </ | ||
| + | |||
| + | Метод '' | ||
| + | |||
| + | **Шаг 8** Пишем метод валидации значения | ||
| + | |||
| + | <code python> | ||
| + | def _validate_value(self): | ||
| + | value = self._value | ||
| + | |||
| + | if not isinstance(value, | ||
| + | self._value = self.default_value | ||
| + | return | ||
| + | |||
| + | if not isinstance(value, | ||
| + | self._value = tuple(value) | ||
| + | |||
| + | if len(value) > len(self.items): | ||
| + | self._value = self._value[: | ||
| + | elif len(value) < len(self.items): | ||
| + | add_items = len(self.items) - len(value) | ||
| + | self._value = self._value + tuple(False for i in range(add_items)) | ||
| + | |||
| + | if not all(isinstance(item, | ||
| + | self._value = tuple(bool(item) for item in self._value) | ||
| + | </ | ||
| + | |||
| + | Примите во внимание, | ||
| + | |||
| + | **Важно!** в процедуре валидации присваивайте значения атрибуту '' | ||
| + | |||
| + | **Шаг 9 (ситуативный)** Иногда нужно переопределить код свойства value, например, | ||
| + | |||
| + | ==== 3.4.4 Разработка кастомного интерфейса для контейнера свойств ==== | ||
| + | |||
| + | Если вас не удовлетворяет стандартный интерфейс вывода виджетов Свойств, | ||
| + | |||
| + | Для начала создам типовой плагин, | ||
| + | |||
| + | <code python> | ||
| + | class Settings(PropertyContainer): | ||
| + | daughter_name_t1 = StringProperty(name=" | ||
| + | son_name_t1 = StringProperty(name=" | ||
| + | mother_name_t1 = StringProperty(name=" | ||
| + | father_name_t1 = StringProperty(name=" | ||
| + | |||
| + | daughter_birthdate_t2 = StringProperty(name=" | ||
| + | son_birthdate_t2 = StringProperty(name=" | ||
| + | mother_birthdate_t2 = StringProperty(name=" | ||
| + | father_birthdate_t2 = StringProperty(name=" | ||
| + | |||
| + | num1_t3 = IntProperty(name=" | ||
| + | num2_t3 = FloatProperty(name=" | ||
| + | num3_t3 = IntProperty(name=" | ||
| + | |||
| + | action1_t4 = StringProperty(name=" | ||
| + | action2_t4 = StringProperty(name=" | ||
| + | action3_t4 = StringProperty(name=" | ||
| + | action4_t4 = StringProperty(name=" | ||
| + | action5_t4 = StringProperty(name=" | ||
| + | </ | ||
| + | |||
| + | Для идентификации категории я добавил именам соответствующие суффиксы '' | ||
| + | |||
| + | <code python> | ||
| + | @classmethod | ||
| + | def pc_render_gui(cls) -> QWidget: | ||
| + | tab_widget = QTabWidget() | ||
| + | |||
| + | tab_suffixes = [f" | ||
| + | tab_names = [" | ||
| + | |||
| + | for suffix, tab_name in zip(tab_suffixes, | ||
| + | widget = QWidget() | ||
| + | scroll_area = QScrollArea() | ||
| + | scroll_area.setWidgetResizable(True) | ||
| + | scroll_area.setWidget(widget) | ||
| + | layout = QFormLayout(widget) | ||
| + | layout.setVerticalSpacing(15) | ||
| + | layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight | \ | ||
| + | | ||
| + | | ||
| + | |||
| + | properties = [prop for name, prop in cls.pc_properties() if name.endswith(suffix)] | ||
| + | for row, prop in enumerate(properties): | ||
| + | sign_label = QLabel(prop.get_name()) | ||
| + | sign_label.setWordWrap(True) | ||
| + | layout.setWidget(row, | ||
| + | layout.setWidget(row, | ||
| + | tab_widget.addTab(scroll_area, | ||
| + | return tab_widget | ||
| + | </ | ||
| + | |||
| + | Тут все просто, | ||
| + | |||
| + | ==== 3.5 Несколько слов о недостатках ==== | ||
| + | |||
| + | * При импорте плагины выполняются не в изолированной среде, а фактически становятся частью всей программы и каждому из них доступен весь код в рамках сессии. Таким образом любой плагин может вмешаться в работу Менеджера и других плагинов, | ||
| + | * Программа поставляется в виде скриптов, | ||
| + | * Программа является портативной и не создает рабочие файлы в операционной системе, | ||
| + | |||
| + | Проект будет развиваться, | ||
| - | Материалы в данном разделе находятся в процессе написания. | ||
| - | [[https:// | ||
products/pyrog/tutorials/dev/main.1778869504.txt.gz · Последнее изменение: — ironmesh