为什么从代码中调用事件处理程序是不好的做法?

发布于 2024-07-24 01:11:35 字数 144 浏览 8 评论 0原文

假设您有一个菜单项和一个按钮可以执行相同的任务。 为什么将任务代码放入一个控件的操作事件中然后从另一个控件调用该事件是一种不好的做法? Delphi 和 vb6 一样允许这样做,但 realbasic 不允许这样做,并表示您应该将代码放入一个方法中,然后由菜单和按钮调用

Say you have a menu item and a button that do the same task.
Why is it bad practice to put the code for the task into one control's action event and then make a call to that event from the other control?
Delphi allows this as does vb6 but realbasic doesn't and says you should put the code into a method that is then called by both the menu and the button

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

遗失的美好 2024-07-31 01:11:35

这是你的程序如何组织的问题。 在您描述的场景中,菜单项的行为将根据按钮的行为进行定义:

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;

这三种实现中的任何一种都可以工作,但是为什么菜单项如此依赖于按钮?该按钮如此特别以至于它应该定义菜单项? 如果新的 UI 设计取消了按钮,菜单会发生什么变化? 更好的方法是分解事件处理程序的操作,使其独立于其附加的控件。 有几种方法可以做到这一点:

  1. 其中一种方法是完全删除 MenuItem1Click 方法,并将 Button1Click 方法分配给 MenuItem1.OnClick< /code> 事件属性。 为分配给菜单项事件的按钮命名的方法会令人困惑,因此您需要重命名事件处理程序,但这没关系,因为与 VB 不同,Delphi 的方法名称不定义它们所处理的事件处理。 只要签名匹配,您就可以将任何方法分配给任何事件处理程序。 两个组件的 OnClick 事件都是 TNotifyEvent 类型,因此它们可以共享一个实现。 根据它们的用途而不是它们所属的内容来命名方法。

  2. 另一种方法是将按钮的事件处理程序代码移至单独的方法中,然后从两个组件的事件处理程序中调用该方法:

    过程 HandleClick; 
      开始 
        // 做一点事。 
      结尾; 
    
      过程 TJbForm.Button1Click(发件人: TObject); 
      开始 
        处理点击; 
      结尾; 
    
      过程 TJbForm.MenuItem1Click(Sender: TObject); 
      开始 
        处理点击; 
      结尾; 
      

    这样,真正起作用的代码就不会直接与任何一个组件绑定,并且让您可以更轻松地自由地更改这些控件,例如通过重命名它们或替换它们具有不同的控制。 将代码与组件分离导致我们采用第三种方法:

  3. Delphi 4 中引入的 TAction 组件专门针对您所描述的情况而设计,其中存在多个 UI 路径相同的命令。 (其他语言和开发环境提供类似的概念;这并非 Delphi 所独有。)将事件处理代码放入 TActionOnExecute 事件处理程序中,然后分配该代码按钮和菜单项的 Action 属性的操作。

    过程 TJbForm.Action1Click(Sender: TObject); 
      开始 
        // 做一点事 
        //(取决于该事件的行为与 
        // 操纵其余的 UI 控件,可能会导致 
        // 保留我上面提到的 HandleClick 函数的意义。) 
      结尾; 
      

    想要添加另一个像按钮一样的 UI 元素吗? 没问题。 添加它,设置其 Action 属性,然后就完成了。 无需编写更多代码即可使新控件的外观和行为与旧控件类似。 您已经编写过该代码一次。

    TAction 不仅仅是事件处理程序。 它可以让您确保 UI 控件具有统一的属性设置,包括标题、提示、可见性、启用性和图标。 当命令当时无效时,相应地设置操作的 Enabled 属性,任何链接的控件将自动禁用。 例如,无需担心命令通过工具栏被禁用,但仍可通过菜单启用。 您甚至可以使用操作的 OnUpdate 事件,以便操作可以根据当前条件自行更新,而无需知道何时发生可能需要您设置 Enabled 属性。

It's a question of how your program is organized. In the scenario you've described, the menu item's behavior will be defined in terms of the button's:

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;

