На форуме периодически вижу вопросы, связанные с созданием визуальных компонентов в runtime. Если задать гуглу этот вопрос, то он выдаст кучу полезной и не очень информации (гугл он такой гугл). На новшество нижеизложенного не претендую, изложу свой опыт в данном вопросе.
Принцип создания визуальных компонентов в runtime (кнопки, метки, поля ввода и проч.) практически ничем не отличается от принципа создания невизуальных объектов (листы, потоки, таймеры и проч.). Одним из важных условий отображения визуального компонента (он нам ведь нужен имено для того, чтобы отобразиться) является наличие у него владельца (т.е. другого компонента - потомка TWinControl, на котором этот объект находится и относительно которого он отображается). Минимально необходимый код в таком случае выглядит так (на примере TLabel, не забудьте подключить в uses StdCtrls):
| Delphi | 1
2
3
4
5
| with TLabel.create (Form1) do // создали метку
begin
Parent:=Form1; // указали, что она будет лежать на форме
Caption:='Метка'; // заполнили ее Caption
end; |
|
В итоге получается, что при запуске приложения на открывшейся форме (Form1) в левом верхнем углу находится текст "Метка". Есть одно замечательное свойство у TComponent и его потомков - при вызове деструктора экземпляра класса TComponent этот объект сначала вызывает деструкторы принадлежащих ему объектов, т.е. нет нужды заботится об уничтожении созданного в runtime визуального объекта при закрытии приложения (как и в designtime), объясню на вышеприведенном примере:
строка TLabel.create (Form1) не только вызывает конструктор класса TLabel, но и в массив Components[] формы Form1 добавляет создаваемый объект, если забраться в модуль Classes:
модуль Classes Delphi7
| Delphi | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| constructor TComponent.Create(AOwner: TComponent);
begin
FComponentStyle := [csInheritable];
if AOwner <> nil then AOwner.InsertComponent(Self); // добавляем созданный объект в массив объектов
end;
procedure TComponent.DestroyComponents;
var
Instance: TComponent;
begin
while FComponents <> nil do
begin
Instance := FComponents.Last;
if (csFreeNotification in Instance.FComponentState)
or (FComponentState * [csDesigning, csInline] = [csDesigning, csInline]) then
RemoveComponent(Instance)
else
Remove(Instance);
Instance.Destroy; // вызываем деструктор созданного объекта
end;
end; |
|
Вернемся к нашей созданной метке, с конструктором и деструктором, вроде, разобрались, теперь перейдем к свойству Parent - в этом свойстве назначается объект, относительно которого будет отображаться созданный нами визуальный объект, а именно видимость, разрешенность (свойство Enabled), положение (Left и Top), шрифт и т.д., если не назначить владельца, то созданный визуальный объект просто не будет отображен, опять же если забраться в модуль Classes:
модуль Classes Delphi7
| Delphi | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| procedure TControl.SetParent(AParent: TWinControl);
begin
if FParent <> AParent then
begin
if AParent = Self then
raise EInvalidOperation.CreateRes(@SControlParentSetToSelf);
if FParent <> nil then
FParent.RemoveControl(Self);
if AParent <> nil then
begin
AParent.InsertControl(Self); // назначаем владельца
UpdateAnchorRules; // обновляем визуальные свойства объетка
end;
end;
end; |
|
С теорией создания и уничтожения, разобрались, пойдем дальше.
Минимально необходимый код, приведенный выше, в большинстве случаев не устраивает, хотя бы ужно разместить визуальный объект там, где нам это необходимо. Для этого назначаем необходимые значения свойствам Top и Left (помните, что положение меняется относительно назначенного владельца, т.е. объекта в свойстве Parent). Дополню код:
| Delphi | 1
2
3
4
5
6
7
| with TLabel.create (Form1) do // создали метку
begin
Parent:=Form1; // указали, что она будет лежать на форме
Caption:='Метка'; // заполнили ее Caption
Left:=(Form1.ClientWidth div 2) -(Width div 2); // расположили по центру формы по горизонтали
Top:=(Form1.ClientHeight div 2) -(Height div 2); // расположили по центру формы по вертикали
end; |
|
Продолжим, просто отображенная метка это замечательно, но не очень интересно, хотелось бы, чтобы эта метка как-то реагировала, например, на нажатие на нее мыши. У метки есть свойство OnClick:TNotifyEvent; т.е. процедурного типа, не вдаваясь в подробности - указанная в этом свойстве процедура вызывается когда было произведено нажатие мыши на этот объект. Остается создать процедуру, удовлетворяющую типу TNotifyEvent и назначить этому свойству (эта процедура должна быть методом какого-либо класса, существует, конечно, возможность назначить любую процедуру, но тут это разбирать не буду). Объявим в секции private (можно и в секции public или protected) нашей формы процедуру procedure LabelClik(Sender:TObject);, код примет вид:
| Delphi | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| procedure TForm1.FormCreate(Sender: TObject);
begin
with TLabel.create (Form1) do // создали метку
begin
Parent:=Form1; // указали, что она будет лежать на форме
Caption:='Метка'; // заполнили ее Caption
Left:=(Form1.ClientWidth div 2) -(Width div 2); // расположили по центру формы по горизонтали
Top:=(Form1.ClientHeight div 2) -(Height div 2); // расположили по центру формы по вертикали
OnClick:=LabelClik; // назначили процедуру - обработчик нажатия мыши
end;
end;
procedure TForm1.LabelClik(Sender: TObject);
begin
ShowMessage('Click'); // обрабатываем нажатие мыши
end; |
|
Точно также назначаются остальные обработчики. Все хорошо, когда мы создаем один объект, тут все понятно, но что делать, если нам необходимо создать некоторое количество подобных объектов, как разобраться в каком объекте произошло то или иное событие? Рассмотрю варианты:
1 назначить каждому создаваемому объекту свою процедуру-обработчик.
2 назначить всем объектам один обработчик и в нем идентифицировать объект, вызвавший событие.
Пункт 1 рассматривать не буду, я его не рекомендую, хотя он имеет место быть, с ним все и так понятно. Рассмотрим пункт 2. Основная задача - идентификация объекта, вызвавшего событие, объект может быть идентифицирован, например, по имени. Идентификация по имени несет за собой некоторые неудобства - необходимо генерировать отличные друг от друга имена и имена имеют символьный тип, а сним работать не очень. Есть замечательное свойство Tag (метка) типа Integer, его и будем использовать для идентификации - при создании объекта просто запишем в это свойство какое-нибудь значение, например, порядковый номер, код приобретет следующий вид (для примера создадим 10 Label'ов):
| Delphi | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| var
i:integer;
begin
for i:=1 to 10 do
with TLabel.create (Form1) do // создали метку
begin
Parent:=Form1; // указали, что она будет лежать на форме
Caption:='Метка '+IntToStr(i); // заполнили ее Caption
Left:=(Form1.ClientWidth div 2) -(Width div 2); // расположили по центру формы по горизонтали
Top:=(Height *i); // расположили по вертикали
OnClick:=LabelClik; // назначили процедуру - обработчик нажатия мыши
Tag:=i; // назначили идентификатор
end;
end; |
|
В процедуру-обработчик поступает объект, вызвавший это событие, он содержится в Sender:TObject (все является потомком TObject, есть еще TInterfacedObject, но об этом не здесь, поэтому тип аргумента верен), для того, чтобы получить объект, в нашем случае Label, необходимо сначала привести Sender к необходимому типу. Для объектов рекомендуется использовать конструкцию as, код обработчика приобретет следующий вид:
| Delphi | 1
2
3
4
5
6
| procedure TForm1.LabelClik(Sender: TObject);
begin
if (Sender as TLabel).Tag=5 then // если нажали на метку под номером 5
ShowMessage('Click') else
ShowMessage('No');
end; |
|
Такой обработчик имеет место быть, если мы уверены, что в эту процедуру будет передан только объект типа TLabel, но лучше перестраховаться и написать следующее:
| Delphi | 1
2
3
4
5
6
7
| procedure TForm1.LabelClik(Sender: TObject);
begin
if Sender is TLabel then // проверяем тип того, что вызвало обработчик, если это метка
if (Sender as TLabel).Tag=5 then // если нажали на метку под номером 5
ShowMessage('Click') else
ShowMessage('No');
end; |
|
Вот и все, теперь можно создавать множество объектов разных типов, назначать им один и тот же обработчик. С созданием объектов, думаю разберетесь, приведу пример обработчика нажатия мыши:
| Delphi | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| procedure TForm1.LabelClik(Sender: TObject);
begin
if Sender is TLabel then
begin
if (Sender as TLabel).Tag=7 then
// тут что-то делаем с меткой
end;
if Sender is TButton then
begin
if (Sender as TButton).Tag=3 then
// тут что-то делаем с кнопкой
end;
if Sender is TMemo then
begin
if (Sender as TMemo).Tag=2 then
// тут что-то делаем с мемо
end;
end; |
|
Как обратиться к какому-то конкретному созданному в runtime объекту, например, чтобы изменить его свойства по ходу выполнения программы, рассмотрю позже.
|