Инструменты пользователя

Инструменты сайта


products:pussy:other:dev_custom_property

Разрабатываем класс свойства

В данном уроке я расскажу как разработать собственный класс Свойства. Фреймворк уже содержит некоторое количество готовых свойств, но в в определенных случаях этого может быть недостаточно, подробнее о концепции свойств читайте здесь.

Итак, перед началом следует упомянуть несколько важных моментов: значения и параметры Свойств должны иметь такие типы данных, которые могут быть сериализованы с помощью модуля pickle, чтобы Менеджер мог сохранить их в базе данных, в противном случае это может привести к непредсказуемым результатам, вероятнее всего Менеджер просто не сможет сохранить данные.


Значения и параметры должны иметь данные типов, которые могут быть сериализованы с помощью модуля pickle.

Как вам должно быть известно Свойство это компонент, предназначеный для хранения и отображения данных. Свойства делятся на 2 типа: с валидацией значения по заданным параметрам и без валидации, во всех случаях нужно переопределить следующие методы:

На деле все предельно просто, следуйте всем рекомендациям, чтобы получить рабочий класс Свойства, который будет работать без конфликтов с другими компонентами системы.

Разработка Свойства без механизма валидации значения

Опишу процесс разработки на примере уже существующего свойства BoolProperty, оно очень просто устроено и служит для хранения одного булевого значения.

Для начала создадим новый py-файл и определим новый класс, унаследованный от Property. Затем нужно переопределить необходимые методы, код метода _ _init_ _() приведен ниже

 def __init__(self, default_value:bool=False, name="Unnamed", tool_tip=""):
        self._value = default_value
        self.p_name = name
        self.p_tool_tip = tool_tip

тут все просто, здесь мы задаем начальное значение свойства и устанавливаем его параметры. Значение свойства должно храниться в поле _value, если отступить от этого правила, то придется переопределять методы value() и set_value(). Имена полей параметров должны начинаться с префикса p_, отступление от этого правила вынудит переопределять методы get_parameters_dict() и set_parameters_from_dict(). Имя свойства должно храниться в поле p_name, в противном случае будет необходимо переопределить метод get_name(). Имена аргументов для метода лучше выбрать из тех, что существуют, а для тех что нет, следует подобрать имена, которые точно описывают их назначение. Существующие имена аргументов:

  • default_value - значение свойства по умолчанию
  • name - отображаемое имя свойства
  • tool_tip - всплывающая подсказка, которая появляется при наведении курсора на виджет

Далее нужно переопределить метод (get_input_widget()), который будет возвращать виджет для редактирования значения, в нашем случае отлично подходит существующий виджет QCheckBox, также переопределим вспомогательный метод retranslate(), их код приведен ниже

    def get_input_widget(self) -> QCheckBox:
        self._widget_ref = QCheckBox()
        self._widget_ref.setCheckState(Qt.Checked if self._value else Qt.Unchecked)
        self.retranslate()
        return self._widget_ref
 
    def retranslate(self) -> None:
        self._widget_ref.setToolTip(QCoreApplication.translate("properties", self.p_tool_tip))

В методе get_input_widget() создается экземпляр виджета и сохраняется в поле _widget_ref, у виджета устанавливается состояние согласно тому, что установлено в значении свойства; в методе retranslate() производится установка всплывающей подсказки, тут производится поиск перевода в установленных словарях в контексте properties, в этом методе следует производить все установки отображаемых данных, которым требуется перевод, так как пользователь может на лету производить переключение языка интерфейса; в конце метод должен вернуть ссылку на виджет.

Итак, ссылку на виджет мы имеем, теперь, нам нужно обновить его значение, все правильно, значение свойства синхронизируется с виджетом по запросу, а не при изменении, эту работу выполняет метод extract_widget_data(), его задача извлечь данные из виджета и записать их внутрь свойства, также он должен уведомить о том, что значение было изменено.

def extract_widget_data(self) -> bool:
        has_value_changed: bool = False
        if hasattr(self, "_widget_ref"):
            widget_value: bool = self._widget_ref.checkState() == Qt.Checked
            if self._value != widget_value:
                self._value = widget_value
                has_value_changed = True
        return has_value_changed

Для начала определям переменную has_value_changed и присваиваем ей значение False, этот флаг будет сигнализировать о том, что значение свойства было изменено; затем проверяем существует ли поле со ссылкой на виджет _widget_ref, так сказать: «на всякий случай»; если ссылка присутствует, то значение виджета извлекается и сравнивается с текущим значением свойства, если они не равны, то значение свойства обновляется, флаг has_value_changed устанавливается как True; в конце метод возвращает значение флага has_value_changed.

Итак, вы только что узнали как создавать собственные свойства, согласитесь, это было очень просто, далее мы разберем как создавать свойства с механизмов валидации значения.

Дополнительный раз напомню, что значение свойства должно храниться в поле _value, если отступить от этого правила, то придется переопределять методы value() и set_value(); имена полей параметров должны начинаться с префикса p_, отступление от этого правила вынудит переопределять методы get_parameters_dict() и set_parameters_from_dict(); имя свойства должно храниться в поле p_name, в противном случае будет необходимо переопределить метод get_name().

Разработка Свойства с механизмом валидации значения

Здесь рассмотрим процесс разработки на примере уже существующего свойства IntProperty, которое служит для хранения одного целого числа.

Для начала создадим новый py-файл и определим новый класс, унаследованный от PropertyValidated, приведу код для данного класса целиком, затем опишу каждый метод в отдельности

class IntProperty(PropertyValidated):
 
    def __init__(self, default_value:int = 0, name="Unnamed", minimum=1, maximum=10, single_step=1, tool_tip=""):
        self._set_validation(False)
        self.p_name = name
        self.p_minimum = minimum
        self.p_maximum = maximum
        self.p_single_step = single_step
        self.p_tool_tip = tool_tip
        self._set_validation(True)
        self._value = default_value
 
    def get_input_widget(self) -> QSpinBox:
        self._widget_ref = QSpinBox()
        self._widget_ref.setMinimum(self.p_minimum)
        self._widget_ref.setMaximum(self.p_maximum)
        self._widget_ref.setSingleStep(self.p_single_step)
        self._widget_ref.setValue(self._value)
        self.retranslate()
 
        sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
        self._widget_ref.setSizePolicy(sizePolicy)
 
        return self._widget_ref
 
    def extract_widget_data(self) -> bool:
        has_value_changed: bool = False
        if (hasattr(self, "_widget_ref")) and (self._value != self._widget_ref.value()):
            self._value = self._widget_ref.value()
            has_value_changed = True
        return has_value_changed
 
    def retranslate(self) -> None:
        self._widget_ref.setToolTip(QCoreApplication.translate("properties", self.p_tool_tip))
 
    def validate_value(self, changed_attr:str=""):
        check_list = ["_value", "p_maximum", "p_minimum"]
        if (changed_attr in check_list) or not changed_attr:
            value = self._value
            if value <self.p_minimum:
                self.__dict__["_value"] = self.p_minimum
            elif value> self.p_maximum:
                self.__dict__["_value"] = self.p_maximum

Метод _ _init_ _() как и в прошлом случае делает тоже самое, только обратите внимание на один нюанс: в самом начале вызывается метод _set_validation() и ему передается значение False, этот метод создает флаг _do_validation с указанным значением, он указывает на то, производить валидацию значения или нет, передав False мы запрещаем свойству производить валидацию, валидацию мы включаем перед созданием поля _value, передав методу _set_validation () значение True, чтобы после установки значения свойства произвести его валидацию.

Механизм валидации устроен очень просто, у класса PropertyValidated переопределен магический метод _ _setattr_ _(), который вызывается каждый раз, когда в коде происходит присвоение полей экземпляру класса, кстати, вот его код

   def __setattr__(self, key, value) -> None:
        self.__dict__[key] = value
 
        if self._do_validation:
            self.validate_value(key)

Здесь как обычно производится присвоение полю значения , затем выполняется вызов метода validate_value(), если флаг _do_validation установлен как True. Напомню, что _do_validation устанавливается методом _set_validation().

Методы get_input_widget(), retranslate() и extract_widget_data() пишутся по тем же правилам, которые были описаны в первом случае.

Для свойств данного типа нужно определить важный метод validate_value(), который и определяет алгоритм валидации значения свойства. Данный метод принимает changed_attr, в нем содержится строка с именем изменяемого поля; в переменной сheck_list содержится список полей, при изменении которых будет производиться валидация, в данном случае это само значение свойства, максимальное и минимальное значения, можно обойтись и без данного списка, только валидация будет производиться даже в тех случаях, когда это не требуется; далее производится проверка на вхождение изменяемого атрибута в чеклист, если он входит или отсутствует, то выполняется алгоритм валидации, обратите внимание, что присвоение значения следует производить следующим образом: self._ _dict_ _[«_value»] = < значение>, а не так: self._value = < значение>, чтобы программа не вошла в бесконечный цикл.

Ну, вот и все, урок по разработке собственного класса свойства подошел к концу, как вы могли убедиться это очень простой процесс, который не займет много времени и сил, но перед использованием не забывайте тестировать свой код. Если что-то не понятно, то можно задать вопрос в Discord.

products/pussy/other/dev_custom_property.txt · Последнее изменение: 2023/12/13 14:54 — 127.0.0.1