Параметр Sender Delphi

Работа с Sender в обработчиках компонентов




Уроки Delphi
  1.  Первая программа
  2.  Использование компонентов
  3.  События Delphi
  4.  Типы данных Delphi
  5.  Создание своих типов данных
  6.  Выражения и операторы
  7.  Работа с файлами в Delphi
  8.  Дополнительные формы
  9.  Подпрограммы в Delphi
  10. Исключительные ситуации
  11. Взаимодействие приложения с пользователем
  12. Указатели в Delphi
  13. Обзор компонентов
  14. Работа со строками
  15. Создание интерфейса
  16. Графика в Delphi
  17. Многопоточность в Delphi
  18. Динамическое создание
        компонентов
Поиск по сайту




 Это важно:
   Метод Application.ProcessMessages;

 Это полезно:
   Параметр Sender в обработчиках событий;










Бояться не надо



   Параметр Sender в Delphi-программе присутствует в каждом обработчике событий любого компонента. Однако, поскольку в использовании параметра Sender часто нет необходимости, новички про него "забывают" и часто даже не догадываются о его предназначении. В этой статье я хочу рассказать о том, для чего предназначен параметр Sender Delphi и как работать с таким, как оказывается, важным и удобным параметром как Sender.

   Sender имеет тип TObject, и имеет значение объекта - источника события, в обработчике которого он используется. То есть, если на Форме находится несколько одинаковых компонентов, к тому же выполняющих одинаковые функции, то нет необходимости для каждого из них создавать свои процедуры-обработчики событий. Вполне достаточно одного комплекта обработчиков, а выяснить, к какому компоненту относится событие обрабатываемое именно сейчас, позволит именно параметр Sender.

   Ну, во первых, мы можем непосредственно выяснить имя компонента - источника события. Положите на Форму два любых компонента (например кнопки Button), для одного из низ создайте заготовку обработчика любого события (например OnClick), а второму просто сопоставьте обработчик первого. Для этого откройте Инспектор Oбъектов, выберите второй ваш объект, и на вкадке Events щёлкните по обработчику выбранного события (OnClick в данном случае). В раскрывшемся списке выберите Button1Click. Затем щёлкните туда дважды. В редакторе кода, куда вы попадёте, введите такой код:

if Sender = Button1
  then Caption:='Щелчок по кнопке №1'
  else Caption:='Щелчок по кнопке №2';

   Программа покажет, по какой именно кнопке был щелчок. Ну а зная это, можно предусмотреть дальнейшую реакцию программы.

   Это ещё не всё! Работая с параметром Sender, можно обойтись даже и без выяснения имени компонента-источника. Например, задача такая: мы должны следить за свойством Text нескольких компонентов Edit и при появлении в любом из них символа ',' (запятая) менять его на '.' (точка). Создайте такой обработчик события OnChange для одного из Edit'ов, а остальным просто сопоставьте, как в предыдущем случае:

procedure TForm1.Edit1Change(Sender: TObject);
var n: Integer;
    S: String;
begin
n:=pos(',', Edit1.Text);
if n<>0 then
  begin
    S:=Edit1.Text;
    S[n]:='.';
    Edit1.Text:=S;
    Edit1.SelStart:=n+1;
  end;
end;

   Прежде всего заметим, что слово Edit1 написано много раз, что на самом деле излишне. С помощью оператора присоединения with избавимся от необходимости писать Edit1 внутри операторов:

procedure TForm1.Edit1Change(Sender: TObject);
var n: Integer;
    S: String;
begin
with Edit1 do
  begin
    n:=pos(',', Text);
    if pos(',', Text)<>0 then
      begin
        S:=Text;
        S[n]:='.';
        Text:=S;
        SelStart:=n+1;
      end;
  end;
end;

   Всё! Теперь для того чтобы избавится от запятых во всех Edit'ах, нужно заменить в операторе присоединения Edit1 на (Sender as TEdit) - лучше вот так, в скобках.
   Дело в том, что конструкция (Sender as TObject) позволяет работать с источником события как с соответствующим объектом. В частности, если после (Sender as TEdit) поставить точку, то Delphi выдаст нам список свойств и методов компонента Edit. Хотя, по лично моей логике, вместо (Sender as TEdit) достаточно было бы просто Sender. Ан нет, не получается...

   Теперь вы понимаете, почему мы избавились от Edit1 внутри операторов - заменять каждый Edit1 на (Sender as TEdit) было бы затруднительно!

   Наконец, иногда бывает необходимо выяснить не имя, а тип источника события. В этом поможет оператор is, с помощью которого параметр Sender можно сравнить с одним из типов Delphi:

if (Sender is TButton)
  then Caption:='Источник события - кнопка TButton';



Кстати, насчёт использованного выше способа замены запятой на точку... Вместо того чтобы заменять их в тексте строки, гораздо удобнее делать это прямо "на лету", используя процедуру OnKeyPress:

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if Key=',' then Key:='.';
end;


   Почему я сразу не использовал этот способ? Потому что тут вообще не используется параметр Sender! Хотя работать эта процедура также будет для всех компонентов, которым она сопоставлена. Но учитесь видеть разные возможности, когда-то и первый способ пригодится. И вообще, учитесь...


Обзор компонентов Delphi           В начало урока          Компонент Delphi SpeedButton

Уроки Delphi начинающим



Вопросы и комментарии (34)      Решение задач в Delphi

Николай, добавлено 21.04.12, 14:41:07 
отличная статья, выручила. 5+
Ольга, добавлено 28.05.12, 23:07:55 
если создаешь свой компонент Tlabel в кнопке. Созданный компонент становится невидимым если щелкаешь по нему 2 раза lb.OnDblClick:=label2.OnDblClick; а в Label2 пишем if sender is Tlabel then tlabel(sender).Visible:=false;
Как узнать при проверке какой из созданных компонентов был стерт.
 
Автор, добавлено 29.05.12, 05:45:27 
При какой проверке? В этой же процедуре?

if Sender=Label2
  then Caption:='Был стёрт Label2';
  else Caption:='Был стёрт lb';

Я же это показал в статье, читали её? Сравните:

if Sender = Button1
  then Caption:='Щелчок по кнопке №1'
  else Caption:='Щелчок по кнопке №2';

Ольга, добавлено 29.05.12, 11:40:00 
Я это читала. Загвоздка в том что lb будет не один при каждом нажатии кнопки создается новый и если придавать каждому имя с цифрой или использовать tag все равно запоминается последний, а как сделать чтобы программа узнавала какой lb был стерт первый второй или например третий из созданных?
Автор, добавлено 29.05.12, 12:08:37 
Не понимаю. Sender указывает на источник, какая разница какой он по счёту? Выяснить что за компонент - не проблема. И даже через Tag, почему нет? А вы пробовали, или умозрительно сомневаетесь?
Ольга, добавлено 29.05.12, 12:17:33 
Пробовала. не получается. если было создано 5 компонентов lb потом стираю один из них и в Label2.caption хочу чтоб написал lb.tag стертого он пишет последний то есть 5 а не один или 2 который был стерт
Автор, добавлено 29.05.12, 12:30:41 
Давайте, скопируйте сюда процедуры создания компонентов и щелчка по ним, я скопирую себе и посмотрим вместе что не так.
Автор, добавлено 29.05.12, 12:45:24 
Отмена. Вообще не понимаю в чём проблема. Вот я не создавал, просто поставил 5 лабелей, каждому таг от 1 до 5. Процедура OnDblClick:

Caption:=IntToStr((Sender as TLabel).Tag);
Ольга, добавлено 29.05.12, 12:47:06 
{$R *.dfm}
var lb:Tlabel;b:boolean;i:byte;
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
lb:=tlabel.Create(self);
  lb.Parent:=self;
  lb.Font.Color:=clnavy;
  lb.Font.Name:='Arial';
  lb.Font.Size:=26;
  lb.Caption:='(';
  inc(i);
  if b then lb.Tag:=i-1
  else lb.Tag:=i;
  lb.OnDblClick:=label1.OnDblClick;
lb.Left:= 20+random(100);
 lb.Top:=20+random(100);

end;

procedure TForm1.Label1DblClick(Sender: TObject);
begin
b:=true;
if sender is Tlabel then begin
tlabel(sender).Visible:=false;
label1.Caption:=inttostr(lb.tag);
end else label1.Caption:='net';
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
b:=false;
i:=0;
end;

end.

Автор, добавлено 29.05.12, 13:02:46 
Всё ясно. Проблема в том, что у вас все лабели создаются одной переменной, и фактически вы присваиваете таг не компоненту а этой переменной lb. Естественно, она и будет иметь таг последний. А исчезновение вы делаете не этой переменной, а как раз компонента типа TLabel - источника события, поэтому это получается.
Ольга, добавлено 29.05.12, 13:05:54 
А как присваивать таг компоненту в моем случае?
Автор, добавлено 29.05.12, 13:21:02 
Пойдём методом последовательного приближения
1. Присваивать нужно ведь не переменной а компоненту, который вы создали. А обращение к компоненту происходит ВСЕГДА с указанием его имени.
2. Или использовать для каждого компонента собственную переменную. Но и тогда вы будете использовать таг не компонента а переменной, просто они будут совпадать.
Ольга, добавлено 29.05.12, 13:36:59 
А по конкретнее можно? в моем случае мне менять создавать вектор переменных но и тогда будет запоминаться конечная переменная. помогите пожалуйста!
Ольга, добавлено 29.05.12, 13:57:10 
с вектором тоже не получается
Ольга, добавлено 29.05.12, 14:24:45 
как присваивать компоненту? lb из переменной после строк lb:=tlabel.Create(self);
  lb.Parent:=self; становится компонентом логически?! или нет? и что делать? куда и как присваивать?
Автор, добавлено 29.05.12, 15:14:18 
Создаётся динамический массив - вы это называете вектором? Ну пусть.

var lb: Array of Tlabel;

Впрочем, вот вам весь проект. Только не копируйте слепо, а посмотрите в чём разница:

var
  Form1: TForm1;
  b:boolean;i:byte;
  lb: Array of Tlabel;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
SetLength(lb, i+1);
lb[i]:=tlabel.Create(Self);
  lb[i].Parent:=Self;
  lb[i].Font.Color:=clnavy;
  lb[i].Font.Name:='Arial';
  lb[i].Font.Size:=26;
  lb[i].Caption:='(';
  if b then lb[i].Tag:=i
  else lb[i].Tag:=i+1;
  lb[i].OnDblClick:=label1.OnDblClick;
lb[i].Left:= 20+random(100);
 lb[i].Top:=20+random(100);
  inc(i);
end;

procedure TForm1.Label1DblClick(Sender: TObject);
begin
b:=true;
if (sender is Tlabel) then
 begin
  tlabel(sender).Visible:=false;
  label1.Caption:=inttostr(tlabel(sender).tag);
 end else
  label1.Caption:='net';
end;

Ольга, добавлено 29.05.12, 16:07:13 
Спасибо!я не списывала тупо. в моем массиве были две ошибки и я их исправила! setlength(lb,10) на setlength(lb,i-1) но это не такая большая загвозка была в этом: label1.Caption:=inttostr(i-1);я афищировала только и label1.Caption:=inttostr(tlabel(sender).tag);
Спасибо большое! и извените за беспокойство!
Ольга, добавлено 29.05.12, 16:33:04 
кстати уничтожать компонент этим же способом не получается!или только у меня? выдает ошибку Access violation of adress...
if (sender is Tlabel) then tlabel(sender).Free; и на это тоже
 if (sender is Tlabel) then freeandnil(tlabel(sender));
