为什么从代码中调用eventhandler是不好的做法?

jjb*_*jjb 73 delphi vb6 coding-style realbasic

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

Rob*_*edy 83

这是一个关于你的计划如何组织的问题.在您描述的场景中,菜单项的行为将根据按钮的定义:

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;
Run Code Online (Sandbox Code Playgroud)

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

  1. 一种是MenuItem1Click完全摆脱该方法并将该Button1Click方法分配给MenuItem1.OnClickevent属性.将按钮命名的方法分配给菜单项的事件是令人困惑的,因此您需要重命名事件处理程序,但这没关系,因为与VB不同,Delphi的方法名称不定义它们处理的事件.只要签名匹配,您就可以将任何方法分配给任何事件处理程序.两个组件的OnClick事件都是类型的TNotifyEvent,因此它们可以共享一个实现.为他们所做的事情命名方法,而不是他们所属的.

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

    procedure HandleClick;
    begin
      // Do something.
    end;
    
    procedure TJbForm.Button1Click(Sender: TObject);
    begin
      HandleClick;
    end;
    
    procedure TJbForm.MenuItem1Click(Sender: TObject);
    begin
      HandleClick;
    end;
    
    Run Code Online (Sandbox Code Playgroud)

    这样,真正做事的代码并不直接与任何一个组件绑定,这使您可以更轻松地更改这些控件,例如通过重命名或用不同的控件替换它们.将代码与组件分离导致我们采用第三种方式:

  3. TActionDelphi 4中引入的组件专门针对您所描述的情况而设计,其中有多个UI路径指向同一命令.(其他语言和开发环境提供了类似的概念,它不是唯一的德尔福.)放入你的事件处理代码TActionOnExecute事件处理程序,然后分配到行动的Action两个按钮和菜单项的属性.

    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;
    
    Run Code Online (Sandbox Code Playgroud)

    想要添加另一个像按钮一样的UI元素吗?没问题.添加它,设置它的Action属性,然后你就完成了.无需编写更多代码来使新控件看起来像旧的一样.你已经编写了一次代码.

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

  • @jjb:即使他们的行为不可用,也不会禁用控件ATM会造成一个非常令人困惑的用户界面恕我直言.但是,由于禁用的控件确实使UI不易被发现,因此当鼠标悬停在禁用的控件上时,应该有一些原因指示,例如工具提示或状态栏帮助消息.我更喜欢这种方法,而不是UI,它没有表明它所处的状态. (3认同)

smo*_*ok1 15

因为您应该将内部逻辑与其他函数分开并调用此函数...

  1. 来自两个事件处理程序
  2. 如果需要,可以与代码分开

这是一种更优雅的解决方案,更易于维护.

  • 答案是B更优雅的解决方案,更容易维护,来自我个人的经验.事实上,自己的个人经验并不是认为你能用硬数据来证明,这是体验事物和科学证明之间的区别.在谈论优雅时......你无法定义它,你只能感受到它......最终提到Steve McConnell的"Code Complete",他对这些问题有很好的报道. (2认同)

smo*_*ok1 10

正如所承诺的那样,这是一个扩展答案.2000年,我们开始使用Delphi编写应用程序.这是一个EXE,很少有DLL包含逻辑.这是电影业,所以有客户DLL,预订DLL,票房DLL和计费DLL.当用户想要进行计费时,他打开了相应的表单,从列表中选择了客户,然后OnSelectItem逻辑将客户影院加载到下一个组合框,然后选择影院后,OnSelectItem事件填充第三个组合框,其中包含有关电影的信息,尚未收费了.该过程的最后一部分是按下"Do Invoice"按钮.一切都是作为活动程序完成的.

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

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

所以这就是教我不要将逻辑直接放在GUI事件处理程序中的原因,可能的例外是小项目.

  • 感谢您提出这个。我认为这清楚地说明了你所表达的观点。我喜欢布尔参数的想法,它允许事件实际发生时的不同行为,而不是通过代码完成。 (2认同)

Chr*_*nce 8

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


Ger*_*ald 8

假设在某些时候你决定菜单项不再有意义,你想要摆脱菜单项.如果您只有另一个控件指向菜单项的事件处理程序,这可能不是一个大问题,您只需将代码复制到按钮的事件处理程序中即可.但是,如果您可以通过几种不同的方式调用代码,则必须进行大量更改.

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

  • 德尔福也可以这样做.为menuitem和按钮分配一个动作 - 我一直在为镜像菜单功能的工具栏按钮执行此操作. (3认同)

小智 8

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

如果将代码重构为一个专门处理它需要执行的作业的模块,那么测试变得更加容易.