Перечисление всех компонентов на форме

Сегодня рассмотрим вопрос о том, как перечислить все компоненты на форме. Например, как очистить все TEdit одним циклом, как изменить надписи на всех TLabel, как нажать все TButton :)
В заметке про создание кнопок с использованием TImage я уже испольовал данный приём, теперь рассмотрим подробнее.

0. Матчасть

У объекта TForm (а также других, на которых можно поставить, например, кнопку) есть массив Components. Очевидно, так мы можем обращаться ко всем объектам на форме.
Количество объектов — свойство ComponentCount. Первый объект имеет индекс 0, последний ComponentCount-1.

1. Просто перечислим имена компонентов

Запишем в Memo, другой наглядной реализации в голову не пришло

var
  i:integer;
begin
  for i:=0 to ComponentCount-1 do
    Memo1.Lines.Add(Components[i].Name);
end;

Теперь в Memo1 у нас имена всех компонентов.

2. Конкретизируем задачу

Будем использовать операторы is и as, тут без них никак:

var
  i:integer;
begin
  for i:=0 to ComponentCount-1 do
    if Components[i] is TLabel then
	  Memo1.Lines.Add((Components[i] as TLabel).Caption);
end;

Вот так просто мы получили все надписи на объектах TLabel. Теперь давай сделаем их невидимыми — пусть никто не прочитает :)

var
  i:integer;
begin
  for i:=0 to ComponentCount-1 do
    if Components[i] is TLabel then
	  (Components[i] as TLabel).Visible:=False;
end;

3. Итог

Теперь мы крутые, и нам не нужно прописывать:

Label1.Visible:=False;
Label2.Visible:=False;
...

Наконец, хочу сделать картинку к этому посту в моём delphi блоге. Я поставил на форму в случайном порядке несколько TLabel и TEdit, поставил кнопку, по нажатию на которую написал:

procedure TForm1.Button1Click(Sender: TObject);
var
  i:integer;
begin
  for i:=0 to ComponentCount-1 do
    if Components[i] is TLabel then
	    (Components[i] as TLabel).Caption:='http://parsers.info'
    else if Components[i] is TEdit then
      (Components[i] as TEdit).Text:='crystalbit';
end;

Запустил, нажал на неё. А результат смотри в начале этого поста :)

5. Замечание

Если используешь фреймы на форме, то этот механизм может не сработать.
Спасибо Алексею Тимохину

6. Вариант с рекурсией от JayDi

Подходит для фреймов

procedure FillChildComponentsList(const SourceComponent: TComponent;  
  var ChildsList: TList);  
var  
  I: Integer;  
begin  
  //рекурсивный поиск дочерних компонентов  
  for I := 0 to SourceComponent.ComponentCount - 1 do  
  begin  
    ChildsList.Add(SourceComponent.Components[I]);  
    FillChildComponentsList(SourceComponent.Components[I], ChildsList);  
  end;  
end;  
  
procedure Tf_TestFrameComponents.Button_TestComponentsClick(Sender: TObject);  
var  
  FoundedComponentsList: TList;  
  I: Integer;  
  Comp: TComponent;  
begin  
  //получение списка компонетов  
  FoundedComponentsList := TList.Create;  
  FillChildComponentsList(Self, FoundedComponentsList);  
  
  //смена текста у всех лейблов  
  for I := 0 to FoundedComponentsList.Count - 1 do  
  begin  
    Comp := FoundedComponentsList[I];  
    if Comp is TLabel then  
    begin  
      (Comp as TLabel).Caption := 'Its work!';  
    end;  
  end;  
  
  FreeAndNil(FoundedComponentsList);  
end;  

(c) crystalbit, http://parsers.info
а еще на блоге есть rss-лента!

P.S. можно писать посты в блог, можно пытаться сделать ссылку во флеш, в любом случае надо усердно работать.

12 ответов к «Перечисление всех компонентов на форме»

  1. Имхо, такой обход всё же лучше делать рекурсивным.
    Ведь, если на форме фрейм, на котором лежат компоненты, то такой код не тронет то, что на фрейме.
    + он также не сработает если компоненты создавались вручную в коде и у них Owner не форма.

  2. Согласен, спасибо
    А если компонент создавался динамически, и owner не форма (и не на форме), то как быть? И как такое может быть, если это визуальный компонент?

  3. > А если компонент создавался динамически, и owner не форма (и не на форме), то как быть? И как такое может быть, если это визуальный компонент?

    Если компонент создаётся например так:
    var
    tmpEdit: TEdit;
    begin
    tmpEdit:=TEdit.Create(Panel1); // это не корректно, но возможно
    tmpEdit.Parent := Panel1;
    end;

      1. Panel1 — на форме, а tmpEdit на панели. Поэтому перебирая компоненты формы, мы не получим доступ к tmpEdit. Но реально — такая ситуация очень редка и скорее является извращённой.
        Чаще же всего в реале с ней можно столкнуться только при использовании фреймов.

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

        1. всё понял
          думал, что при создании, например, TLabel на TPanel в процессе дизайна, она также не будет в массиве Components, и что рекурсия — единственный выход
          Теперь проверил и убедился, сейчас внесу изменения

  4. Вот рабочий вариант с рекурсией (подходит для фреймов):

    procedure FillChildComponentsList(const SourceComponent: TComponent;
      var ChildsList: TList);
    var
      I: Integer;
    begin
      //рекурсивный поиск дочерних компонентов
      for I := 0 to SourceComponent.ComponentCount - 1 do
      begin
        ChildsList.Add(SourceComponent.Components[I]);
        FillChildComponentsList(SourceComponent.Components[I], ChildsList);
      end;
    end;
    
    procedure Tf_TestFrameComponents.Button_TestComponentsClick(Sender: TObject);
    var
      FoundedComponentsList: TList;
      I: Integer;
      Comp: TComponent;
    begin
      //получение списка компонетов
      FoundedComponentsList := TList.Create;
      FillChildComponentsList(Self, FoundedComponentsList);
    
      //смена текста у всех лейблов
      for I := 0 to FoundedComponentsList.Count - 1 do
      begin
        Comp := FoundedComponentsList[I];
        if Comp is TLabel then
        begin
          (Comp as TLabel).Caption := 'Its work!';
        end;
      end;
    
      FreeAndNil(FoundedComponentsList);
    end;
    
    1. Параметр ChildsList: TList лучше объявить как const, чтобы случайно не изменить указатель.
      Также хорошо бы использовать try .. finally.

      procedure FillChildComponentsList(const SourceComponent: TComponent;
       const ChildsList: TList);
      var
        I: Integer;
      begin
        //рекурсивный поиск дочерних компонентов
        for I := 0 to SourceComponent.ComponentCount - 1 do
        begin
          ChildsList.Add(SourceComponent.Components[I]);
          FillChildComponentsList(SourceComponent.Components[I], ChildsList);
        end;
      end;
      
      procedure Tf_TestFrameComponents.Button_TestComponentsClick(Sender: TObject);
      var
        FoundedComponentsList: TList;
        I: Integer;
        Comp: TComponent;
      begin
        //получение списка компонетов
        FoundedComponentsList := TList.Create;
        try 
          FillChildComponentsList(Self, FoundedComponentsList);
      
          //смена текста у всех лейблов
          for I := 0 to FoundedComponentsList.Count - 1 do
          begin
            Comp := FoundedComponentsList[I];
            if Comp is TLabel then
            begin
              (Comp as TLabel).Caption := 'Its work!';
            end;
          end;
        
        finally
          FreeAndNil(FoundedComponentsList);
        end;
      end;
      

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *