Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
VPS в 21 локации

От 104 рублей в месяц

Безлимитный трафик. Защита от ДДоС.

🔥 VPS до 5.7 ГГц под любые задачи с AntiDDoS в 7 локациях

💸 Гифткод CITFORUM (250р на баланс) и попробуйте уже сейчас!

🛒 Скидка 15% на первый платеж (в течение 24ч)

Скидка до 20% на услуги дата-центра. Аренда серверной стойки. Colocation от 1U!

Миграция в облако #SotelCloud. Виртуальный сервер в облаке. Выбрать конфигурацию на сайте!

Виртуальная АТС для вашего бизнеса. Приветственные бонусы для новых клиентов!

Виртуальные VPS серверы в РФ и ЕС

Dedicated серверы в РФ и ЕС

По промокоду CITFORUM скидка 30% на заказ VPS\VDS

Редакторы свойств

Содержание

Обзор

Открытость Delphi проявляется наиболее ярко в том, что наряду с расширяемостью Библиотеки Визуальных Компонент можно изменять саму среду программирования. Delphi предоставляет соответствующее API с тем, чтобы программисты могли расширять функциональность среды разработки. С помощью этого API можно создать свои собственные Эксперты (Experts), свою Систему Контроля Версий (Version Control system), Редакторы Компонент (Component Editors) и Редакторы Свойств (Property Editors).

При написании новых объектов часто требуется создавать для них свои Редакторы Свойств и Редакторы Компонент. В данном уроке и рассказывается, как это сделать и приводятся примеры.

Краткое описание инструментов среды Delphi и модулей, в которых реализованы соответствующие API:

API Экспертов - Позволяет создать свои собственные эксперты; модули EXPINTF.PAS и VIRTINTF.PAS

API Контроля Версий - Дает возможность создать свою систему Контроля Версий или подключить систему третьей фирмы; модули VCSINTF.PAS и VIRTINTF.PAS

API Редакторов Компонент - Создание диалогов, связанных с объектом во время дизайна. Пример - Menu Designer для TMenu или Fields Editor для TTable; модуль DSGNINTF.PAS

API Редакторов Свойств - Создание редакторов для использования их при редактировании свойств в Инспекторе Объектов; модуль DSGNINTF.PAS

Модули можно найти в библиотеке визуальных компонент (в директории X:\DELPHI\SOURCE\VCL).

Необходимо отметить, что большинству людей никогда не придется использовать вышеперечисленные API. Однако, некоторым программистам они очень могут пригодиться, особенно разработчикам новых объектов.

Редакторы свойств

Как Вы знаете, во время дизайна для настройки внешнего вида и поведения объекта нужно пользоваться Инспектором Объектов. Например, можно изменить цвет фона у объекта TLabel на форме.

Перейдем в окно Инспектора Объектов и выберем свойство Color - отметьте, что справа есть маленькая стрелка, она означает, что мы можем выбрать цвет из списка. Нажмите мышкой на эту стрелку (рис.1)

Рис.1 : Выбор цвета из списка

Столкнувшись с этим в первый раз Вы могли подумать, что этот список цветов является некоей функцией, жестко заданной разработчиками среды программирования Delphi. В действительности, для свойства Color используется соответствующий Редактор Свойств. И Вам вовсе не нужно работать в компании Borland, чтобы создать подобные Редакторы Свойств. Точно так же, как Вы добавляете новые компоненты в Delphi, Вы можете добавить свой собственный Редактор Свойств в среду разработки.

Стандартные Редакторы Свойств

Прежде, чем приступить к созданию своего Редактора Свойств, давайте исследуем уже имеющиеся в среде Delphi редакторы свойств. Вы уже видели редактор для свойства Color. Даже простейшие свойства, вроде Left или Caption, имеют свои редакторы. Причем, компоненты сами по себе даже не знают, что за редакторы используются для их свойств. Это означает, что Вы можете свой Редактор Свойств связать с уже существующими свойствами. Например, можно было бы написать Редактор Свойств, который ограничивает свойство, имеющее целый тип (Integer), некоторым максимальным значением и затем связать этот редактор со свойством Width для всех существующих компонент.

Взглянем на иерархию классов Редакторов Свойств. Базовым является класс TPropertyEditor:

TPropertyEditor
    TOrdinalProperty
        TIntegerProperty
            TColorProperty
            TModalResultProperty
            TTabOrderProperty
        TCharProperty
        TEnumProperty
        TSetProperty
        TShortCutProperty
    TFloatProperty
    TStringProperty
        TComponentNameProperty
        TFontNameProperty
        TCaptionProperty
    TSetElementProperty
    TClassProperty
          TFontProperty
    TMethodProperty
    TComponentProperty
Названия классов в большинстве своем очевидны. Класс TFloatProperty связан со свойствами, которые имеют тип Float, класс TSetProperty связан со свойствами, которые имеют тип Set. Некоторые редакторы имеют специальное назначение. Так, например, TTabOrderProperty нужен для того, чтобы предотвратить изменение свойства TabOrder (тип Integer) при выборе на форме нескольких компонент одновременно.

Класс TPropertyEditor

Прежде, чем писать свой собственный Редактор Свойств, нужно разобраться в базовом классе TPropertyEditor:

TPropertyEditor = class
  private
    FDesigner: TFormDesigner;
    FPropList: PInstPropList;
    FPropCount: Integer;
    constructor Create(ADesigner: TFormDesigner; APropCount: Integer);
    function GetPrivateDirectory: string;
    procedure SetPropEntry(Index: Integer; AInstance: TComponent;
      APropInfo: PPropInfo);
  protected
    function GetPropInfo: PPropInfo;
    function GetFloatValue: Extended;
    function GetFloatValueAt(Index: Integer): Extended;
    function GetMethodValue: TMethod;
    function GetMethodValueAt(Index: Integer): TMethod;
    function GetOrdValue: Longint;
    function GetOrdValueAt(Index: Integer): Longint;
    function GetStrValue: string;
    function GetStrValueAt(Index: Integer): string;
    procedure Modified;
    procedure SetFloatValue(Value: Extended);
    procedure SetMethodValue(const Value: TMethod);
    procedure SetOrdValue(Value: Longint);
    procedure SetStrValue(const Value: string);
  public
    destructor Destroy; override;
    procedure Activate; virtual;
    function AllEqual: Boolean; virtual;
    procedure Edit; virtual;
    function GetAttributes: TPropertyAttributes; virtual;
    function GetComponent(Index: Integer): TComponent;
    function GetEditLimit: Integer; virtual;
    function GetName: string; virtual;
    procedure GetProperties(Proc: TGetPropEditProc);virtual;
    function GetPropType: PTypeInfo;
    function GetValue: string; virtual;
    procedure GetValues(Proc: TGetStrProc); virtual;
    procedure Initialize; virtual;
    procedure SetValue(const Value: string); virtual;
    property Designer: TFormDesigner read FDesigner;
    property PrivateDirectory: string read GetPrivateDirectory;
    property PropCount: Integer read FPropCount;
    property Value: string read GetValue write SetValue;
  end;

Методы, приведенные ниже, можно переопределять (override) для изменения поведения Редактора свойств. ( "SetXxxValue" используется для представления одного из методов SetFloatValue, SetMethodValue, SetOrdValue или SetStrValue. "GetXxxValue" обозначает GetFloatValue, GetMethodValue, GetOrdValue или GetStrValue)

    Activate
    Вызывается, когда свойство выбирают в инспекторе объектов. Может быть полезно позволить некоторым атрибутам свойства определяться в каждый момент выбора этого свойства.
  • AllEqual
    Вызывается всякий раз, когда на форме выбирается более чем один объект. Если этот метод возвращает True, то вызывается GetValue, иначе в Инспекторе Объектов показывается пустая строка. AllEqual вызывается при условии, что GetAttributes возвращает paMultiSelect.
  • Edit
    Вызывается при нажатии кнопки '...' или по двойному щелчку мыши на свойстве. Этот метод может, к примеру, показать какое-нибудь диалоговое окно для редактирования свойства (пример - свойство Font).
  • GetAttributes
    Возвращает необходимую Инспектору Объектов информацию для того, чтобы тот смог отобразить свойство в подходящей манере. GetAttributes возвращает множество (set) значений типа TPropertyAttributes:
  • paValueList: Редактор свойств может возвращать список значений для этого свойства. Если этот атрибут установлен, то нужно определить GetValues. В Инспекторе объектов справа от свойства появится кнопка для выпадающего списка.
    paSortList: Инспектор объектов будет сортировать список, полученный от GetValues.
    paSubProperties: Свойство имеет подсвойства, которые будут показываться ниже в виде иерархии (outline). Если GetProperties будет генерировать объекты-свойства, то этот атрибут должен быть установлен.
    paDialog: Показывает, что метод Edit будет вызывать диалог. Если данный атрибут установлен, то появится кнопка '...' справа от свойства в Инспекторе Объектов.
    paMultiSelect: Позволяет свойству оставаться в Инспекторе Объектов, когда на форме выбрано сразу несколько объектов. Некоторые свойства не годятся для множественного выбора, например, Name.
    paAutoUpdate: Если этот атрибут установлен, то метод SetValue будет вызываться при каждом изменении, произведенном в редакторе, а не после завершения редактирования (пример - свойство Caption).
    paReadOnly: Значение менять нельзя.
  • GetComponent
    Возвращает компонент под номером Index в случае множественного выбора объектов (multiselect). GetAttributes должен возвращать paMultiSelect.
  • GetEditLimit
    Возвращает число символов, которые пользователь может ввести при редактировании свойства. По умолчанию 255 символов.
  • GetName
    Возвращает имя свойства. По умолчанию это имя получается из информации о типе, все подчеркивания замещаются пробелами. Данный метод Вам нужно переопределять только в том случае, если имя свойства отличается от того, которое нужно отображать в Инспекторе Объектов.
  • GetProperties
    Должен быть переопределен для вызова PropertyProc для каждого подсвойства (или вложенного свойства) редактируемого свойства и передачи нового TPropertyEdtior для каждого подсвойства. По умолчанию, PropertyProc не вызывается и подсвойства не ожидаются. TClassProperty будет передавать новый редактор свойств для каждого свойства, объявленного published в классе. TSetProperty передает новый редактор для каждого элемента множества.
  • GetPropType
    Возвращает указатель на информацию о типе редактируемого свойства.
  • GetValue
    Возвращает значение свойства в виде строки. По умолчанию возвращает '(unknown)'. Этот метод нужно переопределять с тем, чтобы возвращать правильное значение.
  • GetValues
    Вызывается, если GetAttributes возвращает paValueList. Должно вызвать Proc для каждого значения, которое приемлемо для данного свойства.
  • Initialize
    Вызывается при создании Редактора свойств.
  • SetValue(Value)
    Вызывается для того, чтобы установить значение свойства. Редактор свойств должен уметь разобрать строку (Value) и вызвать метод SetXxxValue. Если строка имеет некорректный формат или неверное значение, то редактор Свойств должен сгенерировать исключительную ситуацию (exception), описывающую данную проблему. SetValue может вообще проигнорировать все изменения и оставить всю обработку изменений методу Edit (как в свойстве Picture).

Свойства и методы полезные при создании нового класса Редактора свойств:

  • PrivateDirectory (свойство)
    Это директория, в которой находится .EXE, либо рабочая директория, указанная в DELPHI.INI. Если редактор должен сохранить какую-то информацию (установки), то лучше в этой директории.
  • Value (свойство)
    Текущее значение свойства, то же самое возвращает GetValue.
  • Modified (метод)
    Вызывается для того, чтобы показать, что значение свойства изменилось. Методы SetXxxValue вызывают Modified автоматически.
  • GetXxxValue (метод)
    Возвращает значение первого из редактируемых свойств.
  • SetXxxValue (метод)
    Устанавливает значения свойства для всех выбранных объектов.

Создание Редактора Свойств

При создании нового Редактора Свойств, конечно, не нужно всегда переписывать его заново от базового класса TPropertyEditor. Может оказаться достаточным выбрать в качестве предка уже существующий для данного свойства редактор и переопределить некоторые его методы. Давайте рассмотрим простейший пример нового Редактора Свойств. Как Вы знаете, у всех видимых объектов есть свойство Hint - подсказка, появляющаяся во время выполнения программы, если задержать на некоторое время мышь на объекте. Это свойство имеет тип String и во время дизайна для его редактирования используется Редактор типа TStringProperty. Обычно, подсказка бывает однострочной, но в некоторых случаях ее нужно сделать многострочной. В принципе, здесь проблемы нет, достаточно во время выполнения программы присвоить свойству Hint нужное значение, например:

Button1.Hint:='Line1'#13#10'Line2';

Теперь подсказка будет состоять из двух строк. Но это достаточно неудобно, более удобно было бы формировать многострочную подсказку во время дизайна, однако редактор TStringProperty такой возможности не дает. Давайте создадим новый редактор, который мог бы это сделать.

В нашем случае будет достаточно выбрать в качестве предка редактор TStringProperty и переписать некоторые методы. Во-первых, нужно переопределить метод Edit, в котором будет вызываться диалог для ввода строк подсказки. Во-вторых, нужно переопределить функцию GetAttributes, которая возвращает набор параметров, описывающих данное свойство. В частности, должен быть установлен атрибут paDialog, при этом в Инспекторе Объектов у свойства появится кнопка ':' для вызова диалога. И вообще-то нужно изменить метод GetValue, который используется для отображения значения свойства в Инспекторе Объектов.

Назовем новый Редактор Свойств THintProperty, декларация нового класса:

THintProperty = class(TStringProperty)
  public
    function GetAttributes: TPropertyAttributes; override;
    function GetValue : String; override;
    procedure Edit; override;
end;

Рассмотрим по порядку методы нового класса.

Функция GetAttributes добавляет к унаследованному множеству атрибуты paDialog (появляется кнопка ':') и paReadOnly (свойство нельзя редактировать непосредственно в Инспекторе Объектов, а только в диалоге, вызываемом через кнопку ':'):

function THintProperty.GetAttributes: TPropertyAttributes;
begin
  Result := inherited GetAttributes + [paDialog,
  paReadOnly];
end;

Функция GetValue заменяет "неправильные" символы #10 и #13 (перевод каретки и переход на новую строку) на символ ">":

function THintProperty.GetValue : string;
var
  i : Byte;
begin
  result:=inherited GetValue;
  for i:=1 to Byte(result[0]) do
    if result[i]<#32 then result[i]:='>';
end;

Процедура Edit вызывает диалог для ввода строк подсказки. Диалог можно было бы нарисовать свой собственный, однако можно воспользоваться уже готовым. Несколько разных диалогов лежит в директории X:\DELPHI\SOURCE\LIB. Мы воспользуемся модулем STREDIT.PAS, в котором есть необходимый диалог редактирования строк. Итак, процедура Edit:

procedure THintProperty.Edit;
var
  HintEditDlg : TStrEditDlg;
  s : string;
begin
  HintEditDlg:=TStrEditDlg.Create(Application);
  with HintEditDlg do
  try
    Memo.MaxLength := 254;
    s:=GetStrValue+#0;
    Memo.Lines.SetText(@s[1]);
    UpdateStatus(nil);
    ActiveControl := Memo;
    if ShowModal = mrOk then begin
      s:=StrPas(Memo.Lines.GetText);
      if s[0]>#2 then Dec(Byte(s[0]),2);
      SetStrValue(s);
    end;
  finally
    Free;
  end;
end;

Строка if s[0]>#2 then Dec(Byte(s[0]),2) нужна, так как Memo.Lines.GetText возвращает все строки с символами #13#10.

Регистрация Редактора Свойств

Новый Редактор Свойств готов, осталось только его зарегистрировать в среде Delphi. Для этого в интерфейсной части модуля с нашим редактором требуется поместить декларацию процедуры Register, а в части implementation написать следующее:

procedure Register;
begin
  RegisterPropertyEditor(TypeInfo(String), TControl, 'Hint',
                         THintProperty);
end;

Как уже сообщалось выше, один и тот же редактор свойств можно "привязать" к свойствам, в зависимости от их названия или типа объекта. Это определяется параметрами (второй и третий), которые передаются во время регистрации в процедуре RegisterPropertyEditor. Возможны четыре варианта:
Класс компонентыИмя свойстваДля каких свойств
Nil''совпадает тип свойства
Nil'Name'Тип свойства + Имя свойства
TClass''Тип свойства + класс компоненты
TClass'Name'Тип свойства + Имя свойства+ класс компоненты

Пояснение к таблице. Если вы зарегистрировали Редактор и указали как класс компоненты, так и имя свойства, то данный редактор "привязывается" ко всем свойствам, которые:

  • имеют тип, указанный в первом параметре процедуры;
  • принадлежат компоненте, которая относится к классу (или его потомкам), указанному во втором параметре;
  • имеют имя, совпадающее с указанным в третьем параметре;

Если вместо типа класса в процедуре регистрации стоит Nil, а вместо имени свойства - пустая строка '', то данный редактор "привязывается" ко всем свойствам, которые имеют тип, указанный в первом параметре, независимо от их имени или принадлежности к объекту какого-либо класса.

Если указан только класс, то редактор относится ко всем свойствам указанного типа для объектов указанного класса.

Если указано только имя, то редактор относится к свойствам указанного типа, которые имеют указанное имя.

В нашем случае Редактор Свойств зарегистрирован для всех свойств, которые имеют тип String, относятся к компоненте класса TControl или наследника от него и имеют имя 'Hint'.

Установка Редактора свойств

После того, как модуль с новым редактором свойств подготовлен, его нужно подключить к среде Delphi. Установка Редактора Свойств абсолютно аналогична установке новых объектов в палитру компонент и происходит следующим образом:

  • выберите пункт меню "Options|Install Components...."
  • нажмите кнопку "Add"
  • укажите имя подключаемого модуля (или воспользуйтесь кнопкой "Browse")
  • нажмите "OK" и еще раз "OK"

После успешной перекомпиляции библиотеки проверьте, как действует новый редактор свойств. Для этого создайте новый проект, положите на форму какой-либо видимый объект, например TButton, установите ShowHint для него в True, вызовите редактор подсказки (кнопка ':' в свойстве Hint), редактор выглядит примерно так:

В диалоге нажмите "OK" и запустите программу.

Полный текст модуля с Редактором Свойств см. в примерах к данному уроку.

Редактор Компонент

Редактор Компонент во многом похож на Редактор свойств, отличия в том, что его используют для изменений скорее всего объекта, нежели отдельного свойства.

Давайте взглянем на класс TComponentEditor в модуле DSGNINTF.PAS:

 TComponentEditor = class
  private
    FComponent: TComponent;
    FDesigner: TFormDesigner;
  public
    constructor Create(AComponent: TComponent;
      ADesigner: TFormDesigner); virtual;
    procedure Edit; virtual;
    procedure ExecuteVerb(Index: Integer); virtual;
    function GetVerb(Index: Integer): string; virtual;
    function GetVerbCount: Integer; virtual;
    procedure Copy; virtual;
    property Component: TComponent read FComponent;
    property Designer: TFormDesigner read FDesigner;
 end;

Редактор Компонент создается для каждого выбранного объекта на форме основываясь на классе объекта. При двойном щелчке на объекте вызывается метод Edit Редактора Компонент. При вызове контекстного меню (popup menu) по правой кнопке мыши, то для построения этого меню вызываются методы GetVerbCount и GetVerb. Если в этом меню выбирается пункт, то вызывается метод ExecuteVerb. Copy вызывается при копировании компонента в Clipboard.

Редактор Компонент по умолчанию (TDefaultEditor) при двойном щелчке на объекте создает (или переходит на) в Редакторе Исходного Текста заготовку для событий OnCreate, OnChanged или OnClick (какое первым попадется).

При создании Редактора Компонент вы должны переопределить либо метод Edit, либо три следующих метода: GetVerb, GetVerbCount и ExecuteVerb. Можно переопределять все четыре метода.

Если Редактор Компонент был вызван и изменил компонент, то всегда обязательно нужно вызвать метод Designer.Modified, чтобы Дизайнер об этом узнал.

Методы и свойства TComponentEditor:

Create(AComponent, ADesigner): Конструктор Редактора Компонент. AComponent - редактируемый компонент. ADesigner - интерфейс к Дизайнеру среды Delphi.

Edit: Вызывается при двойном щелчке мышью на компоненте. Редактор Компонент может вызвать какой-нибудь диалог или эксперт.

ExecuteVerb(Index): Выполняется, когда был выбран пункт номер Index из контекстного меню. Считается, что Редактор Компонент знает, как проинтерпретировать это значение.

GetVerb(Index): Редактор Компонент должен вернуть в этом методе строку, которая будет показана в виде пункта контекстного меню. Можно использовать обычные для пунктов меню символы, например &.

GetVerbCount: Возвращает число, которое определяет на какие значения будут отвечать методы GetVerb и ExecuteVerb. Например, если это число равно 3, то в меню будет добавлено три пункта со значениями Index от 0 до 2.

Copy: Вызывается, когда компонент нужно скопировать в Clipboard. На самом деле, образы полей компонента уже находятся в Clipboard. Просто предоставляется возможность скопировать различные типы форматов, которые игнорируются Дизайнером, но которые могут быть распознаны другими приложениями.

Пример Редактора Компонент

В качестве примера давайте создадим Редактор Компонент для класса TButton. Этот Редактор будет показывать сообщение и изменять свойство Caption у объекта TButton. В данном примере это будет срабатывать и при двойном щелчке мыши, и через контекстное меню.

Декларация нового класса Редактора Компонент:

TButtonEditor = class(TComponentEditor)
 private
  procedure HiThere;
 public
  procedure Edit; override;
  procedure ExecuteVerb(Index: Integer); override;
  function GetVerb(Index: Integer): string; override;
  function GetVerbCount: Integer; override;
end;

Процедура HiThere и будет показывать сообщение и изменять свойство Caption:

procedure TButtonEditor.HiThere;
begin
  MessageDlg('Hi! It replaces Default Component Editor.',
              mtInformation, [mbOK], 0);
  (Component as TButton).Caption:='Hi!';
  Designer.Modified;
end;

Процедуры Edit и ExecuteVerb только вызывают HiThere:

procedure TButtonEditor.Edit;
begin
  HiThere;
end;
procedure TButtonEditor.ExecuteVerb(Index: Integer);
begin
  if Index = 0 then HiThere;
end;

Процедуры GetVerb и GetVerbCount определяют вид контекстного меню:

function TButtonEditor.GetVerb(Index: Integer): string;
begin
  result:='&Get message ...'
end;
function TButtonEditor.GetVerbCount: Integer;
begin
  result:=1;
end;

Здесь в контекстное меню добавляется один пункт "Get message :".

Редактор Компонент готов.

Необходимо зарегистрировать новый Редактор Компонент, это делается аналогично регистрации Редактора Свойств, только проще:

procedure Register;
begin
  RegisterComponentEditor(TButton, TButtonEditor);
end;

После того, как Вы подключите новый Редактор Компонент в среду Delphi, а это делается в пункте меню "Options|Install Components", создайте новый проект, положите на форму объект TButton и щелкните дважды на нем - появится диалог:

После того, как Вы нажмете "OK", текст на кнопке изменится.

Созданный нами Редактор Компонент заместит Редактор по умолчанию для всех объектов класса TButton и его наследников, например, TBitBtn.

Полный текст Редактора Компонент приведен в файле SBEDIT.PAS в примерах к данному уроку.

Назад | Содержание | Вперед

 

VPS/VDS серверы. 30 локаций на выбор

Серверы VPS/VDS с большим диском

Хорошие условия для реселлеров

4VPS.SU - VPS в 17-ти странах

2Gbit/s безлимит

Современное железо!

Бесплатный конструктор сайтов и Landing Page

Хостинг с DDoS защитой от 2.5$ + Бесплатный SSL и Домен

SSD VPS в Нидерландах под различные задачи от 2.6$

✅ Дешевый VPS-хостинг на AMD EPYC: 1vCore, 3GB DDR4, 15GB NVMe всего за €3,50!

🔥 Anti-DDoS защита 12 Тбит/с!

Новости мира IT:

Архив новостей

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 495 7861149
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2019 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...