Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.134.104.173] |
|
Сообщ.
#1
,
|
|
|
На форме на разных панелях есть TButton (допустим их 20). Перед обработкой нажатия кнопки нужно проверить одно и то же условие. Если оно выполняется - обработать нажатие, то есть вызывать 20 разных процедур в зависимости от нажатой кнопки. Вставлять в каждый обработчик проверку условия - не подходит по некоторым причинам. Предполагаю, что можно назначить один OnClick на все кнопки, в обработчике OnClick проверить условие и при помощи Win API каким-нибудь образом идентифицировать, какая кнопка была нажата. Подозреваю что при помощи хэндлов. Только вот как ?
|
Сообщ.
#2
,
|
|
|
Dmitry_Z
Все проще. У каждой кнопки задай свой уникальный номер Tag(есть у всех визуальных компонентов), обычно по номеру кнопки. Сделай 1 обработчик для всех кнопок, и так отслеживай номер нажатой: ShowMessage(IntToStr( (Sender as TButton).tag )); |
Сообщ.
#3
,
|
|
|
Возможно, легче проверку вынести в отдельную функцию и просто вызывать ее в обработчиках (если действия, которые они должны выполнять, сильно отличаются).
|
Сообщ.
#4
,
|
|
|
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls; type TForm1 = class(TForm) Panel1: TPanel; Panel2: TPanel; Panel3: TPanel; Panel4: TPanel; Panel5: TPanel; Button1: TButton; Button2: TButton; Button3: TButton; Button4: TButton; Button5: TButton; Button6: TButton; Button7: TButton; Button8: TButton; Button9: TButton; Button10: TButton; procedure MyButtonClick(Sender: TObject); private public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} const my_condition:boolean=true; { TForm1 } procedure TForm1.MyButtonClick(Sender: TObject); begin if my_condition then if (Sender is TButton) then ShowMessage((Sender as TButton).Name + ' clicked'); end; end. Если нужно обработать нажатие какой-нить конкретной кнопки, можно сделать так: procedure TForm1.MyButtonClick(Sender: TObject); begin if my_condition then if ((Sender is TButton) and ((Sender as TButton).Name='Button5')) then ShowMessage((Sender as TButton).Name + ' clicked'); end; См. проект в аттаче. Прикреплённый файлButtons_Click_Handler.zip (646,13 Кбайт, скачиваний: 278) |
Сообщ.
#5
,
|
|
|
Спасибо всем за ответы.
В другом форуме мне просто рекомендовали назначить всем кнопкам один обработчик и анализировать Sender. Я думаю что как-то так: procedure TForm1.Button1Click(Sender: TObject); begin if Sender = Button1 then ... else if Sender = Button2 then ... else if Sender = Button3 then ... ... end; |
Сообщ.
#6
,
|
|
|
Dmitry_Z, посмотри пост #5 - там именно это и делается.
|
Сообщ.
#7
,
|
|
|
А зачем так сложно ?
if ((Sender is TButton) and ((Sender as TButton).Name='Button5')) |
Сообщ.
#8
,
|
|
|
>А зачем так сложно ?
Потому что в общем случае Sender может быть другого типа без свойства Name. IMHO, c Tag проще, быстрее, легче автоматизировать (программно создать полсотни кнопок, например) |
Сообщ.
#9
,
|
|
|
Цитата Dmitry_Z @ А зачем так сложно ? Ничего сложного - это стандартная проверка на тип объекта, потому как Цитата MBo @ в общем случае Sender может быть другого типа Можно обойтись и без "Name": if ((Sender is TButton) and ((Sender as TButton)=Button5)) then ... Цитата MBo @ IMHO, c Tag проще, быстрее, легче автоматизировать (программно создать полсотни кнопок, например) К тому же это будет оптимальнее - как по скорости, так и по размеру кода. Юзать можно, например, так: procedure TForm1.MyButtonClick(Sender: TObject); begin // if my_condition then if (Sender is TButton) then case (Sender as TButton).Tag of 1: begin // if my_condition then ShowMessage((Sender as TButton).Name + ' clicked'); end; 2: begin // if my_condition then ShowMessage((Sender as TButton).Name + ' clicked'); end; 3: begin // if my_condition then ShowMessage((Sender as TButton).Name + ' clicked'); end; ... end; end; или так: procedure TForm1.MyButtonClick(Sender: TObject); var i:integer; begin // if my_condition then if (Sender is TButton) then for i:=1 to 10 do if (Sender as TButton).Tag=i then begin // if my_condition then ShowMessage((Sender as TButton).Name + ' clicked'); end; end; |
Сообщ.
#10
,
|
|
|
Цитата Krid @ Ничего сложного - это стандартная проверка на тип объекта, потому как Цитата MBo @ в общем случае Sender может быть другого типа Это не стандартная проверка, а примитивно-перестраховочная и "масло-масляная". Во-первых, после проверки типа по is нет смысла делать приведение типа по as (т.к. по сути это приводит к вызову повторной проверки типа по is) - достаточно прямого приведения типа к заданному классу (см. справку). Во-вторых, из той же справки следует, что операторы is и as используются в случае, когда реальный тип объекта может принадлежать заданному классу либо одному из его наследников. Если же по условию задачи наследники нас не интересуют, то вместо навороченной\тормозной проверки типа по is, можно использовать прямую проверку типа по if Sender.ClassType = TButton. Ну и наконец, в-третьих, если делается проверка принадлежности объекта не просто определенному классу, а конкретному экземпляру класса, то проверки по is\as вообще не нужны, т.к. твой вариант без "Name" if ((Sender is TButton) and ((Sender as TButton) = Button5)) then ... Поэтому ТС прав - если он не обращается к общим свойствам Sender as Component типа Name или Tag, а сразу делает проверку на принадлежность Sender определенному экземпляру кнопки Button1 и т.п., то его вариант #6 с прямыми проверками if Sender = Button1 и т.п. является наиболее простым, быстрым и "самодостаточным" (в том смысле, что никакие доп. проверки по is\as тут не нужны). |
Сообщ.
#11
,
|
|
|
Можно также не привлекать Tag (за его актуальностью еще следить надо), а создать массив кнопок, выполнять поиск Sender в нём и по индексу уже запускать действие. Минус в том, что нет очевидного соответствия между кнопкой и индексом.
Buttons = TArray<TButton>.Create(Button1, Button2, ...); case IndexOf(Buttons, Sender) of ... Возможно, код со сравнением Sender с Button1/2/3... окажется нагляднее всего. Во избежание сложной структуры кода в обработчике (если действия для каждой кнопки больше пары строк) можно убрать конструкции else: if Sender = Button1 then begin ... Exit; end; if Sender = Button2 then begin ... Exit; end; |
Сообщ.
#12
,
|
|
|
Ещё проще:
1 Сделать ActionManager с акшинами равными количеству кнопок. 2 Назвать каждый акшон так-же как и кнопку+1 любой символ(например AButton1) 3 Все кнопки зациклить на 1 кнопку. 4 вызывать (FindComponent('A'+ (sender as TButton).Name) as TAction).Execute; |
Сообщ.
#13
,
|
|
|
Спасибо всем.
Дело в том что я кое-что переделываю в сотнях строк уже написанного кода. У меня уже есть обработчики десятка кнопок. Я разместил на форме еще одну кнопку с visible := false, в её обработчике разместил код procedure TForm1.SelectorButtonClick(Sender: TObject); begin if Sender = Button1 then Button1Click(Sender) else if Sender = Button2 then Button2Click(Sender) else if Sender = Button3 then Button3Click(Sender) ... else if Sender = Button10 then Button10Click(Sender); end; Так "не взлетит" ? Вроде бы пока всё работает... |
Сообщ.
#14
,
|
|
|
1. Невидимая кнопка зачем? Обработчик - это просто метод формы
2. Если есть уже нужные обработчики - зачем этот огород? Вообще конечно взлетит, но смысла в этом немного |
Сообщ.
#15
,
|
|
|
Цитата Dmitry_Z @ procedure TForm1.SelectorButtonClick(Sender: TObject); begin if Sender = Button1 then Button1Click(Sender) else if Sender = Button2 then Button2Click(Sender) else if Sender = Button3 then Button3Click(Sender) ... else if Sender = Button10 then Button10Click(Sender); end; а есть аналог findcomponent только для процедуры? Типа Button+'10'+Click(Sender)? |
Сообщ.
#16
,
|
|
|
Цитата Fr0sT @ Невидимая кнопка зачем? Обработчик - это просто метод формы Видимо Dmitry_Z не в курсе, что метод формы можно создать самому, ручками - вот и использовал клик на кнопке для автосоздания заготовки метода. Теперь осталось только удалить эту кнопку за ненадобностью Цитата ^D^ima @ а есть аналог findcomponent только для процедуры? Типа Button+'10'+Click(Sender) Для published методов есть class function TObject.MethodAddress(MethodName):Pointer. Правда придется немного пошаманить с приведением TNotifyEvent к записи TMethod //метод формы var p:pointer; m:TNotifyEvent; begin ... p:=MethodAddress(...); //адрес метода if p <> nil then begin TMethod(m).code:=p; TMethod(m).data:=Self; m(Sender); //вызов метода end; Единственный вопрос - что это дает? Или цитируя Fr0sT-а Цитата Fr0sT @ зачем этот огород? Вообще конечно взлетит, но смысла в этом немного |
Сообщ.
#17
,
|
|
|
Цитата leo @ зачем этот огород? Чтобы в 1 строку записать для 100 кнопок код, который бы вызывал Button+'номер кнопки'+Click(Sender) |
Сообщ.
#18
,
|
|
|
Вот вам ещё вариант.
Чтобы не заморачиваться с if/case и т.п. Так даже проще менять кол-во кнопок/процедур. (у каждой кнопки прописан номер процедуры в Tag) unit Main; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; Button3: TButton; Button4: TButton; Button5: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure Click1; begin ShowMessage('One') end; procedure Click2; begin ShowMessage('Two') end; procedure Click3; begin ShowMessage('Three') end; procedure Click4; begin ShowMessage('Four') end; procedure Click5; begin ShowMessage('Five') end; const ProcAddr: array [1..5] of TProcedure = (Click1, Click2, Click3, Click4, Click5); procedure TForm1.Button1Click(Sender: TObject); var N: Integer; begin N := (Sender as TButton).Tag; if N in [Low(ProcAddr)..High(ProcAddr)] then ProcAddr[N] end; end. p.s. Делать проверку через is не вижу смысла, т.к. процедура сделана специально под TButton.OnClick. Можно было бы даже вместо (Sender as TButton) написать просто TButton(Sender), ну да ладно... Прикреплённый файлClick.zip (3,19 Кбайт, скачиваний: 54) |
Сообщ.
#19
,
|
|
|
Цитата ^D^ima @ Чтобы в 1 строку записать для 100 кнопок код, который бы вызывал Button+'номер кнопки'+Click(Sender) Я по-прежнему не понимаю, накой? Если уже есть эти 100 обработчиков - что мешает использовать их? Добавить первой строкой if not Condition then Exit и всё. А если уж так хочется краткости - можно вклиниться в обработчик. for Button in TArray<TButton>.Create(Button1, Button2, ...) do begin Button.Tag := NativeUInt(Button.OnClick); Button.OnClick := ButtonPreHandler; end; procedure TForm1.ButtonPreHandler(Sender); begin if Condition then TNotifyEvent(TButton(Sender).Tag)(Sender); end; Добавлено Также можно сохранять обработчики в Dictionary<TButton, TNotifyEvent>, если не хочется применять Tag |
Сообщ.
#20
,
|
|
|
Цитата Fr0sT @ TNotifyEvent(TButton(Sender).Tag)(Sender); Тут Invalid typecast, т.к. TNotifyEvent - это не один указатель, а два (см.TMethod). Поэтому тут нужны такие же извращения с приведением типа TNotifyEvent к TMethod, как в #16 (или же с приведением типа указателя к type TNotifyEventProc = procedure(Self,Sender:TObject) ) Но если заниматься подобными хаками, то можно использовать и другие варианты. Например, 1) Объявить тип TMyButton c переопределенным методом Click (типа if Condition then inherited;) и в FormCreate заменить классы нужных кнопок (PClass(Button)^ = TMyButton, где type PClass = ^TClass); 2) Задействовать неиспользуемое в TButton события, например OnDblClick - объявить тип TButtonHook (= class(TButton) end) для доступа к protected свойствам, а затем по твоей схеме сохранить OnClick в TButtonHook(Button).OnDblClick и затем в обработчике вызвать TButtonHook(Sender).OnDblClick(Sender). ======================= Однако, думается, что сама постановка задачи и все предлагаемые варианты ее решения - это извращение в стиле саги об X,Y,Z. По хорошему, если условия для запуска процедуры по кнопке не выполнены, то сама кнопка должны быть не активна (Enabled = false). Поэтому для создания нормального юзер-интерфейса нужно не полениться и обеспечить блокировку\разблокировку всех кнопок при выполнении заданного условия по некоторым другим событиям. PS: Вообще, не понятно почему 10-20 кнопок реализуются на обычных TButton (какого они должны быть размера или как нужно зашифровать\сократить их надписи, чтобы это выглядело нормально)? Может лучше использовать меню или панель инструментов? |
Сообщ.
#21
,
|
|
|
Цитата leo @ 1) Объявить тип TMyButton c переопределенным методом Click (типа if Condition then inherited;) и в FormCreate заменить классы нужных кнопок (PClass(Button)^ = TMyButton, где type PClass = ^TClass); Можно также привлечь "шаманский метод Geo" (TButton = class(Buttons.TButton)), чтобы не переопределять класс. Цитата leo @ Однако, думается, что сама постановка задачи и все предлагаемые варианты ее решения - это извращение в стиле саги об X,Y,Z. |
Сообщ.
#22
,
|
|
|
Цитата leo @ Вообще, не понятно почему 10-20 кнопок реализуются на обычных TButton (какого они должны быть размера или как нужно зашифровать\сократить их надписи, чтобы это выглядело нормально)? Может лучше использовать меню или панель инструментов? Может это игра в крестики нолики. |
Сообщ.
#23
,
|
|
|
Цитата Bas @ Может это игра в крестики нолики. Едва ли, там у каждой кнопки свой обработчик |
Сообщ.
#24
,
|
|
|
"Обработчик - это просто метод формы" - и этот метод вызывается по определенному СОБЫТИЮ. Какое событие придумать для селектора из 10 кнопок ? С удовольствиеем выслушаю...
"Если есть уже нужные обработчики - зачем этот огород?" - просто так нагляднее и не запутаешься. В принципе можно было и в Button1Click сделать селектор, но в этом обработчике начинаются всякие разные обращения к USB портам и так далее... Зачем усложнять отладку и вылавливание возможных в процессе эксплуатации ошибок ? Любое ветвление - уже само по себе потенциальный источник ошибок, если не предусмотрены все случаи , включая деление на ноль :-) Добавлено "Вообще, не понятно почему 10-20 кнопок реализуются на обычных TButton" На форме 5-7 панелей, одна наложена на другую, на каждой панели по 1-2 TButton, панели в определенный момент делаются visible. В результате форма не перегружается контролами, в видимой области в данный момент времени есть только то что нужно для работы. Что не так ? Да, редактирование формы в процессе отладки - еще тот процесс (растаскивание панелей по углам экрана), но что делать, кому сейчас легко :-) Вообще-то этот метод придумал не я, а один производитель некоего оборудования в одной японской лаборатории. Я просто подсмотрел, что форма остается статичной, меняются лишь панели с контролами. И сделал так же. Наверное получилось не хуже чем у японцев... |
Сообщ.
#25
,
|
|
|
Dmitry_Z, если ты хочешь назначить один обработчик на N контролов + выполнять код по какому-то условию, то "ветвлений" ты никак не избежишь в любом случае.
Код у тебя в итоге всё равно сведётся к условным переходам (jXX + call). Не заморачивайся на этом. Посмотри ещё раз посты #4 и #9 - проверка по "is" (защищённый код - "safe code" в оригинале) избавит тебя от ошибок в дальнейшем - например, если ты назначишь тот же обработчик контролам другого типа (скажем, TMenuItem'ам, дублирующим функциональность TButton'ов), или ты вообще не юзаешь VCL в каких-то частях своего кода, etc. |
Сообщ.
#26
,
|
|
|
Цитата Dmitry_Z @ На форме 5-7 панелей, одна наложена на другую... Что не так ? Да, редактирование формы в процессе отладки - еще тот процесс (растаскивание панелей по углам экрана), но что делать Использовать TPageControl без ярлычков (у каждой вкладки установить TabVisible = false). При этом видимость вкладок можно переключать по ActivePage как в рантайме, так и при дизайне\отладке формы. Цитата Dmitry_Z @ Какое событие придумать для селектора из 10 кнопок ? ... ... на каждой панели по 1-2 TButton, панели в определенный момент делаются visible По какому "событию" они делаются visible? Вот когда ты делаешь панель видимой, тогда и можно проверять условие доступности нажатия кнопок и устанавливать им Enabled:=true или false. (Причем делать это достаточно не для всех кнопок, а только для 1-2, которые расположены на данной панели, т.к. остальные по любому не видимы и соотв-но недоступны) Цитата Dmitry_Z @ В принципе можно было и в Button1Click сделать селектор, но в этом обработчике начинаются всякие разные обращения к USB портам и так далее... Зачем усложнять отладку и вылавливание возможных в процессе эксплуатации ошибок ? А в чем ты видишь усложнение? Тебе уже не раз говорили, что твой "селектор" нужно вынести в отдельный метод и вызывать его до начала "всяких разных обращений" type TForm1 = class(TForm) ... public function MySelector:boolean; ... end; function TForm1.MySelector:boolean; begin ... //проверка условий с установкой Result:=true или false; ... //+ возможен вывод сообщения через ShowMessage, чтобы юзер не ломал голову, почему кнопка не срабатывает end; procedure TForm1.Button1Click(Sender:TObect); begin if not MySelector then Exit; ... //всякие разные обращения к USB портам и так далее end; Никакого усложнения отладки тут нет, т.к. ошибка может возникнуть либо в общей функции MySelector, либо в коде обработки конкретной кнопки (на строках if not и Exit никакой ошибки в принципе быть не может). Цитата Dmitry_Z @ Любое ветвление - уже само по себе потенциальный источник ошибок, если не предусмотрены все случаи , включая деление на ноль :-) Угу, а ты вместо тривиальной проверки if not MySelector then Exit, упорно хочешь создать ветвистое дерево проверок на 10 условий, рискуя нарваться на дополнительные ошибки. |
Сообщ.
#27
,
|
|
|
Селектор сделать нужно в одной процедуре или функции. Потому как после этого она объявляется exports, далее в системе защиты StarForce она маркируется как защищенная. Почему плохо делать десять защищенных функций вместо одной - слишком долго расписывать. Я не пишу универсальную функцию на все случаи жизни, мне достаточно написать одну компактную и устойчивую к возможным ошибкам. Спасибо, над какими-то моментами из начала обсуждения топика я обязательно подумаю.
|
Сообщ.
#28
,
|
|
|
Цитата Dmitry_Z @ Селектор сделать нужно в одной процедуре или функции. Потому как после этого она объявляется exports, далее в системе защиты StarForce она маркируется как защищенная. И что мешает защитить саму функцию селектора? Она будет отвязана от ГУЯ и прекрасно может быть применена как в выносной DLL, так и в консоли (ежели вдруг решите писать тесты). А кнопочки должны служить только средством для запуска функций, и никакого "обращения к USB портам" в Button1Click быть не должно. P.S. Старфорс - мастдай! |