Форум программистов, компьютерный форум, киберфорум
cotseec
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
Различные приемы программирования на Delphi, применяемые мной на практике.

Динамические компоненты VCL

Запись от cotseec размещена 20.12.2012 в 18:41
Показов 5005 Комментарии 0
Метки delphi

На форуме периодически вижу вопросы, связанные с созданием визуальных компонентов в 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 объекту, например, чтобы изменить его свойства по ходу выполнения программы, рассмотрю позже.
Метки delphi
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Запустил конкурс "тем и промптов для текстовых квестов созданных почти чисто ИИ"
Adler 06.10.2026
Всем привет! За последние три-четыре дня я создал более 16 текстовых квестовых игр используя преимущественно по одному запросу к ИИ на игру. Мне так понравилось смотреть все ветки/ сцены во всех. . .
ИИ не может найти нужный язык в списке
Supersumestria 05.10.2026
Я ему даю вот такое изображение и прошу найти и подчеркнуть немецкий язык. Возвращает он вот это: https:/ / i. **********/ vqBWLe2. png Нужную строчку в 3й колонке просто выдумал. . Это. . .
Новая последняя моя музыка в SUNO
zorxor 05.10.2026
Здравствуйте, дорогие мои друзья! С большой радостью я хотел бы представить вам свою новую последнею музыку, которую сгенерировала мне по моей просьбе нейросеть SUNO. С уважением, zorxor. Это. . .
Программный домашний кинотеатр
russiannick 27.09.2026
Сподобился на программный домашний кинотеатр. В качестве ЯВУ по традиции выбрал js. В помощники взял Яндекс-Алису. Было создано три зала на разные интересы. исторические и ретро сериал Хичкок. . .
Беседа с ИИ о программистах, недопускающих к созданию и правке кода генеративные ИИ и причины этого
zorxor 21.09.2026
Раньше я радовался или получал некоторые эмоции, пусть небольшие, но всё же, от самого процесса написания кода, рекомпиляции и запуска, видя постепенное развитие программы и прочее. А теперь лень. . .
Мобильное приложение ColorStep
pavlinmavlin 17.09.2026
Реализовал приложение Красный, Зеленый, Синий в Unity3d + c#. Название изменил на ColorStep. Приложение прошло модерацию и теперь доступно для скачивания. Делал его сам, шаг за шагом — и вот,. . .
Запрет дублирования строк в табличной части
Maks 13.09.2026
Реализация из решения ниже выполнена на нетиповом справочнике "Нормы ТО" с табличной часть "Виды ТО", разработанного в КА2, со следующими реквизитами: - ВидТО (СправочникСсылка. ВидыТО); - ВидГСМ. . .
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр.
Jin X 06.09.2026
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр. Работая с форумом и нейросетями в браузере часто хочется что-то подкорректировать или добавить какого-то функционала. Ниже прикреплён. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru