Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4495.html
 

Установка свойства для нескольких объектов (RTTI#5)

Опубликовано 31.10.2011 г. 20:40

А у вас никогда не было желания написать что-нибудь вроде for c in [button1, button2, edit3] do c.enabled := false? Иногда, следуя логике поведения программы, нам необходимо, например, скрывать ряд компонентов, или делать их неактивными.

Такие ситуации могут встречаться достаточно часто, например, в режиме редактирования одни элементы активны, в противном случае нет. Либо какой-нибудь демо-режим программы, где вам нужно отключить некоторые функциональные возможности. Если элементы имеют одного родителя, то можно отключать его, но часто приходится писать код вида (не обращайте внимание на названия элементов):
button1.enabled := false;
button2.enabled := false;
edit1.enabled := false;
ComboBox5.enabled := false;
А затем, при наступлении какого то события, включать их обратно. Конечно, такие ситуации не так уж и часты, но согласитесь, встречаются. И каждый раз когда нужно одно временно отключить хотя бы два элемента, мне приходит в голову одна мысль: "ну почему же нельзя написать код вида.."
for c in [button1, button2, edit1, comboBox5] do c.enabled := false; 
Свойство enabled приведено для примера, хотя, наверное, все таки enabled & visible наиболее частые для подобных ситуаций. И пару дней назад, когда я ковырялся с утилитой настройки Outlook Social-провайдера для ВКонтакте, мне вдруг вспомнилась эта задача. Ведь на самом деле решить ее можно весьма просто, и механизм RTTI позволяет нам это сделать с наименьшими усилиями. Сразу родились 3 идеи решения, каждая из которых являлась улучшением предыдущей. Третья идея к сожалению не осуществилась, но и второй вариант на мой взгляд неплох. Следуя принципам ООП функционал для работы я заключил в запись TMultiProp, хотя можно было обойтись одной отдельной функцией (ибо класс имеет только 1 метод, да и в записи при вызове это было бы короче). Сразу напишу конечный результат. Вот пример обработчика нажатия кнопки, который отключает перечень других элементов формы, устанавливая в false значение свойства enabled.
procedure TForm1.Button1Click(Sender: TObject);
begin
    TMultiProp.Objects([button1, button2, label1, comboBox1, RadioButton1] )['enabled'] := false;
end;
К минусам можно отнести то, что имя свойства указывается в виде строки, что может привести к ошибкам (правда легко выявляемым), но имя свойства остается регистро-независимо. Из представленного фрагмента кода можно сделать следующие выводы:
  1. Работа ведется с помощью структуры TMultiProp
  2. Данная структура имеет классовый метод Objects, название метода предполагает, что метод получает список объектов
  3. Параметром метода является открытый массив объектов
  4. Метод возвращает некоторую структуру, имеющую свойство по умолчанию в виде свойства-массива, с индексом строкой (именем устанавливаемого свойства объектов)
  5. значение свойства - TValue (раз уж мы упомянули о RTTI)
  6. Раз уж основной класс у нас только один, то возвращаемая структура является вложенным классом
Вложенная структура имеет имя TObjetProperty и имеет свойство Properties[], используемое по умолчанию. Посмотрим на объявление записи:
    TMultiProp = record
      strict private
        class var FData : array of TObject;
      public
        type
            TObjectProperty = record
              strict private
                procedure setValue(propName : string; value : TValue);
              public
                property Properties[propName :string ] :TValue write setValue;  default;
            end;

        class function Objects(objects : array of TObject) : TObjectProperty; static;
    end;
Метод Objects() копирует массив переданных указателей на объекты во внутреннюю переменную FData и возвращает структуру TObjectProperty. Дочерняя структура в свою очередь имеет доступ к private полям своего "родителя" (свойства и методы TObjectProperty тоже можно было определить как классовые).
class function TMultiProp.Objects(objects: array of TObject): TObjectProperty;
var i : integer;
begin
    setLength(FData, length(objects));
    for i := 0 to high(objects) do
        FData[i] := objects[i];
end;
Теперь дело за установкой значения свойств объектов с использованием RTTI. Сложного здесь ничего нет, для каждого объекта из переданного списка получить RTTI-описание его типа с помощью RTTI-контекста. Затем получить список свойств объекта, выбрать нужное, и установить его для выбранного экземпляра объекта.
procedure TMultiProp.TObjectProperty.setValue(propName: string; value: TValue);
var ctx : TRttiContext;
    t : TRttiType;
    p : TRttiProperty;
    obj : TObject;
begin
    ctx := TRttiContext.Create();
    propName := LowerCase(propName);
    try
        for obj in FData do begin
            t := ctx.GetType(obj.ClassType);
            if t = nil then continue;

            for p in t.GetProperties() do begin
                if LowerCase(p.Name) <> propName then continue;

                p.SetValue(obj, value);
            end;
        end;
    finally
        ctx.Free();
        FData := nil;
    end;
end;
В конце мы финализируем созданный ранее массив указателей на объекты. Конечно, в случае, если объекты (компоненты) не содержат информации об RTTI, такой подход работать не будет, а для использования такого класса вам понадобится Delphi 2010 и выше.
Метки:  rtti 

Комментарии

Oleg
31.10.2011 в 22:53

procedure EnableControls(IsEnabled : Boolean; Controls : array of TComponent);
var i : integer;
begin
for i:= 0 to High(Controls) do begin
(Controls[i] as TControl).Enabled := IsEnabled;
end;
end;


пример вызова:

EnableControls(true, [CheckBox1,TBXToolbar1,Label1,Button1,Button2]);


работает в д2007, а скорей всего и в более ранних.
ter
02.11.2011 в 17:25
работает. но это если все рассматривать пример с 3мя компонентами и свойством enabled.
если мы захотим изменить свойство Caption для TButton & TLabel то уже не будет.
Sf
31.10.2011 в 22:31
Написание свойства enabled не проверяется кмпилятором, грабли очевидны.
ter
02.11.2011 в 17:07
ну этот нюанс я в статье упомянул (:
для данного свойства имхо тестирование весьма просто (:
IL
01.11.2011 в 02:16
Отличная идея и реализация. Кажется, вызовы TRttiContext.Create/Free не нужны, да?
Наверное, можно также сделать сохранение/восстановление значений свойств контролов в массиве TValue:
SaveText:= TMultiProp.Save([edit1, edit2, combobox1, maskedit1] , 'text');
TMultiProp.Restore([edit1, edit2, combobox1, maskedit1], 'text', SaveText);
ter
02.11.2011 в 17:40
согласен, мысль интересная (:

Create/Free освобождают все объекты созданные при работе с RTTI, например getPropertries() создает массив объектов TRttiProperties, и разрушаются они при вызове Free контекста.
Keeper
01.11.2011 в 03:16
Супер! )
ter
02.11.2011 в 18:52
(:
Jack128
02.11.2011 в 12:46
// «ну почему же нельзя написать код вида..»
// for c in [button1, button2, edit1, comboBox5] do c.enabled := false;
а почему нельзя?? enabled/visible вроде в TControl объявлены?
Если да, то можно написать так:
for c in TArray.Create(button1, button2, edit1, comboBox5) do
c.Enabled := False;
ter
02.11.2011 в 18:05
как уже писал выше, caption для TLabel & TButton так не установить, а enabled - частный случай.
а тут можно любое свойство для любого наследника TObject.

зы: для массива еще в конец free нужен.
Jet
02.11.2011 в 16:28
Можно проще:
type
  TControlArray = array of TControl;
var
  C: TControl;
begin
  for C in TControlArray.Create(Button1, Edit1, Memo1) do
    C.Enabled := False;
end;

Либо без объявления типа, но с дженериками:
var
  C: TControl;
begin
  for C in TArray.Create(Button1, Edit1, Memo1) do
    C.Enabled := False;
end;
ter
02.11.2011 в 18:01
ответ в предыдущем комменте ибо он такой же (:
Всеволод Леонов
03.11.2011 в 10:40
Пост отличный, +10 за великолепный синтаксис :)

Но хочется большего.
TMultiProp.Objects([button1, button2, label1, comboBox1, RadioButton1] )['enabled'] := false;

Каков принцип помещения компонента в список? А также механизм?
На уровне ощущений - в данном RTTI-подходе есть нечто большее, чем "компактирование" записи.
ter
03.11.2011 в 10:30
псиб (:

ну можно подумать как можно что то подобное использовать для более практических целей, но чето хз (:
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно