Почему это - плохая практика для вызова eventhandler от кода?

На всякий случай это кусает кого-то еще: Parallels Desktop для OS X захватывает F6 kbd> и использует его для «Показать и скрыть Parallels Desktop». Его можно отключить в Предпочтения -> Системные ярлыки OS X

73
задан jjb 5 June 2009 в 14:43
поделиться

9 ответов

Вопрос в том, как организована ваша программа. В описанном вами сценарии поведение пункта меню будет определяться в терминах кнопки:

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's 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;

Любая из этих трех реализаций будет работать, но почему пункт меню должен так зависеть от кнопки? Что такого особенного в кнопке, что она должна определять пункт меню? Если в новом дизайне пользовательского интерфейса не было кнопок, что будет с меню? Лучше всего выделить действия обработчика событий так, чтобы они не зависели от элементов управления, к которым он привязан. Есть несколько способов сделать это:

  1. Один из них - полностью избавиться от метода MenuItem1Click и назначить метод Button1Click для MenuItem1.OnClick свойство события. Сложно иметь методы, названные для кнопок, назначенных событиям пунктов меню, поэтому вы захотите переименовать обработчик событий, но это нормально, потому что, в отличие от VB, имена методов Delphi не определяют , какие события они обрабатывают. . Вы можете назначить любой метод любому обработчику событий, если подписи совпадают. События OnClick обоих компонентов относятся к типу TNotifyEvent , поэтому они могут совместно использовать одну реализацию. начать // Сделай что-нибудь. конец; процедура TJbForm.Button1Click (Отправитель: TObject); начать HandleClick; конец; процедура TJbForm.MenuItem1Click (Sender: TObject); начать HandleClick; конец;

    Таким образом, код, который действительно что-то делает, не привязан напрямую ни к одному из компонентов, а дает вам свободу изменять эти элементы управления более легко , например, переименовывая их или заменяя на различные элементы управления. Отделение кода от компонента приводит нас к третьему пути:

  2. Компонент TAction , представленный в Delphi 4, разработан специально для описанной вами ситуации, когда существует несколько путей пользовательского интерфейса к та же команда. (Другие языки и среды разработки предоставляют аналогичные концепции; это не уникально для Delphi.) Поместите свой код обработки событий в обработчик событий OnExecute TAction , а затем назначьте это действие для свойство Action как кнопки, так и пункта меню.

     procedure TJbForm.Action1Click (Sender:
    начать
     // Сделай что-нибудь
     // (В зависимости от того, насколько тесно поведение этого события связано с
     // манипулируя остальными элементами управления пользовательского интерфейса, это может сделать
     // смысл сохранить функцию HandleClick, о которой я упоминал выше.)
    конец;
    

    Хотите добавить еще один элемент пользовательского интерфейса, который действует как кнопка? Нет проблем. Добавьте его, установите его свойство Action , и все готово. Нет необходимости писать дополнительный код, чтобы новый элемент управления выглядел и действовал как старый. Вы уже писали этот код один раз.

    TAction выходит за рамки просто обработчиков событий. Это позволяет гарантировать, что элементы управления пользовательского интерфейса имеют одинаковые настройки свойств , включая заголовки, подсказки, видимость, включенность и значки. Если команда в данный момент недействительна, установите свойство действия Включено соответствующим образом, и все связанные элементы управления будут автоматически отключены. Не нужно беспокоиться о том, что команда отключена через панель инструментов, но все еще включена, например, через меню. Вы даже можете использовать действие ' s OnUpdate , чтобы действие могло обновляться в соответствии с текущими условиями, вместо того, чтобы вам нужно было знать, когда что-то происходит, что может потребовать от вас сразу установить свойство Enabled .

83
ответ дан 24 November 2019 в 12:19
поделиться

Потому что вы должны отделить внутреннюю логику от какой-то другой функции и вызывать эту функцию ...

  1. из обоих обработчиков событий
  2. отдельно от код, если вам нужно

Это более элегантное решение, и его намного проще поддерживать.

15
ответ дан 24 November 2019 в 12:19
поделиться

Это расширенный ответ, как и было обещано. В 2000 году мы начали писать приложение на Delphi. Это был один EXE и несколько DLL, содержащих логику. Это была киноиндустрия, поэтому были клиенты DLL, DLL бронирования, DLL кассы и DLL биллинга. Когда пользователь хотел выставить счет, он открывал соответствующую форму, выбирал клиента из списка, затем логика OnSelectItem загружала кинотеатры клиентов в следующее поле со списком, затем после выбора кинотеатра следующее событие OnSelectItem заполнило третье поле со списком с информацией о фильмах, которые не были выставлен счет еще. Последней частью процесса было нажатие кнопки «Выставить счет». Все было сделано как процедура события.

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

Через два года кто-то решил реализовать другую функцию - чтобы пользователь, работающий с данными о клиентах в другом модуле (модуле клиентов), был представлен с кнопкой с названием «Выставить счет этому клиенту». Эта кнопка должна активировать форму счета-фактуры и представить ее в таком состоянии, как если бы это был пользователь, который вручную выбирал все данные (пользователь должен был иметь возможность просматривать, вносить некоторые изменения и нажимать волшебную кнопку «Сделать счет-фактуру» ). Поскольку данные клиента были одной DLL, а биллинг - другой, сообщения передавал EXE. Таким образом, очевидная идея заключалась в том, что разработчик данных о клиентах будет иметь единую процедуру с одним идентификатором в качестве параметра, и что вся эта логика будет внутри модуля биллинга.
внести некоторые изменения и нажать волшебную кнопку «Сделать счет»). Поскольку данные клиента были одной DLL, а биллинг - другой, сообщения передавал EXE. Таким образом, очевидная идея заключалась в том, что разработчик данных о клиентах будет иметь единую процедуру с одним идентификатором в качестве параметра, и что вся эта логика будет внутри модуля биллинга.
внести некоторые изменения и нажать волшебную кнопку «Сделать счет»). Поскольку данные клиента были одной DLL, а биллинг - другой, сообщения передавал EXE. Таким образом, очевидная идея заключалась в том, что разработчик данных о клиентах будет иметь единую процедуру с одним идентификатором в качестве параметра, и что вся эта логика будет внутри модуля биллинга.
Представьте, что произошло. Поскольку ВСЯ логика находилась внутри обработчиков событий, мы потратили огромное количество времени, пытаясь фактически не реализовать логику, а пытаясь имитировать действия пользователя - например, выбор элементов, приостановку Application.MessageBox внутри обработчиков событий с использованием ГЛОБАЛЬНЫХ переменных и так далее. Представьте себе - если бы у нас были даже простые логические процедуры, вызываемые внутри обработчиков событий, мы могли бы ввести логическую переменную DoShowMessageBoxInsideProc в подпись процедуры. Такая процедура могла быть вызвана с параметром true, если вызывалась из обработчика событий, и с параметрами FALSE при вызове из внешнего места.

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

