СТАТЬЯ 12.09.01

Предыдущая часть

Профессиональная разработка приложений с помощью Delphi5

Часть 2. Создание компонентов Delphi

Сергей Трепалин,
УКЦ Interface Ltd.
КомпьютерПресс #2 2001

Статья была опубликована в КомпьютерПресс (www.cpress.ru)

Редакторы свойств и редакторы компонентов

Все, о чем рассказывалось в предыдущих разделах, относится к созданию кода приложения, которое будет распространяться для пользователей. Однако среда разработки Delphi позволяет модифицировать саму себя. Для этого не требуется знаний специального языка, поскольку все методы для изменения среды разработки пишутся на Delphi. Здесь эти методы, а именно редакторы свойств и редакторы компонентов, рассмотрены частично ‑ в плане создания инструментов для работы с компонентами. При чтении материалов данного раздела следует четко понимать, что конечный пользователь, работающий с вашим приложением, никогда не увидит ни редактора свойств, ни редактора компонентов – они создаются для программистов и работают только в среде разработки  Delphi.

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

Во время разработки приложения свойства отображаются в инспекторе объектов. Обратите внимание: свойства в инспекторе объектов редактируются по-разному. Некоторым свойствам (Width, Caption) можно определить только новое текстовое значение.  Свойство типа Cursor предоставляет раскрывающийся список, щелкнув по которому можно выбрать значение. Свойство типа TFont имеет знак «+» слева; при щелчке по нему оно разворачивается, давая возможность модифицировать отдельные поля. Кроме того, справа имеется кнопка с тремя точками (elliptic button), при щелчке на которой появляется диалог редактора свойств.

Каждое из вышеперечисленных свойств имеет свой редактор, и большим преимуществом среды разработки Delphi является возможность создать свои редакторы свойств. Новые редакторы свойств довольно часто встречаются среди распространяемых компонентов. Но к ним надо относиться осторожно: первоначально выполнить тесты на компьютере, где при необходимости можно повторно инсталлировать Delphi. Как правило, они создаются квалифицированными программистами и претензий к коду не бывает, но часто забывают включить в распространяемый редактор свойств какую-либо DLL. После инсталляции такого редактора мы получаем ряд свойств, которые невозможно редактировать, – старый редактор перекрыт, а новый не работает…

Перед созданием нового редактора свойств имеет смысл подумать, стоит ли это делать, – среди стандартных редакторов, вероятно, можно найти подходящий. Если же придется делать редактор свойств, необходимо соблюдать правило: следует избегать создания редакторов для стандартных типов данных (integer, string и др.). Другие программисты привыкли к стандартным редакторам, и ваш может им не понравиться. Следовательно, придется проявить скромность и регистрировать редактор для своего класса, а не для класса TComponent. Если ваш редактор свойств понравится программистам, большинство из них смогут сами изменить регистрацию так, чтобы редактор работал для всех компонентов. Вопрос регистрации редактора мы обсудим ниже.

Итак, поставим модельную задачу, для реализации которой необходимо будет реализовать редактор свойств. Предположим, какой-либо компонент имеет свойство ‑ день недели. В принципе, для ввода дня недели можно воспользоваться стандартным редактором с выпадающим списком. Однако мы хотим, чтобы программист на этапе разработки мог вводить день недели, задавая либо его порядковый номер (1 ‑ понедельник, 2 ‑ вторник и т.д.), либо текст на национальном или английском языке. При вводе текста допускается смешение прописные и строчные букв.

Прежде всего необходимо создать компонент, в котором будет храниться день недели. Создадим новый компонент вызовом команды Component/New component. В качестве класса-предка выберем TComponent и дадим новому классу имя TDayStore. После этого установим компонент в палитру. Теперь надо решить, в каком виде хранить день недели. Ясно, что для однозначной идентификации и экономии ресурсов его следует хранить в виде целого числа с допустимыми диапазонами 1‑7. Однако, если мы собрались создавать редактор свойств, следует вспомнить правило о несоздании новых редакторов для уже имеющихся типов. Поэтому определим новый тип – TDayWeek, причем все операции с ним будем производить как с целыми числами. Определим переменную FDay в секции private компонента. Поскольку эта переменная будет инициализироваться значением 0 при отработке конструктора по умолчанию, а  это число находится за пределами допустимых значений, необходимо переписать конструктор. В заключение определим свойство DayWeek в секции published для отображения его в инспекторе объектов. Окончательный вариант компонента выглядит следующим образом:

type 
  TDayWeek=type integer;  
   
  TDayStore = class(TComponent)  
  private  
    { Private declarations }  
    FDay:TDayWeek;  
  protected  
    { Protected declarations }  
  public  
    { Public declarations }  
    constructor Create(AOwner:TComponent); override;  
  published  
    { Published declarations }  
    property DayWeek:TDayWeek read FDay write FDay;  
  end;  
…  
implementation  
   
constructor TDayStore.Create(AOwner:TComponent);  
begin  
  inherited Create(Aowner);  
  FDay:=1;  
end;  

Следует обратить внимание на редкую конструкцию определения нового типа

TDayWeek=type integer;   

Таким образом, вводится новый тип данных, который имеет тот же размер, что и тип integer, все операции над этим типом данных осуществляются как с целыми числами. Смысл этой операции – объявить новый тип данных, чтобы наш редактор свойств был применим именно к нему и не затрагивал другие типы данных.

Теперь создадим редактор свойства TDayWeek. Для этого к имеющемуся проекту добавим новую форму, запомним ее под каким-либо подходящим именем (DayPropE.pas) и исключим из проекта. После этого откроем форму как отдельный файл и будем реализовывать в ней редактор свойств. На первом этапе форма нам не понадобится, но позднее мы реализуем на ней диалог.

Модуль для создания редакторов свойств называется DsgnIntf.pas (Design Interface), в нем определены базовый класс TPropertyEditor и классы-потомки, предназначенные для редакции стандартных свойств – TIntegerProperty, TFloatProperty, TStringProperty и др. Механизм работы редакторов свойств заключается в следующем:

  1. Он регистрируется в среде разработки Delphi вызовом метода RegisterPropertyEditor. В качестве параметров этот метод принимает следующие значения:

    a) информация о типе свойств, для редакции которых предназначен данный редактор. Из-за наличия этой информации нам пришлось определять новый тип TDayWeek;

    b) информация о компоненте, в котором применим данный редактор. Редактор будет вызываться не только для указанного компонента, но и для всех его потомков. Если установить это значение TComponent, редактор будет вызываться для любого компонента;

    c) имя свойства, для которого используется данный редактор. Если имя – пустая строка, используются два вышеупомянутых фильтра;

    d) ссылка на сам класс, описывающий новый редактор свойств, – среда разработки должна знать, что вызывать.

  2. Вызывается метод GetValue, когда необходимо считать текущее значение свойства из компонента. Этот метод для любого свойства возвращает строку, которая помещается в инспекторе объектов.
  3. Вызывается метод SetValue, когда программист ввел новое значение свойства в инспекторе объектов. В качестве параметра передается новая строка. В методе она должна быть проанализирована и приведена к типу редактируемого свойства.

Методы GetValue и SetValue являются виртуальными, при их переписывании создаются новые редакторы свойств. Итак, теперь можно начать создание нового редактора свойств.

Сошлемся в секции uses модуля DayPropE.pas на модуль DsgnIntf и определим в секции Interface новый класс:

type 
  TDWPropED=class(TPropertyEditor)  
  public  
    function GetValue:string; override;  
    procedure SetValue(const Value:string); override;  
  end;  

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

const 
  DayWeek:array[1..7] of string = ('Понедельник', 'Вторник', 'Среда', 'Четверг',
                                                          'Пятница', 'Суббота', 'Воскресенье');  
  DayWeekEn:array[1..7] of string = ('Monday', 'Tuesday', 'Wednesday', 'Thursday',
                                                              'Friday', 'Saturday', 'Sunday');  
   
function TDWPropED.GetValue:string;  
begin  
  Result:=DayWeek[GetOrdValue];  
end;  
   
procedure TDWPropED.SetValue(const Value:string);  
var  
  I,N:integer;  
