假设您有一个菜单项和一个按钮执行相同的任务。为什么将该任务的代码放入一个控件的action事件中,然后从另一个控件对该事件进行调用,这是一种不好的做法?Delphi允许这样做,而vb6允许,但是realbasic不允许,并说您应该将代码放入方法中,然后由菜单和按钮调用
Answers:
这是程序的组织方式的问题。在您描述的场景中,菜单项的行为将根据按钮的定义:
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设计取消了按钮,菜单将如何处理?更好的方法是排除事件处理程序的操作,使其独立于其附加的控件。有几种方法可以做到这一点:
一种是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中引入的组件是专门为您所描述的情况而设计的,在该情况下,同一命令有多个UI路径。(其他语言和开发环境提供了类似的概念;这并不是Delphi独有的。)将事件处理代码放入TAction
的OnExecute
事件处理程序中,然后将该操作分配给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;
是否要添加另一个类似于按钮的UI元素?没问题。添加它,设置它的Action
属性,您就完成了。无需编写更多代码即可使新控件的外观和行为与旧控件相似。您已经编写了该代码一次。
TAction
不仅仅是事件处理程序。它使您可以确保UI控件具有统一的属性设置,包括标题,提示,可见性,启用性和图标。当命令当时无效时,请相应地设置操作的Enabled
属性,所有链接的控件都将自动被禁用。例如,无需担心通过工具栏禁用命令,但是仍然可以通过菜单启用命令。您甚至可以使用该操作的OnUpdate
事件,以便该操作可以根据当前条件进行自身更新,而不用需要知道何时发生某些事情可能需要您立即设置该Enabled
属性。
因为您应该将内部逻辑与其他函数分开,然后调用此函数...
这是一个更优雅的解决方案,并且易于维护。
如所承诺的,这是一个扩展答案。在2000年,我们开始使用Delphi编写应用程序。这是一个EXE,而包含逻辑的DLL却很少。这是电影业,因此有客户DLL,预订DLL,票房DLL和计费DLL。当用户想要进行计费时,他打开适当的表单,从列表中选择客户,然后OnSelectItem逻辑将客户剧院加载到下一个组合框,然后在选择剧院后,下一个OnSelectItem事件用有关电影的信息填充了第三个组合框,但尚未尚未结帐。该过程的最后一部分是按“做发票”按钮。一切都作为事件过程完成。
然后有人决定我们应该有广泛的键盘支持。我们从另一个事件处理程序中添加了调用事件处理程序。事件处理程序的工作流程开始变得复杂。
两年后,有人决定实施另一种功能–因此,在另一个模块(客户模块)中处理客户数据的用户应看到一个标题为“为该客户开具发票”的按钮。此按钮应触发发票表单并以这种状态显示,就像是用户一直在手动选择所有数据一样(该用户可以查看,进行一些调整,然后按神奇的“执行发票”按钮)。由于客户数据是一个DLL,而计费是另一个,因此EXE传递消息。因此,显而易见的想法是,客户数据开发人员将使用带有单个ID作为参数的单个例程,并且所有这些逻辑都将在计费模块中。
想象发生了什么。由于ALL逻辑位于事件处理程序内部,因此我们花费了大量时间,实际上不执行逻辑,而是尝试模仿用户活动-例如选择项,使用GLOBAL变量将事件中的Application.MessageBox挂起,等等。想象一下–如果我们甚至在事件处理程序中调用了简单的逻辑过程,我们就可以将DoShowMessageBoxInsideProc布尔变量引入过程签名中。如果从事件处理程序中调用,则可以使用true参数来调用此过程,而从外部位置调用时可以使用FALSE参数来调用。
因此,这使我学会了不要将逻辑直接放在GUI事件处理程序中,除了小型项目以外。
关注点分离。 一个类的私有事件应该封装在该类内,而不是从外部类调用。如果对象之间具有牢固的接口,这将使您的项目更容易更改,并最大程度地减少了多个入口点的出现。
Implements
关键字实现接口的Private
方法类似,默认情况下类似于为实现创建方法的方法,不同之处在于事件和事件处理程序经过特殊处理(即,不必为类公开的所有事件实现处理程序,编译器都会插入空事件处理程序)。
假设您决定某个菜单项不再有意义,并且想要摆脱该菜单项。如果只有另一个控件指向菜单项的事件处理程序,那么这可能不是一个大问题,只需将代码复制到按钮的事件处理程序中即可。但是,如果有几种不同的方式可以调用代码,则必须进行很多更改。
我个人喜欢Qt处理此问题的方式。有一个QAction类带有它自己的事件处理程序,可以将其挂钩,然后将QAction与需要执行该任务的任何UI元素相关联。
为什么这是不好的做法?因为当代码未嵌入到UI控件中时,重用代码要容易得多。
您为什么不能在REALbasic中做到这一点?我怀疑有任何技术原因;这可能只是他们做出的设计决定。当然,它确实可以实施更好的编码实践。
假设您决定某个时间菜单应该做些不同的事情。也许仅在某些特定情况下才会发生此新更改。您忘记了该按钮,但是现在您也更改了它的行为。
另一方面,如果调用一个函数,则更改它的功能的可能性较小,因为您(或下一个家伙)知道这将带来严重的后果。
cooked up
很多spaghetti
应用程序,这些都是维护工作的噩梦,但是由于这些应用程序相当不错,所以很遗憾。但是我开始讨厌自己的创造。IMO,Rob的回答确实非常详尽。