10
ответ дан 24 November 2019 в 12:19
поделиться

Еще одна важная причина - тестируемость. Когда код обработки событий скрыт в пользовательском интерфейсе, единственный способ проверить это - либо ручное тестирование, либо автоматическое тестирование, которое сильно привязано к пользовательскому интерфейсу. (например, откройте меню A, нажмите кнопку B). Любое изменение в пользовательском интерфейсе, естественно, может нарушить работу десятков тестов.

Если код преобразовать в модуль, который имеет дело исключительно с работой, которую он должен выполнять, то тестирование станет намного проще.

8
ответ дан 24 November 2019 в 12:19
поделиться

Разделение проблем. Частное событие для класса должно быть инкапсулировано внутри этого класса и не вызываться из внешних классов. Это упрощает внесение изменений в ваш проект в будущем, если у вас есть надежные интерфейсы между объектами и минимизируется вероятность появления нескольких точек входа.

8
ответ дан 24 November 2019 в 12:19
поделиться

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

Лично мне нравится, как Qt справляется с этим. Есть класс QAction с собственным обработчиком событий, который можно подключить,

8
ответ дан 24 November 2019 в 12:19
поделиться

Очевидно, аккуратнее. Но простота использования и производительность, конечно, также всегда важны.

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

Я знаю, что это не имеет значения в Lazarus / Delphi. Другие языки могут иметь более особое поведение, связанное с обработчиками событий.

4
ответ дан 24 November 2019 в 12:19
поделиться

Почему это плохая практика? Потому что намного проще повторно использовать код, если он не встроен в элементы управления пользовательского интерфейса.

Почему вы не можете сделать это в REALbasic? Я сомневаюсь, что есть какая-то техническая причина; Вероятно, это просто дизайнерское решение, которое они приняли. Это, безусловно, способствует более совершенным методам кодирования.

2
ответ дан 24 November 2019 в 12:19
поделиться

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

С другой стороны, если вы вызываете функцию, вы с меньшей вероятностью измените то, что она делает, поскольку вы (или следующий парень) знаете, что это будет иметь плохие последствия.

1
ответ дан 24 November 2019 в 12:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: