Собсно вопрос:
На форме имеется несколько компонентов типа, например TEdit, с именами Edit1, Edit2, Edit3 и т.д. Как можно обратиться к каждому компоненту поочередно в цикле for i:=1 to ... do чтобы изменить какое либо его свойство?
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
02-07-2009 12:22
02-07-2009 08:16
Но вот если вы вдруг захотите переименовать компонент... Ведь при компиляции строчки FindComponent('Edit1') ошибки не возникнет, хотя самого Edit1 в помине уже и нет...
Согласен, но на то и есть программист чтобы сначала написать, а потом отлаживать программу)
проверку имени компонента
Но вот если вы вдруг захотите переименовать компонент... Ведь при компиляции строчки FindComponent('Edit1') ошибки не возникнет, хотя самого Edit1 в помине уже и нет...
В примере приведенном уважаемым Sphinxs не учитывается что на форме могут быть компоненты типа TEdit, которые не нужно обрабатывать. Т.е. чтобы пользоваться таким примером нужно добавлять проверку имени компонента. Меня лично устраивает и FindComponent.
>>> При этом вы сами должны следить за уникальностью этого имени, так как VCL не запрещает различным компонентам иметь одно и то же значение свойства Name, даже если эти компоненты имеют одного владельца.
Уточнение:
Компоненты, принадлежащие одному владельцу, не могут иметь одинаковых имён, за исключением случая, когда эти имена пустые. Если при создании компонента не указывать владельца, т.е. писать Create(nil), то за неимением владельца против дублирования имён никто и не возражает.
Привожу работающий пример, чтобы все желающие могли поиграться.
procedure TForm1.FormCreate(Sender: TObject);
begin
ShowLists;
end;
function TForm1.NextTop: Integer;
begin
Inc(FLastTop, 25);
Result := FLastTop;
end;
procedure TForm1.ShowLists;
var
i: Integer;
begin
{Компоненты формы}
lbComponents.Items.Clear;
for i := 0 to ComponentCount-1 do
lbComponents.Items.Add(Format('[%.2d] - "%s"', [i, Components[i].Name]));
{Контролы панели}
lbControls.Items.Clear;
for i := 0 to Panel1.ControlCount-1 do
lbControls.Items.Add(Format('[%.2d] - "%s"', [i, Panel1.Controls[i].Name]));
end;
{ Кнопки }
procedure TForm1.Button1Click(Sender: TObject);
begin
try
with TEdit.Create(Self) do // Owner = Self
begin
Left := 10;
Top := NextTop;
Parent := Panel1; // Сначала Parent
Name := edtName.Text; // Потом Name
end;
finally
ShowLists;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
try
with TEdit.Create(nil) do // Owner = nil
begin
Left := 10;
Top := NextTop;
Parent := Panel1; // Сначала Parent
Name := edtName.Text; // Потом Name
end;
finally
ShowLists;
end;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
try
with TEdit.Create(Self) do // Owner = Self
begin
Left := 10;
Top := NextTop;
Name := edtName.Text; // Сначала Name
Parent := Panel1; // Потом Parent
end;
finally
ShowLists;
end;
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
try
with TEdit.Create(nil) do // Owner = nil
begin
Left := 10;
Top := NextTop;
Name := edtName.Text; // Сначала Name
Parent := Panel1; // Потом Parent
end;
finally
ShowLists;
end;
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
try
with TEdit.Create(Self) do // Owner = Self
begin
Left := 10;
Top := NextTop;
Parent := Panel1; // Parent
// Name := edtName.Text; // Name не задаём
end;
finally
ShowLists;
end;
end;
procedure TForm1.Button6Click(Sender: TObject);
begin
try
with TEdit.Create(nil) do // Owner = nil
begin
Left := 10;
Top := NextTop;
Parent := Panel1; // Parent
// Name := edtName.Text; // Name не задаём
end;
finally
ShowLists;
end;
end;
end.
Unit1.dfm
object Form1: TForm1
Left = 0
Top = 0
BorderStyle = bsDialog
Caption = 'Form1'
ClientHeight = 588
ClientWidth = 788
Color = clBtnFace
Font.Charset = RUSSIAN_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Arial'
Font.Style = []
OldCreateOrder = False
Position = poDesktopCenter
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 16
object Label1: TLabel
Left = 8
Top = 448
Width = 34
Height = 16
Caption = 'Name'
end
object Label2: TLabel
Left = 435
Top = 8
Width = 123
Height = 16
Caption = 'Form1.Components'
Font.Charset = RUSSIAN_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Arial'
Font.Style = [fsBold]
ParentFont = False
end
object Label3: TLabel
Left = 600
Top = 8
Width = 100
Height = 16
Caption = 'Panel1.Controls'
Font.Charset = RUSSIAN_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Arial'
Font.Style = [fsBold]
ParentFont = False
end
object Label4: TLabel
Left = 8
Top = 487
Width = 103
Height = 16
Caption = #1057#1085#1072#1095#1072#1083#1072' Parent'
Font.Charset = RUSSIAN_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Arial'
Font.Style = [fsBold]
ParentFont = False
end
object Label5: TLabel
Left = 130
Top = 487
Width = 98
Height = 16
Caption = #1057#1085#1072#1095#1072#1083#1072' Name'
Font.Charset = RUSSIAN_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Arial'
Font.Style = [fsBold]
ParentFont = False
end
object Label6: TLabel
Left = 252
Top = 487
Width = 65
Height = 16
Caption = #1041#1077#1079' Name'
Font.Charset = RUSSIAN_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Arial'
Font.Style = [fsBold]
ParentFont = False
end
object Panel1: TPanel
Left = 8
Top = 8
Width = 375
Height = 418
Caption = 'Panel1'
TabOrder = 1
object Edit1: TEdit
Left = 328
Top = 390
Width = 41
Height = 19
TabOrder = 0
Text = 'Edit1'
end
end
object Button1: TButton
Left = 8
Top = 509
Width = 110
Height = 27
Caption = 'TEdit.Create(Self)'
TabOrder = 2
OnClick = Button1Click
end
object Button2: TButton
Left = 8
Top = 547
Width = 110
Height = 25
Caption = 'TEdit.Create(nil)'
TabOrder = 3
OnClick = Button2Click
end
object lbComponents: TListBox
Left = 435
Top = 30
Width = 146
Height = 542
ItemHeight = 16
TabOrder = 8
end
object lbControls: TListBox
Left = 600
Top = 30
Width = 151
Height = 542
ItemHeight = 16
TabOrder = 9
end
object edtName: TEdit
Left = 48
Top = 445
Width = 121
Height = 24
TabOrder = 0
Text = 'NewName'
end
object Edit2: TEdit
Left = 336
Top = 432
Width = 43
Height = 19
TabOrder = 10
Text = 'Edit2'
end
object Button3: TButton
Left = 130
Top = 509
Width = 110
Height = 27
Caption = 'TEdit.Create(Self)'
TabOrder = 4
OnClick = Button3Click
end
object Button4: TButton
Left = 130
Top = 547
Width = 110
Height = 25
Caption = 'TEdit.Create(nil)'
TabOrder = 5
OnClick = Button4Click
end
object Button5: TButton
Left = 252
Top = 509
Width = 110
Height = 27
Caption = 'TEdit.Create(Self)'
TabOrder = 6
OnClick = Button5Click
end
object Button6: TButton
Left = 252
Top = 547
Width = 110
Height = 25
Caption = 'TEdit.Create(nil)'
TabOrder = 7
OnClick = Button6Click
end
end
Спасибо за ответ. Идея использовать массив мне приходила самому, но не понравилась. Конкретно меня интересовало что-то типа использования FindComponent. Я видел такие примеры раньше, но забывал постоянно из-за нечастого программирования подобных задач. Для себя использовал это следующим образом:
for i:=1 to ... do
begin
(Form1.FindComponent('MemoV'+IntToStr(i)) as TMemo).Lines.Clear;
(Form1.FindComponent('MemoV'+IntToStr(i)) as TMemo).Lines.Append(...);
(Form1.FindComponent('CheckBox'+IntToStr(i)) as TCheckBox).Checked:=...;
end;
Спасибо за ответ. Надеюсь запомнить это теперь на долго).
Все компоненты (т.е. наследники TComponent), которые помещаются на форму, могут иметь своего владельца. Владельцем может быть любой другой компонент. Компонен-владелец указывается в качестве параметра конструктора вновь создаваемого компонента и, за исключением очень экзотических случаев, остаётся неизменным на протяжении всего времени жизни этого компонента. Когда компонент-владелец уничтожается, он авоматически уничтожает все компоненты, которыми владеет. Владельцем всех созданных на форме в design-time компонентов становится сама форма.
У каждого компонента есть свойство Name, в котором хранится имя компонента, присвоенное ему при создании. У компонентов, созданных в design-time, это имя совпадает с именем поля формы, которое создаётся средой для данного компонента. Но при этом надо помнить, что совпадение имени переменной и значения свойства Name - это не универсальный закон. Это правило выполняется только для компонентов, созданных в design-time, к которым обращаются через те поля, которые создала сама среда. А если, например, вы создаёте компонент в run-time, то его поле Name по умолчанию имеет пустое значение. Если вы хотите, чтобы такой компонент имел имя, вы должны присвоить значение полю Name вручную. При этом вы сами должны следить за уникальностью этого имени, так как VCL не запрещает различным компонентам иметь одно и то же значение свойства Name, даже если эти компоненты имеют одного владельца.
Для доступа к компонентам, которыми владеет компонент, существуют два свойства: ComponentCount типа Integer и индексированное свойство Components, которое возвращает владеемый компонент с заданным номером. Пройдя по списку Components от 0 до ComponentCount-1, мы можем получить ссылки на все компоненты, которыми он владеет.
Поиск компонента по имени автоматизирован: в классе TComponent существует функция FindComponent, которая возвращает ссылку на компонент с заданным именем из числа тех, которыми владеет данный компонент. Если компонентов с таким именем несколько, FindComponent
Вспоминая о том, что владельцем всех созданных в design-time компонентов является форма, получаем такой код для решения вашей задачи (этот код должен быть размещён в одном из методов формы; в противном случае обращение к методу FindComponent должно включать в себя ссылку на форму, для которой он вызывается):
var
MyEdit: TEdit;
...
for I := 1 to 10 do
begin
MyEdit := FindComponent('Edit' + IntToStr(I)) as TEdit;
// Теперь в MyEdit хранится ссылка на требуемый TEdit.
// Делаем с ним всё, что нужно
end;
Это очень схематичный код. Он не учитывает двух возможных ситуаций. Во-первых, компонента с заданным именем может вообще не существовать на форме (в этом случае FindComponent вернёт nil). Во-вторых, из-за ручных манипуляций с полем Name может оказаться, что имя вида 'Edit<N>' получит и ещё какой-нибудь компонент с типом, отличным от TEdit (в этом случае попытка прведения найденного компонента к типа TEdit при помощи as даст исключение). Чтобы этот код стал надёжным, надо вставить дополнительные проверки, отслеживающие данные ситуации. Я предлагаю вам следать это самостоятельно.
Метод FindComponent устроен внутри очень просто: он в цикле перебиает все элементы списка Components, пока не дойдёт до элемента с заданным именем. Понятно, что в приведённом ваше коде получается два цикла: внешний, написанный нами, и вложенный, который выполняется внутри FindComponent. Если на форме много компонентов, а искомые компоненты лежат в конце списка, это здорово снижает производительность. Поэтому FindComponent обычно применяют для поиска одиночного компонента, а не нескольких компонентов в цикле. Хочется обойтись одним циклом по массиву компонентов. Сделать это можно примерно так (чтобы этот пример откомпилировался, необходимо подключить модуль StrUtils):
var
MyEdit: TEdit;
EditNumber: Integer;
...
for I := 0 to ComponentCount - 1 do
if (Components[I] is TEdit) and
AnsiStartsStr('Edit', Components[I].Name) and
TryStrToInt(Copy(Components[I].Name, 5, MaxInt), EditNumber) and
(EditNumber >= 1) and (EditNumber <= 10) then
begin
MyEdit := Components[I] as TEdit; // Здесь дополнительные проверки не нужны, всё уже проверено
// Делаем с MyEdit всё, что нужно
end;
Слабое в смыле производительности место этого варианта - затратные операции со строками. Правда, если отключена опция компилятора Complete boolean eval (а по умолчанию она как раз отключена), эти операции будут выполняется не на каждом шаге цикла, а только в том случае, когда очередной компонент из списка - TEdit. Тем не менее, если на форме много TEdit'ов и практически нет других компонентов, данный способ может оказаться даже более затратным, чем первый, особенно если интересующие вас компоненты находятся близко к началу списка, и FindComponent отрабатывает быстро.
Как же обойтись без операций над строками? В этом нам может помочь ствойство Tag, которое есть у каждого компонента. Это целочисленное свойство, которое никак не используется стандартными классами. Сделано оно специально для того, чтобы вы могли записывать туда любое значение и использовать его по своему усмотрению. По умолчанию Tag=0. Записываем в design-time в свойства Tag интересующих вас TEdit'ов значения от 1 до 10 и следим, чтобы Tag никакого другого TEdit'а не получил значение из этого диапазона. Тогда можно использовать такой код:
for I := 0 to ComponentCount - 1 do
if (Components[I] is TEdit) and (Components[I].Tag >= 1) and (Components[I].Tag <= 10) then
begin
MyEdit := Componets[I] as TEdit;
...
end;
Приведённый код - тоже не предел по быстродействию. Первое улучшение напрашивается сразу: надо выкинуть проверку Components[I] is TEdit. Правда, тогда нам придётся следить, чтобы вообще никакой компонент, а не только TEdit, не имел занчения свойства Tag в диапазоне 1..10. Второе улучшение связано с ситуацией, когда интересующие вас компоненты группируются в начале списка. Рассмотрим крайний случай: интересующие вас TEdit'ы занимают элементы с индексами от 0 до 9, а всего на форме 100 компонентов. Всё, что нужно, цикл выполнит на первых 10-ти итерациях, и оставшиеся 90 итераций будет заниматься заведомо бессмысленными проверками. Чтобы этого не происходило, нужно завести отдельную переменную, служащую счётчиком для найденных TEdit'ов. Перед началом цикла мы этот счётчик обнуляем, на каждом найденном TEdit'е увеличиваем на единицу, и как только он достигнет значения 10, прерываем цикл с помощью Break (кстати, аналогичную оптимизацию можно сделать и для второго варианта). Правда, такая модификация может дать обратный эффект, если искомые компоненты находятся в конце списка. Так, если один из искомых компонентов стоит последним в списке, количество итераций цикла вообще не уменьшится, а время работы увеличится из-за дополнительных проверок.
Недостаток этого метода - то, что надо внимательно следить за занчением свойства Tag. Если вам придётся модифицировать этот код спустя несколько лет, или же этим будет заниматься вообще другой человек, будет не очень просто сразу восстановить логику работы.
Не следует забывать и о том, что каждый визуальный компонент имеет ещё и родителя, который определяется значением свойства Parent. Родитель - это компонент, который визуально содержит данный компонент. Например, родителем компонентов, положенных непосредственно на форму, становится сама форма. А родителем компонентов, положенных на панель (TPanel), становится эта панель. Родитель (им может быть любой наследник TWinControl) имеет свойства ControlCount и Controls, аналогичные ComponentCount и Components, но служащие для перечисления дочерних визуальных компонентов. Существует так же и аналогичная функция FindControl, так что все перечисленные выше методы могут быть модифицированы для поиска в списке Controls. Преимуществ у поиска в Controls перед поиском в Components два. Во-первых, список Controls любого компонента-контейнера на форме будет короче, чем список Components формы, что даст выигрыш в скорости. Во-вторых, компоненты, над которыми требуется выполнить одинаковые действия, часто и визуально размещаются в одном контейнере, что позволяет сократить число проверок. Например, если все интересующие вас TEdit'ы расположены на одной панели, и больше на этой панели никаих TEdit'ов нет, вы можете просто пройти по Controls этой панели, тупо выбрать все TEdit'ы, и не проверяя ни имя, ни Tag, выполнить нужное действие. Однако у этого способа есть два серьёзных ограничения. Он не применим, во-первых, для поиска невизуальных компонентов, а во-вторых, для поиска компонентов, у которых разные родители.
И, наконец, самый быстрый способ. Заводите в классе формы поле MyEdits: array[1..10] of TEdit, а в обработчике OnCreate пишете такой код:
Теперь в любой момент практически без дополнительных издержек вы можете обратиться к нужному вам TEdit'у через массив MyEdits. Перебрать все его элементы в цикле тоже не составит труда. Правда, если таких TEdit'ов будет не 10, а, например, 100, писать все присваивания будет несколько утомительно, но если производительность для вас очень критична, а групповые операции выполняются часто, эти затраты того стоят.
Думаю, теперь, когда вы знакомы с достоинствами и недостатками различных способов, вы легко выберете тот из них, который лучше всего подходит для вашей задачи.
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.