Скажите, что у вас есть пункт меню и кнопка, выполняющая ту же задачу. Почему это плохая практика, чтобы поставить код для задачи в одно событие управления, а затем сделать вызов этого события из другого элемента управления? Delphi позволяет это, как и vb6, но realbasic не делает и говорит, что вы должны поместить код в метод, который затем вызывается как меню, так и кнопки
Почему это плохая практика, чтобы вызвать обработчик событий из кода?
Ответ 1
Это вопрос о том, как организована ваша программа. В описанном вами сценарии поведение элемента меню будет определено в терминах кнопки:
procedure TJbForm.MenuItem1Click(Sender: TObject);
begin
// Three different ways to write this, with subtly different
// ways to interpret it:
Button1Click(Sender);
// 1. "Call some other function. The name suggests it the
// function that also handles button clicks."
Button1.OnClick(Sender);
// 2. "Call whatever method we call when the button gets clicked."
// (And hope the property isn't nil!)
Button1.Click;
// 3. "Pretend the button was clicked."
end;
Любая из этих трех реализаций будет работать, но почему элемент меню будет так зависеть от кнопки?. Что так особенного в кнопке, что он должен определить пункт меню? Если новый дизайн пользовательского интерфейса покончил с кнопками, что произойдет с меню? Лучший способ состоит в том, чтобы исключить действия обработчика событий, чтобы он не зависел от элементов управления, к которым он привязан. Есть несколько способов сделать это:
-
Один из них заключается в том, чтобы полностью избавиться от метода
MenuItem1Click
и присвоить методуButton1Click
свойству событияMenuItem1.OnClick
. Сложно использовать методы, названные для кнопок, назначенных для событий пунктов меню, поэтому вы захотите переименовать обработчик событий, но это нормально, потому что в отличие от VB имена методов Delphi не определяют, какие события они обрабатывают. Вы можете назначить любой метод любому обработчику событий, пока совпадают подписи. События двух компонентовOnClick
имеют типTNotifyEvent
, поэтому они могут совместно использовать одну реализацию. Назовите методы того, что они делают, а не то, к чему они принадлежат. -
Другой способ - переместить код события-обработчика кнопки в отдельный метод, а затем вызвать этот метод из обработчиков событий обоих компонентов:
procedure HandleClick; begin // Do something. end; procedure TJbForm.Button1Click(Sender: TObject); begin HandleClick; end; procedure TJbForm.MenuItem1Click(Sender: TObject); begin HandleClick; end;
Таким образом, код, который действительно делает материал, напрямую не привязан к любому компоненту, а дает вам свободу более легко изменять эти элементы управления, например, переименовывая их или заменяя их с различными элементами управления. Разделение кода от компонента приводит нас к третьему пути:
-
Компонент
TAction
, представленный в Delphi 4, разработан специально для описанной вами ситуации, где есть несколько путей пользовательского интерфейса к той же команде. (Другие языки и среды разработки предоставляют аналогичные концепции, они не уникальны для Delphi.) Поместите код обработки событий в обработчик событийTAction
OnExecute
, а затем назначьте это действие свойствуAction
как кнопки, так и пункт меню.procedure TJbForm.Action1Click(Sender: TObject); begin // Do something // (Depending on how closely this event behavior is tied to // manipulating the rest of the UI controls, it might make // sense to keep the HandleClick function I mentioned above.) end;
Хотите добавить еще один элемент пользовательского интерфейса, который действует как кнопка? Нет проблем. Добавьте его, установите его свойство
Action
, и вы закончите. Не нужно писать больше кода, чтобы заставить новый элемент управления выглядеть и действовать как старый. Вы уже написали этот код один раз.TAction
выходит за рамки только обработчиков событий. Это позволяет гарантировать, что ваши элементы пользовательского интерфейса имеют единые настройки свойств, включая титры, подсказки, видимость, включенность и значки. Если в тот момент команда недействительна, установите соответствующее действиеEnabled
, и любые связанные элементы управления будут автоматически отключены. Не нужно беспокоиться о том, что команда отключена с помощью панели инструментов, но все же включена через меню, например. Вы даже можете использовать событие actionOnUpdate
, чтобы действие могло обновляться на основе текущих условий, а не вам нужно знать, когда что-то происходит, что может потребовать сразу установить свойствоEnabled
.
Ответ 2
Поскольку вы должны отделить внутреннюю логику к какой-либо другой функции и вызвать эту функцию...
- из обоих обработчиков событий
- отдельно от кода, если вам нужно
Это более элегантное решение и гораздо проще поддерживать.
Ответ 3
Это ответ на добавочный номер, как и было обещано. В 2000 году мы начали писать приложение с помощью Delphi. Это был один EXE и несколько DLL, содержащих логику. Это была индустрия кино, поэтому были DLL клиентов, бронирование DLL, DLL в прокате и DLL для выставления счетов. Когда пользователь хотел сделать биллинг, он открыл соответствующую форму, выбранный клиент из списка, затем логику OnSelectItem загрузили театры клиентов в следующую комбинированную коробку, а затем после выбора следующего театрального события OnSelectItem заполнил третий комбинированный блок с информацией о фильмах, которая не была выставлен счет. Последняя часть процесса нажала кнопку "Сделать счет". Все было сделано как процедура событий.
Затем кто-то решил, что мы должны иметь обширную поддержку клавиатуры. Мы добавили обработчики вызовов из других четных обработчиков. Рабочий процесс обработчиков событий начал усложняться.
Через два года кто-то решил реализовать еще одну функцию - чтобы пользователь, работающий с данными клиента в другом модуле (модуль клиентов), должен был быть представлен кнопкой "Счет-фактура этого клиента". Эта кнопка должна запускать форму счета-фактуры и представлять ее в таком состоянии, как пользователь, который вручную выбирает все данные (пользователь должен был иметь возможность смотреть, вносить какие-то коррективы и нажимать кнопку "Сделать счет-фактуру" )). Поскольку данные клиента были одной DLL, а биллинг был другой, EXE передавал сообщения. Таким образом, очевидная идея заключалась в том, что разработчик данных клиента будет иметь единую процедуру с единственным идентификатором в качестве параметра и что вся эта логика будет находиться в модуле выставления счетов.
Представьте, что произошло. Поскольку логика ALL находилась внутри обработчиков событий, мы потратили огромное количество времени, пытаясь фактически не реализовывать логику, но пытаемся имитировать активность пользователя - например, выбирать элементы, приостанавливая Application.MessageBox внутри обработчиков событий с использованием переменных GLOBAL и т.д. Представьте себе - если бы у нас были даже простые логические процедуры, называемые внутри обработчиков событий, мы могли бы ввести логику DoShowMessageBoxInsideProc Boolean в подпись процедуры. Такая процедура могла быть вызвана с истинным параметром, если вызвана из обработчика события, и с параметрами FALSE при вызове из внешнего места.
Таким образом, это научило меня не вводить логику непосредственно в обработчики событий GUI, за исключением небольших проектов.
Ответ 4
Разделение проблем. Частное событие для класса должно быть инкапсулировано внутри этого класса и не вызываться из внешних классов. Это упростит ваш проект в будущем, если у вас есть сильные интерфейсы между объектами и минимизированы случаи появления нескольких точек входа.
Ответ 5
Предположим, что в какой-то момент вы решили, что пункт меню больше не имеет смысла, и вы хотите избавиться от пункта меню. Если у вас есть еще один элемент управления, указывающий на обработчик событий элемента меню, это может быть не большая проблема, вы можете просто скопировать код в обработчик событий кнопки. Но если у вас есть несколько разных способов, код может быть вызван, вам придется много изменить.
Лично мне нравится, как Qt справляется с этим. Существует класс QAction с его собственным обработчиком событий, который может быть подключен, а затем QAction связан с любыми элементами пользовательского интерфейса, которые должны выполнять эту задачу.
Ответ 6
Другая важная причина заключается в проверке. Когда код обработки событий похоронен в пользовательском интерфейсе, единственный способ проверить это - это либо ручное тестирование, либо автоматическое тестирование, которое сильно связано с пользовательским интерфейсом. (например, откройте меню A, нажмите кнопку B). Любое изменение в пользовательском интерфейсе естественно может сломать десятки тестов.
Если код рефакторизуется в модуль, который занимается исключительно заданием, которое ему нужно выполнить, то тестирование становится намного проще.
Ответ 7
Совершенно очевидно. Но, конечно же, всегда важна простота использования и производительности.
В Delphi я вообще воздерживаюсь от него в серьезных приложениях, но я вызываю обработчики событий в мелочах. Если мелкие вещи каким-то образом превращаются в нечто большее, я очищаю его и, как правило, в то же время увеличиваю разделение логического интерфейса.
Я действительно знаю, что это не имеет значения в Лазаресе/Дельфи. Другие языки могут иметь более специфическое поведение, связанное с обработчиками событий.
Ответ 8
Почему это плохая практика? Потому что гораздо проще повторно использовать код, если он не встроен в элементы управления пользовательским интерфейсом.
Почему вы не можете сделать это в REALbasic? Я сомневаюсь, что есть какие-то технические причины; это скорее всего дизайнерское решение, которое они сделали. Это, безусловно, обеспечивает соблюдение лучших правил кодирования.
Ответ 9
Предположим, что в какой-то момент вы решили, что меню должно делать что-то немного по-другому. Возможно, это новое изменение происходит только при определенных обстоятельствах. Вы забываете о кнопке, но теперь вы также изменили ее поведение.
С другой стороны, если вы вызываете функцию, у вас меньше шансов изменить то, что она делает, поскольку вы (или следующий парень) знаете, что это будет иметь плохие последствия.