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

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


products:pussy:other:dev_custom_property

Различия

Показаны различия между двумя версиями страницы.

Ссылка на это сравнение

Следующая версия
Предыдущая версия
products:pussy:other:dev_custom_property [2023/12/01 15:46] – создано ironmeshproducts:pussy:other:dev_custom_property [2023/12/13 14:54] (текущий) – внешнее изменение 127.0.0.1
Строка 1: Строка 1:
 ====== Разрабатываем класс свойства ====== ====== Разрабатываем класс свойства ======
  
-FIXME+В данном уроке я расскажу как разработать собственный класс Свойства. Фреймворк уже содержит некоторое количество [[:products:pussy:api:pyub:types:properties:main|готовых свойств]], но в в определенных случаях этого может быть недостаточно, подробнее о концепции свойств читайте [[:products:pussy:concepts:property|здесь]]. 
 + 
 +Итак, перед началом следует упомянуть несколько важных моментов: значения и параметры Свойств должны иметь такие типы данных, которые могут быть сериализованы с помощью модуля [[https://docs.python.org/3/library/pickle.html|pickle]], чтобы Менеджер мог сохранить их в базе данных, в противном случае это может привести к непредсказуемым результатам, вероятнее всего Менеджер просто не сможет сохранить данные. 
 + 
 +<WRAP center round important 60%>\\ 
 +Значения и параметры должны иметь данные типов, которые могут быть сериализованы с помощью модуля [[https://docs.python.org/3/library/pickle.html|pickle]].\\ 
 +</WRAP> 
 + 
 +Как вам должно быть известно Свойство это компонент, предназначеный для хранения и отображения данных. Свойства делятся на 2 типа: с валидацией значения по заданным параметрам и без валидации, во всех случаях нужно переопределить следующие методы: 
 + 
 +  * [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;property#init|_ _init_ _()]] 
 +  * [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;abstractproperty#get_input_widget|get_input_widget()]] 
 +  * [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;abstractproperty#extract_widget_data|extract_widget_data()]] 
 +  * [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;abstractproperty#retranslate|retranslate()]] 
 +  * [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;propertyvalidated#validate_value|validate_value()]] (для Свойств с механизмом валидации значения) 
 + 
 +На деле все предельно просто, следуйте всем рекомендациям, чтобы получить рабочий класс Свойства, который будет работать без конфликтов с другими компонентами системы. 
 + 
 +===== Разработка Свойства без механизма валидации значения ===== 
 + 
 +Опишу процесс разработки на примере уже существующего свойства [[:products:pussy:api:pyub:types:properties:boolproperty|]], оно очень просто устроено и служит для хранения одного булевого значения. 
 + 
 +Для начала создадим новый py-файл и определим новый класс, унаследованный от [[:products:pussy:api:pyub:types:properties:property|]]. Затем нужно переопределить необходимые методы, код метода //_ _init_ _() //  приведен ниже 
 + 
 +<code python> 
 + 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 
 + 
 + 
 +</code> 
 + 
 +тут все просто, здесь мы задаем начальное значение свойства и устанавливаем его параметры. Значение свойства должно храниться в поле //_value//, если отступить от этого правила, то придется переопределять методы [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;abstractproperty#value|value()]] и [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;abstractproperty#set_value|set_value()]]. Имена полей параметров должны начинаться с префикса **p_**, отступление от этого правила вынудит переопределять методы [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;abstractproperty#get_parameters_dict|get_parameters_dict()]] и [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;abstractproperty#set_parameters_from_dict|set_parameters_from_dict()]]. Имя свойства должно храниться в поле //p_name//, в противном случае будет необходимо переопределить метод [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;abstractproperty#get_name|get_name()]]. Имена аргументов для метода лучше выбрать из тех, что существуют, а для тех что нет, следует подобрать имена, которые точно описывают их назначение. Существующие имена аргументов: 
 + 
 +  * //default_value //- значение свойства по умолчанию 
 +  * //name //- отображаемое имя свойства 
 +  * //tool_tip //- всплывающая подсказка, которая появляется при наведении курсора на виджет 
 +Далее нужно переопределить метод (//get_input_widget()//), который будет возвращать виджет для редактирования значения, в нашем случае отлично подходит существующий виджет [[:products:pussy:other:qcheckbox|QCheckBox]], также переопределим вспомогательный метод //retranslate(), //  их код приведен ниже 
 + 
 +<code python> 
 +    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)) 
 + 
 + 
 +</code> 
 + 
 +В методе //get_input_widget()//  создается экземпляр виджета и сохраняется в поле //_widget_ref//, у виджета устанавливается состояние согласно тому, что установлено в значении свойства; в методе //retranslate()//  производится установка всплывающей подсказки, тут производится поиск перевода в установленных словарях в контексте //properties, //  в этом методе следует производить все установки отображаемых данных, которым требуется перевод, так как пользователь может на лету производить переключение языка интерфейса; в конце метод должен вернуть ссылку на виджет. 
 + 
 +Итак, ссылку на виджет мы имеем, теперь, нам нужно обновить его значение, все правильно, значение свойства синхронизируется с виджетом по запросу, а не при изменении, эту работу выполняет метод// extract_widget_data()//, его задача извлечь данные из виджета и записать их внутрь свойства, также он должен уведомить о том, что значение было изменено. 
 + 
 +<code python> 
 +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 
 + 
 + 
 +</code> 
 + 
 +Для начала определям переменную //has_value_changed //  и присваиваем ей значение **False**, этот флаг будет сигнализировать о том, что значение свойства было изменено; затем проверяем существует ли поле со ссылкой на виджет //_widget_ref//, так сказать: "на всякий случай"; если ссылка присутствует, то значение виджета извлекается и сравнивается с текущим значением свойства, если они не равны, то значение свойства обновляется, флаг //has_value_changed //  устанавливается как **True**; в конце метод возвращает значение флага //has_value_changed//
 + 
 +Итак, вы только что узнали как создавать собственные свойства, согласитесь, это было очень просто, далее мы разберем как создавать свойства с механизмов валидации значения. 
 + 
 +<WRAP center round important 60%> 
 + 
 +Дополнительный раз напомню, что значение свойства должно храниться в поле //_value//, если отступить от этого правила, то придется переопределять методы [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;abstractproperty#value|value()]] и [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;abstractproperty#set_value|set_value()]]; имена полей параметров должны начинаться с префикса **p_**, отступление от этого правила вынудит переопределять методы [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;abstractproperty#get_parameters_dict|get_parameters_dict()]] и [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;abstractproperty#set_parameters_from_dict|set_parameters_from_dict()]]; имя свойства должно храниться в поле //p_name//, в противном случае будет необходимо переопределить метод [[http://wiki.mig-ironmesh.ru/products;pussy;api;pyub;types;properties;abstractproperty#get_name|get_name()]]. </WRAP> 
 + 
 +===== Разработка Свойства с механизмом валидации значения ===== 
 + 
 +Здесь рассмотрим процесс разработки на примере уже существующего свойства [[:products:pussy:api:pyub:types:properties:intproperty|]], которое служит для хранения одного целого числа. 
 + 
 +Для начала создадим новый py-файл и определим новый класс, унаследованный от [[:products:pussy:api:pyub:types:properties:propertyvalidated|]], приведу код для данного класса целиком, затем опишу каждый метод в отдельности 
 + 
 +<code python> 
 +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 
 + 
 + 
 +</code> 
 + 
 +Метод //_ _init_ _()//  как и в прошлом случае делает тоже самое, только обратите внимание на один нюанс: в самом начале вызывается метод //_set_validation()//  и ему передается значение **False**, этот метод создает флаг //_do_validation //  с указанным значением, он указывает на то, производить валидацию значения или нет, передав **False **  мы запрещаем свойству производить валидацию, валидацию мы включаем перед созданием поля //_value//, передав методу //_set_validation//  () значение **True**, чтобы после установки значения свойства произвести его валидацию. 
 + 
 +Механизм валидации устроен очень просто, у класса [[:products:pussy:api:pyub:types:properties:propertyvalidated|]] переопределен магический метод //_ _setattr_ _()//, который вызывается каждый раз, когда в коде происходит присвоение полей экземпляру класса, кстати, вот его код 
 + 
 +<code python> 
 +   def __setattr__(self, key, value) -> None: 
 +        self.__dict__[key] = value 
 + 
 +        if self._do_validation: 
 +            self.validate_value(key) 
 + 
 + 
 +</code> 
 + 
 +Здесь как обычно производится присвоение полю значения , затем выполняется вызов метода //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 = < значение>//, чтобы программа не вошла в бесконечный цикл. 
 + 
 +Ну, вот и все, урок по разработке собственного класса свойства подошел к концу, как вы могли убедиться это очень простой процесс, который не займет много времени и сил, но перед использованием не забывайте тестировать свой код. Если что-то не понятно, то можно задать вопрос в [[http://discord.gg/JD5yKPrKCk|Discord]].
  
  
products/pussy/other/dev_custom_property.1701434819.txt.gz · Последнее изменение: (внешнее изменение)