Any of those three implementations will work, but why should the menu item be so dependent on the button? What's so special about the button that it should define the menu item? If a new UI design did away with buttons, what would happen to the menu? A better way is to factor out the event handler's actions so it's independent of the controls it's attached to. There are a few ways to do that:

  1. One is to get rid of the MenuItem1Click method altogether and assign the Button1Click method to the MenuItem1.OnClick event property. It's confusing to have methods named for buttons assigned to menu items' events, so you'll want to rename the event handler, but that's OK, because unlike VB, Delphi's method names do not define what events they handle. You can assign any method to any event handler as long as the signatures match. Both components' OnClick events are of type TNotifyEvent, so they can share a single implementation. Name methods for what they do, not what they belong to.

  2. Another way is to move the button's event-handler code into a separate method, and then call that method from both components' event handlers:

    procedure HandleClick;
    begin
      // Do something.
    end;
    
    procedure TJbForm.Button1Click(Sender: TObject);
    begin
      HandleClick;
    end;
    
    procedure TJbForm.MenuItem1Click(Sender: TObject);
    begin
      HandleClick;
    end;
    

    This way, the code that really does stuff isn't tied directly to either component, and that gives you the freedom to change those controls more easily, such as by renaming them, or replacing them with different controls. Separating the code from the component leads us to the third way:

  3. The TAction component, introduced in Delphi 4, is designed especially for the situation you've described, where there are multiple UI paths to the same command. (Other languages and development environments provide similar concepts; it's not unique to Delphi.) Put your event-handling code in the TAction's OnExecute event handler, and then assign that action to the Action property of both the button and the menu item.

    procedure TJbForm.Action1Click(Sender: TObject);
    begin
      // Do something
      // (Depending on how closely this event's behavior is tied to
      // manipulating the rest of the UI controls, it might make
      // sense to keep the HandleClick function I mentioned above.)
    end;
    

    Want to add another UI element that acts like the button? No problem. Add it, set its Action property, and you're finished. No need to write more code to make the new control look and act like the old one. You've already written that code once.

    TAction goes beyond just event handlers. It lets you ensure that your UI controls have uniform property settings, including captions, hints, visibility, enabledness, and icons. When a command isn't valid at the time, set the action's Enabled property accordingly, and any linked controls will automatically get disabled. No need to worry about a command being disabled through the tool bar, but still enabled through the menu, for example. You can even use the action's OnUpdate event so that the action can update itself based on current conditions, instead of you needing to know whenever something happens that might require you to set the Enabled property right away.

深居我梦 2024-07-31 01:11:35

您应该将内部逻辑与其他函数分开,并

  1. 从两个事件处理程序中
  2. 因为如果需要,

与代码分开调用此函数...这是一个更优雅的解决方案,并且更容易维护。

Because you should separate internal logic to some other function and call this function...

  1. from both event handlers
  2. separately from code if you need to

This is a more elegant solution and is much easier to maintain.

一身仙ぐ女味 2024-07-31 01:11:35

正如所承诺的,这是一个扩展答案。
2000年我们开始使用Delphi编写应用程序。 这是一个包含逻辑的 EXE 和几个 DLL。 这是电影行业,所以有客户 DLL、预订 DLL、票房 DLL 和计费 DLL。 当用户想要计费时,他打开适当的表单,从列表中选择客户,然后 OnSelectItem 逻辑将客户剧院加载到下一个组合框,然后选择剧院后,下一个 OnSelectItem 事件用有关电影的信息填充第三个组合框,该信息还没有被填充尚未计费。 该过程的最后一部分是按下“开具发票”按钮。 一切都是作为事件过程完成的。

然后有人决定我们应该提供广泛的键盘支持。 我们添加了从另一个事件处理程序调用事件处理程序。事件处理程序的工作流程开始变得复杂。

两年后,有人决定实现另一个功能——以便在另一个模块(客户模块)中处理客户数据的用户应该看到一个标题为“为该客户开票”的按钮。 此按钮应触发发票表单并以这样的状态呈现它,就像用户手动选择所有数据一样(用户能够查看、进行一些调整,然后按神奇的“开具发票”按钮) )。 由于客户数据是一个 DLL,而计费是另一个,因此是 EXE 传递消息。 因此,显而易见的想法是客户数据开发人员将拥有以单个 ID 作为参数的单个例程,并且所有这些逻辑都将位于计费模块内。
想象一下发生了什么。 由于所有逻辑都位于事件处理程序内部,因此我们花费了大量时间,尝试实际上不实现逻辑,而是尝试模仿用户活动 - 例如选择项目、使用 GLOBAL 变量在事件处理程序内挂起 Application.MessageBox 等等。 想象一下 - 如果我们在事件处理程序内部调用简单的逻辑过程,我们就能够将 DoShowMessageBoxInsideProc 布尔变量引入到过程签名中。 如果从事件处理程序调用,则可以使用 true 参数来调用此类过程;如果从外部位置调用,则可以使用 FALSE 参数来调用此类过程。

因此,这教会了我不要将逻辑直接放入 GUI 事件处理程序中,小项目可能除外。

This is an extension answer, as promised.
In 2000 we have started to write an application using Delphi. This was one EXE and few DLL’s containing logic. This was movie industry, so there was customers DLL, booking DLL, box office DLL and billing DLL. When user wanted to do billing, he opened appropriate form, selected customer from a list, then OnSelectItem logic loaded customers theaters to next combo box, then after selecting theater next OnSelectItem event filled third combo box with information about the movies, that has not been billed yet. Last part of the process was pushing the button “Do Invoice”. Everything was done as an event procedures.

Then someone decided we should have extensive keyboard support. We have added calling event handlers from another even handlers.. The workflow of event handlers begun to complicate.

After two years someone decided to implement another feature – so that user working with customer data in another module (customers module) should be presented with a button titled “Invoice this customer”. This button should fire the invoice form and present it in such a state, like it was user who have been manually selecting all the data (the user was to be able to look at, make some adjustments, and press magic “Do Invoice” button). Since customer data was one DLL and billing was another, it was EXE that was passing messages. So the obvious idea was that customer data developer will have single routine with single ID as a parameter, and that all this logic will be inside billing module.
Imagine what happened. Since ALL logic was inside event handlers, we spent huge amount of time, trying actually not implement logic, but trying to mimic user activity – like choosing items, suspending Application.MessageBox inside event handlers using GLOBAL variables, and so one. Imagine – if we had even simple logic procedures called inside event handlers, we would have been able to introduce DoShowMessageBoxInsideProc Boolean variable to the procedure signature. Such a procedure could have been called with true parameter if called from event handler, and with FALSE parameters when called from external place.

So this is what have taught me not to put logic directly inside GUI event handlers, with a possible exception of small projects.

哎呦我呸! 2024-07-31 01:11:35

关注点分离。类的私有事件应该封装在该类中,而不是从外部类调用。 如果您在对象之间具有强大的接口并最大限度地减少多个入口点的出现,这将使您的项目更容易更改。

Separation of concerns. A private event for a class should be encapsulated within that class and not called from external classes. This makes your project easier to change down the road if you have strong interfaces between objects and minimize the occurences of multiple entry points.

我是有多爱你 2024-07-31 01:11:35

假设在某个时候您认为该菜单项不再有意义,并且您想要删除该菜单项。 如果您只有一个其他控件指向菜单项的事件处理程序,这可能不是一个大问题,您只需将代码复制到按钮的事件处理程序中即可。 但是,如果您有多种不同的方式可以调用代码,则必须进行大量更改。

我个人喜欢 Qt 处理这个问题的方式。 有一个 QAction 类,它有自己的事件处理程序,可以被挂钩,然后 QAction 与需要执行该任务的任何 UI 元素相关联。

Suppose at some point you decide that the menu item no longer makes sense, and you want to get rid of the menu item. If you just have one other control pointing to the menu item's event handler, that might not be a big problem, you can just copy the code into the button's event handler. But if you have several different ways the code can be invoked, you'll have to do a lot of changing.

Personally I like the way Qt handles this. There is a QAction class with it's own event handler that can be hooked, and then the QAction is associated with any UI elements that need to perform that task.

猫腻 2024-07-31 01:11:35

另一个重要原因是可测试性。 当事件处理代码隐藏在 UI 中时,测试它的唯一方法是通过与 UI 密切相关的手动测试或自动化测试。 (例如打开菜单A,单击按钮B)。 UI 中的任何更改自然都会破坏数十个测试。

如果代码被重构为专门处理它需要执行的工作的模块,那么测试就会变得容易得多。

Another big reason is for testability. When event handling code is buried in the UI, the only way to test this is via either manual testing or automated testing that is heavily tied to the UI. (e.g. Open menu A, Click button B). Any change in the UI naturally can then break dozens of tests.

If the code is refactored into a module that deals exclusively with the job it needs to perform, then testing become a whole lot easier.

给我一枪 2024-07-31 01:11:35

显然更整洁了。 但易用性和生产力当然也始终很重要。

在 Delphi 中,我通常会在严肃的应用程序中避免使用它,但我会在小东西中调用事件处理程序。 如果小事情以某种方式演变成更大的事情,我会清理它,并且通常同时增加逻辑-UI 分离。

我确实知道这在 Lazarus/Delphi 中并不重要。 其他语言可能有更多附加到事件处理程序的特殊行为。

It is neater obviously. But ease of use and productivity is of course also always important.

In Delphi I generally refrain from it in serious apps, but I call eventhandlers in small stuff. If small stuff somehow morphes into something bigger, I clean it up, and usually at the same time increase logic-UI separation.

I do know though that it won't matter in Lazarus/Delphi. Other languages might have more special behaviour attached to eventhandlers.

满身野味 2024-07-31 01:11:35

为什么这是不好的做法? 因为当代码未嵌入到 UI 控件中时,重用代码要容易得多。

为什么不能在 REALbasic 中做到这一点? 我怀疑有什么技术原因; 这可能只是他们做出的设计决定。 它确实强制执行更好的编码实践。

Why is it bad practice? Because it is much easier to reuse code when it is not embedded into UI controls.

Why can't you do it in REALbasic? I doubt there's any technical reason; it's likely just a design decision they made. It certainly does enforce better coding practices.

一枫情书 2024-07-31 01:11:35

假设某个时候您决定菜单应该做一些稍微不同的事情。 也许这种新的变化只会在某些特定情况下发生。 您忘记了该按钮,但现在您也更改了其行为。

另一方面,如果您调用一个函数,您就不太可能改变它的功能,因为您(或下一个人)知道这会产生不良后果。

Suppose at some time you decided that the menu should do something slightly differently. Perhaps this new change only happens under some specific circumstances. You forget about the button, but now you have changed its behaviour as well.

On the other hand if you call a function, you are less likely to change what it does, since you (or the next guy) knows that this will have bad consequences.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文