【问题标题】:Delphi: How to call a method when i click a control?Delphi:单击控件时如何调用方法?
【发布时间】:2010-05-13 19:53:38
【问题描述】:

我有一个方法:

procedure Frob(Sender: TObject);

当我点击一个菜单项时我想调用它。

这个方法是通过一个界面来找我的:

animal: IAnimal;

IAnimal = interface
   procedure Frob(Sender: TObject);
end;

问题围绕着为菜单项(即控件)的OnClick 事件处理程序分配什么:

var
   animal: IAnimal;
   ...
begin
   ...
   menuItem := TMenuItem.Create(FileMenu)
   menuItem.Caption := 'Click me!';
   menuItem.OnClick :=  <-------- what to do
   ...
end;

显而易见的选择,my first attempt, and the wrong answer is

   menuItem.OnClick := animal.Frob;

那么当用户点击控件时如何调用方法?

另见

【问题讨论】:

    标签: delphi events event-handling interface


    【解决方案1】:

    让你的任何对象将动物保存在一个私有字段中,然后设置一个调用它的方法。像这样:

    procedure TMyClass.AnimalFrob(Sender: TObject);
    begin
       FAnimal.Frob(sender);
    end;
    

    那么解决方法就变得简单了:

    menuItem.OnClick := self.AnimalFrob;

    【讨论】:

    • 在我的情况下,我有 n 个项目 - 从提供的接口 IInterfaceList 填充。我想这意味着将 n 个项目存储在 n 个私有字段中(又名对象的 TList)。
    • @Ian:嗯,你如何适应它取决于你,但这是基本的想法。创建一个与签名匹配的方法,并知道在哪个接口上调用该方法,并让它为您转发调用。
    • 如果您有成群的动物从界面列表中向您扑来,我假设您也在运行时创建了 TMenuItems。因此,我建议声明您自己的 TMenuItem 后代,它拥有自己的接口并覆盖 Click 方法,因此您无需分配 OnClick 事件处理程序。代码在 cmets 中看起来很糟糕,所以我在这里不包括任何代码。
    【解决方案2】:

    另一种稍微复杂一点的方法是在 TMenuItem 的 Tag 属性中存储对 IAnimal 的引用。

    这可能是您建议的 TList 中 IAnimal 的索引:

    if Sender is TMenuItem then
      IAnimal(FAnimals[TMenuItem(Sender).Tag]).Frob;
    

    或者您可以将接口转换为整数。

    MenuItem.Tag := Integer(AnAnimal);
    

    然后在事件处理程序中转换回 IAnimal:

    if Sender is TMenuItem then
      IAnimal(TMenuItem(Sender)).Frob;
    

    这适用于对象引用,由于引用计数,接口可能需要小心。

    请注意,Delphi 7 在 Classes.pas 中也有一个 TInterfaceList

    【讨论】:

    • 我不知道谁投了反对票,但没有解释原因;但他们做到了。我曾尝试过类似的事情;尽管将接口包装在一个对象中(以维护引用计数),并将 that 填充在标记中。但是将指针填充到整数中通常是不受欢迎的——尽管没有问题。
    • 我不喜欢在 Tag 属性中填充接口对象的想法,可能是因为我认为引用计数器会丢失。但是我一直把 TObjects 放在标签中!只要 Delphi 是 32 位编译器,这将起作用,一旦更改,我实际上希望 Delphi 也将标记更改为 64 位,因为这是常见的做法。所以我投了赞成票,因为我不想在 -1 看到这个
    • 我怀疑 Embarcardero 不会/不能使 Tag 的大小与指针相同,因为如果这样做会破坏太多代码。我不确定接口会发生什么,因为我从未尝试过。
    • 如果您不确切知道自己在做什么,则将接口类型转换为指针/整数是灾难的邀请。引用计数迟早会回来咬你,你会遇到访问冲突,并花费数小时试图找出原因。您已被警告 ;-)
    • @dummzeuch - 关于接口,我同意。这种技术确实适用于对象。 Ian 的想法是创建一个对象来保存接口,或者将索引保存到列表中的接口会更好。完全可以为此目的创建一个列表。
    【解决方案3】:

    我知道您已将问题标记为已回答,但这里有一些其他建议:

      type
        IClicker = Interface
          function GetOnClickProc : TNotifyEvent;
        End;
    
      type
        TBlob = class( TInterfacedObject, IClicker )
          procedure OnClick( Sender : TObject );
          function GetOnClickProc : TNotifyEvent;
        end;
    
    { TBlob }
    
    function TBlob.GetOnClickProc : TNotifyEvent;
    begin
      Result := Self.OnClick;
    end;
    
    procedure TBlob.OnClick(Sender: TObject);
    begin
      MessageDlg('Clicked !', mtWarning, [mbOK], 0);
    end;
    
    { MyForm }
      var
        clicker : IClicker;
    
      begin
        ...
        menuItem.OnClick := clicker.GetOnClickProc;
      end;
    

    当然,你必须注意“点击器”对象的生命周期...

    如果您可以将对象作为对象(而不仅仅是作为接口)来操作,请尝试添加一个公共子类:

    type
      TClicker = class
        procedure OnClick( Sender : TObject ); virtual;
      end;
    
    var
      lClicker : TClicker;
    ...
    menuItem.OnClick := lClicker.OnClick;
    

    我也赞同 Cosmin Prund 的评论:制作一个专门的 TMenuItem 子类。

    【讨论】:

    • 最后我使用了这个想法的变体;虽然你有它的那种由内而外。对象必须调用接口的方法——需要调用的代码由接口公开。在您的变体中,您在对象的 OnClick 方法中有处理程序代码。
    • 确实,为了坚持你的例子,我应该留下“Frob”而不是“OnClick”,也许是“GetNotifyEventProc”而不是“GetOnClickProc”。
    猜你喜欢
    • 2019-08-24
    • 2012-09-08
    • 2021-12-04
    • 1970-01-01
    • 1970-01-01
    • 2012-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多