Автор, добавлено 29.05.12, 17:36:24 
Да, что-то ничего пока не получается. Так что вам спасибо - буду думать. Как придумаю - допишу способ уничтожения к статье "Динамическое создание компонентов". А то там пока тоже Free, а это оказывается на компоненты созданные в массиве не действует. Вернее, действует - компонент уничтожается, но тут же появляется неустранимая ошибка.
Ольга, добавлено 29.05.12, 17:51:26 
Спасибо! Ваши статьи очень помогают!
Автор, добавлено 29.05.12, 19:49:34 
Касаемо уничтожения компонентов. Ошибка возникает потому, что "нельзя уничтожать компонент в его же событии" - цитирую ответ на программистском форуме. Поэтому берём таймер, передаём в него по щелчку параметр Tag, а по его срабатыванию разыскиваем на Форме компонент типа TLabel с Tag'ом равным переданному - и уничтожаем.
Евгений, добавлено 1.06.12, 08:09:51 
на сайте статьи очень полезные, но можете ли вы мне помочь в такой ситуации:
я создал динамически кнопки и у них есть порядковые номера(массив) вот как бы мне узнать индекс той кнопки на которую я нажал? что нужно наипсать для процедуры proc:

mas[i].onclik:=proc; // вот нужно узнать число i кнопки на которой я нажал
Тот же Евгений, добавлено 1.06.12, 08:11:42 
способ уничтожения таков :
  Sender.Free();
я его применял для onclick
Снова Евгений, добавлено 1.06.12, 08:17:32 
удалять компонент вполне возможно внутри себя, я создавал динамически множество кнопок и для каждой из них писал процедуру на onclick sender.free(); получаетсья что при нажатии каждой кнопки он удаляет саму себя но ввиду того что на идекс динамической кнопки ассоциируються в других массивах у м еня возник вопрос (св.выше)
Автор, добавлено 1.06.12, 08:38:01 
Без использования дополнительного признака не обойтись. Можно номер сунуть в имя, но лучше, я думаю, использовать свойство Tag - при создании приравнивать индексу.
Евгений, добавлено 1.06.12, 21:24:58 
Была мысль о теге но как тогда узнать индекс элемента(у нажатой кнопки)?
Автор, добавлено 1.06.12, 21:38:06 
при создании приравнивать индексу
Дмитрий, добавлено 8.05.13, 17:01:53 
Здравствуйте! Статья доходчивая.
Но у меня не получается сохранять разные экземпляры TValueListEditor единственным PopupMenu с созданным пунктом меню Save. Обработчик меню:
procedure TForm1.SaveVLEClick(Sender: TObject);
begin
  SaveDialogVLE.Execute;
  if (Sender is TValueListEditor) then
  (Sender as TValueListEditor).SaveToCSVFile(SaveDialogVLE.FileName)
  else memo1.Lines.Add(Sender.ClassName); // memo1 - это лог
end;
в мемо1 вываливается TMenuItem. Как подобраться к ValueListEditor вызвавшему PopupMenu ? Подскажите пожалуйста.

Автор, добавлено 8.05.13, 22:19:31 
Потому что источник события в данном случае - не ValueListEditor, а тот пункт меню, который вы нажимаете.Вам нужно при щелчке по нему правой кнопкой (да неважно какой) помечать его как-то, например его номер, записанный в свойстве Tag, присваивать некой переменной. А вот уже источник этого события (щелчка) будет ValueListEditor, можно использовать один обработчик. А в процедуре нажатия меню анализировать номер ValueListEditor'а.
Игорь, добавлено 26.01.14, 09:06:06 
Как же вовремя заметил эту статью :) Спасибо
Антон, добавлено 8.02.14, 18:30:08 
Не совсем понятно зачем нужна конструкция Sender as TComponent.
Почему нельзя использовать просто переменную Sender?
(например Sender.Text:='Текст в компоненте Edit' вместо
 (Sender as TEdit).Text:='Текст в компоненте Edit')
Автор, добавлено 8.02.14, 20:41:08 
Я в тексте статьи это специально написал, что тоже не понимаю, зачем не просто Sender, а (Sender as TObject). Но тем не менее, приходится так писать. Это не мы придумали.
Антон, добавлено 8.02.14, 22:15:43 
Нет, вы не поняли, я не понимаю именно почему у вас в статье это так написано. У меня, вроде как все прекрасно работает именно через просто Sender.
Антон, добавлено 8.02.14, 23:13:47 
А нет, прошу прощения. Это я ошибся, видимо запутался в собственном коде.
Оставить комментарий:

Имя  

Текст комментария