begin  
  I:=1;  
  {Checking if programmer has entered name of the day of week
    with national language}  
  while (ANSICompareText(DayWeek[I],Value)<>0) and (I<8) do Inc(I);  
   
  {Checking if programmer has entered name of the day of week
    with English language}  
  if I>7 then begin  
    I:=1;   
    while (ANSICompareText(DayWeek[I],Value)<>0) and (I<8) do Inc(I);  
  end;  
  if I<8 then begin  
    SetOrdValue(I);  
    Exit;  
  end;  
   
  {Checking if programmer has entered order of the day of week}  
  Val(Value,N,I);  
  if (N>0) and (N<=7) and (I=0) then begin  
    SetOrdValue(N);  
    Exit;  
  end;  
   
  {Inform Delphi, that bad value  was entered so that restore previous value}  
  raise Exception.Create(Format('Bad day of week %s',[Value]));  
end;  

Внутри редактора свойств TPropertyEditor определен метод GetOrdValue, который извлекает текущее значение свойства из компонента, если оно может быть описано как ординарный тип (то есть тип, позволяющий упорядочивать переменные, к нему относящиеся). В методе GetValue вызывается этот метод, но в качестве строки возвращается название дня недели, извлеченное из массива. Это название и попадает в инспектор объектов.

Реализация метода SetValue выглядит сложнее, поскольку поставленная задача достаточно сложна. Первоначально проверяется, действительно ли новый текст, набранный в инспекторе объектов и передаваемый в данный метод через параметр Value, представляет собой название дня недели. Поскольку название дня недели вводится национальным алфавитом, для сравнения строк используется метод ANSICompareText. Этот метод сравнивает строки без учета прописных и строчных букв, причем для конвертации строчных букв в прописные используется текущий языковой драйвер. Затем, если Value не обнаружено в массиве русских названий дней недели, поиск осуществляется по английским названиям. Если название совпало, то вызывается метод SetOrdValue класса TPropertyEditor. Этот метод позволяет изменить текущее значение ординарного свойства в компоненте. Если же текстовое название дня недели не было найдено ни в одном из языков, то проверяется, был ли введен порядковый номер дня недели, который может быть целым числом в диапазоне от 1 до 7. Для этого используется метод val, который пытается конвертировать строку в целое (или действительное) число и возвращает код ошибки. Если же значение параметра Value не совпадает ни с одним из предопределенных значений, значит программист ошибся при вводе нового значения свойства. Об этом необходимо информировать среду разработки, что осуществляется генерацией исключения.

Созданный таким образом редактор свойств останется неработоспособным, пока не будет зарегистрирован в среде разработки. Как уже говорилось, это достигается при помощи метода RegisterPropertyEditor, который обязана вызвать среда разработки Delphi. Метод, вызываемый средой разработки, – процедура Register при регистрации компонента. Поэтому в модуле DayStore (где реализован компонент TDayStore) в секции implementation ссылаемся на модули DsgnIntf и DayPropE. Обратите внимание, что ссылка должна находиться в секции implementation – нам скоро потребуется циклическая ссылка! И теперь добавляем один метод к процедуре Register в модуле DayStore:

procedure Register; 
begin  
  RegisterComponents('Samples', [TDayStore]);  
  RegisterPropertyEditor(TypeInfo(TDayWeek),TDayStore,'',TDWPropEd);  
end;  

Смысл параметров метода RegisterPropertyEditor был объяснен ранее. Теперь, поскольку мы изменили метод Register, необходимо повторно инсталлировать компонент: удалить его в редакторе пакета и перекомпилировать пакет, затем вновь добавить и снова перекомпилировать. Подробно о повторной регистрации было рассказано в разделе «Создание простейшего компонента».

Теперь можно тестировать редактор свойств. Для этого помещаем компонент TDayStore на форму и выделяем его. Сразу же видно отличие свойства DayWeek от целочисленных свойств: вместо числа в инспекторе объектов отображается название дня недели.

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

Теперь немного модифицируем редактор свойств так, чтобы он мог показывать диалог с названиями дней недели для выбора. Если редактор свойств использует диалог, то в инспекторе объектов, при выделении соответствующего свойства, справа появляется кнопка с тремя точками (elliptic button). При нажатии этой кнопки выполняется диалог. Для информирования среды разработки о необходимости редактировать свойства специальным образом следует переписать метод  GetAttributes. Кроме того, для диалога необходимо переписать метод Edit. Он будет вызываться, когда программист нажмет кнопку с тремя точками. Итак, в секции public класса TDWPropED определяем заголовки методов:

function GetAttributes:TPropertyAttributes; override; 
procedure Edit; override;  

и в секции реализации реализуем метод GetAttributes:

function TDWPropEd.GetAttributes:TPropertyAttributes;  
begin  
  Result:=[paDialog];  
end;  

Возвращаемый результат – набор флагов, которые информируют среду разработки о том, каким способом редактировать данное свойство. Компилятор пропускает любое сочетание флагов, но не любое сочетание является работоспособным. Например, если выставить одновременно флаги, что требует и список, и диалог, то в инспекторе объектов кнопка, которая заставляет выпадать список, спрячет кнопку диалога и добраться до нее (и, следовательно, вызвать диалог) будет невозможно. Возможные значения флагов и описание методов, которые необходимо переписывать, приведены ниже:

PaValueList — редактор свойств использует выпадающий список, где предлагаются для выбора значения свойства. При этом рядом со свойством появляется кнопка – «стрелка вниз». Необходимо переписать метод GetValues, в котором даются все возможные значения свойств.

PaSortList — то же, что и предыдущее, но выпадающий список будет отсортирован.

PaSubProperties — используется для редакции записей, в частности классов. Слева от имени свойства появляется знак «+», при нажатии на который разворачивается список внутренних свойств. Необходимо переписать метод GetProperties, в котором перечисляются все внутренние свойства.

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

PaMultiSelect — список, из которого можно выбрать несколько значений (флагов). Переписывается метод GetValues.

PaAutoUpdate — информация о том, что метод SetValue необходимо вызывать автоматически каждый раз, когда происходит изменение данного свойства в инспекторе объектов. Пример – редактор заголовка форм (свойство Caption). Методы переписывать не надо.

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

Для реализации метода Edit поставим на форму в модуле DayPropE.pas компонент TListBox и в его свойстве Items определим дни недели. Также поставим две кнопки TBitBtn, свойство Kind одной определим как bkOK, а другой – bkCancel. Далее реализуем метод Edit:

procedure TDWPropEd.Edit;  
var  
  F2:TForm2;  
begin  
  F2:=nil;  
  try  
    F2:=TForm2.Create(nil);  
    F2.ListBox1.ItemIndex:=GetOrdValue-1; {Index in the ListBox is started from zero}  
    if F2.ShowModal=mrOK then SetOrdValue(F2.ListBox1.ItemIndex+1);  
  finally  
    if Assigned(F2) then F2.Release;  
  end;  
end;  

Форма создается с nil-владельцем, поэтому для гарантированного вызова деструктора ее создание и разрушение помещены в защищенный блок. Нумерация массива в TListBox начинается с нуля, поэтому используются операторы +1 и –1 при чтении/записи в TListBox. Остальная часть кода тривиальна.

После изменений необходимо заново регистрировать компонент в палитре, чтобы заново была выполнена процедура RegisterPropertyEditor. Как это сделать, было описано ранее. После любых изменений в исходных кодах редактора свойств необходима повторная регистрация для тестирования. Теперь можно поместить на форму компонент TDayStore и убедиться, что при выборе свойства DayWeek в инспекторе объектов появляется кнопка с тремя точками, нажав на которую можно вызвать созданный нами диалог для редакции свойства.

Аналогично можно использовать другие возможности среды разработки. Ниже приведена реализация методов GetAttributes и  GetValues для редактирования дней недели в виде выпадающего списка:

function TDWPropEd.GetAttributes:TPropertyAttributes;  
begin  
  Result:=[paValueList];  
end;  
   
procedure TDWPropEd.GetValues(Proc:TGetStrProc);  
var  
  I:integer;  
begin  
  for I:=1 to 7 do Proc(DayWeek[I]);  
end;  

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

Продолжение статьи

Дополнительную информацию Вы можете получить в компании Interface Ltd.

Отправить ссылку на страницу по e-mail
Обсудить на форуме Inprise/Borland


Interface Ltd.
Тel/Fax: +7(095) 105-0049 (многоканальный)
Отправить E-Mail
http://www.interface.ru
Ваши замечания и предложения отправляйте автору
По техническим вопросам обращайтесь к вебмастеру
Документ опубликован: 12.09.01