Перечисление всех компонентов на форме
Сегодня рассмотрим вопрос о том, как перечислить все компоненты на форме. Например, как очистить все 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. можно писать посты в блог, можно пытаться сделать ссылку во флеш, в любом случае надо усердно работать.
Имхо, такой обход всё же лучше делать рекурсивным.
Ведь, если на форме фрейм, на котором лежат компоненты, то такой код не тронет то, что на фрейме.
+ он также не сработает если компоненты создавались вручную в коде и у них Owner не форма.
Согласен, спасибо
А если компонент создавался динамически, и owner не форма (и не на форме), то как быть? И как такое может быть, если это визуальный компонент?
> А если компонент создавался динамически, и owner не форма (и не на форме), то как быть? И как такое может быть, если это визуальный компонент?
Если компонент создаётся например так:
var
tmpEdit: TEdit;
begin
tmpEdit:=TEdit.Create(Panel1); // это не корректно, но возможно
tmpEdit.Parent := Panel1;
end;
это да, Panel1 же на форме
Получается, рекурсией — наиболее полноценный алгоритм? С твоего позволения напишу пост об этом)
Panel1 — на форме, а tmpEdit на панели. Поэтому перебирая компоненты формы, мы не получим доступ к tmpEdit. Но реально — такая ситуация очень редка и скорее является извращённой.
Чаще же всего в реале с ней можно столкнуться только при использовании фреймов.
Не знаю, что там может быть на целый пост, Имхо, достаточно в этом посте указать, что для фреймов, этот механизм может не сработать.
всё понял
думал, что при создании, например, TLabel на TPanel в процессе дизайна, она также не будет в массиве Components, и что рекурсия — единственный выход
Теперь проверил и убедился, сейчас внесу изменения
Вот рабочий вариант с рекурсией (подходит для фреймов):
большое спасибо, внёс в пост
Параметр ChildsList: TList лучше объявить как const, чтобы случайно не изменить указатель.
Также хорошо бы использовать try .. finally.
Как здорово, что все мы здесь сегодня собрались :)
Спасибо, с праздниками!
Очень полезная возможность.
Делал примерно таким образом перевод интерфейса.
Хорошее применение :)