在Delphi 7中停止事件传播

6
我在Delphi 7中遇到了一个关于事件传播的问题(由于我的无知而陷入困境)。我被要求在表单上动态添加一些控件的OnMouseUp事件处理程序(这一点我没问题),但是如果存在OnMouseUp,则该控件上的OnClick事件不能被处理。如果您想知道原因,那么我负责修改一个旧的生产监控应用程序,从现在开始必须为某些控件提供有条件的行为,以直接响应对特殊功能按钮的先前点击。我建议利用已经存在于应用程序表单中的面向对象设计:它们都继承自相同的自定义祖先对象,因此我计划在那里插入一个初始化方法,以动态附加 OnMouseUp 事件到在子类中声明支持它的控件。然而,我想让动态附加的 OnMouseUp 事件处理程序停止传播到那些控件上预先存在的 OnClick 事件,这就是我的问题。Delphi 7是否支持这个?

4
或者,在分配 OnMouseUp 时,也要取消分配 OnClick - Ondrej Kelle
不确定这是否是您想要实现的,但我已经发布了一个示例,演示如何将所有分配的OnClick事件重定向到另一个事件,并可选择将它们恢复为原始状态。请注意,它并没有明确回答这个问题;它是一种概念重新设计的提议——由于使用了重定向,就不需要额外的OnMouseUp事件,您只需编写另一个通用的OnClick事件处理程序即可。它使用RTTI,并适用于所有具有OnClick事件处理程序分配的组件(但可以轻松地限制为仅适用于一个组件类类型)。 - TLama
2个回答

6
请注意,以下内容并没有明确回答此处的问题。这更是一个概念重新设计的提议(重定向OnClick事件而不是添加额外的OnMouseUp)。它涉及如何将所有组件(如果需要可以进行过滤)的OnClick事件处理程序重定向到另一个(通用的)OnClick事件处理程序(如果已分配某些)。它还包括一种将它们恢复到原始状态的方法。
在下面的示例中,我将尝试向您展示如何替换然后选择性地恢复具有特定事件处理程序的OnClick事件处理程序(如果组件已编写)。这是针对所有发布了OnClick事件的组件完成的,因此您无需事先知道组件类是否可用OnClick事件(但可以非常简单地修改为仅使用特定类)。
代码由以下部分组成:
- OnSpecialClick - 这是当您调用ReplaceOnClickEvents过程时所有OnClick事件将绑定到的事件处理程序,请注意,它必须发布以便RTTI可见!!! - Button1Click - 在此表示应该被替换的旧事件处理程序,它绑定在设计时的Button1.OnClick事件上。 - ReplaceOnClickEvents - 该方法遍历窗体上的所有组件,并检查当前迭代的组件是否已分配OnClick事件处理程序;如果是,则将其存储到备份集合中,并将此事件处理程序替换为OnSpecialClick。 - RestoreOnClickEvents - 该方法还原原始OnClick事件处理程序;它遍历备份集合并将事件方法分配给其存储的组件实例。 - CheckBox1Click - 此复选框单击事件旨在成为通用模式和特殊模式之间的开关(CheckBox1已选中状态表示为特殊模式),仅此OnClick事件不会被ReplaceOnClickEvents调用替换(因为您将无法将模式恢复回正常状态)。
以下是代码:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, TypInfo, StdCtrls, Contnrs;

type
  TEventBackup = class
    Component: TComponent;
    OnClickMethod: TMethod;
  end;

type
  TForm1 = class(TForm)
    Button1: TButton;
    CheckBox1: TCheckBox;
    procedure Button1Click(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
  private
    procedure ReplaceOnClickEvents;
    procedure RestoreOnClickEvents;
  published
    procedure OnSpecialClick(Sender: TObject);
  end;

var
  Form1: TForm1;
  EventBackupList: TObjectList;

implementation

{$R *.dfm}

procedure TForm1.OnSpecialClick(Sender: TObject);
begin
  ShowMessage('Hi, I''m an OnSpecialClick event message!');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage('Hi, I''m just that boring original OnClick event message!');
end;

procedure TForm1.ReplaceOnClickEvents;
var
  I: Integer;
  Component: TComponent;
  EventMethod: TMethod;
  EventBackup: TEventBackup;
begin
  for I := 0 to ComponentCount - 1 do
  begin
    Component := Components[I];
    if Component = CheckBox1 then
      Continue;

    if IsPublishedProp(Component, 'OnClick') then
    begin
      EventMethod := GetMethodProp(Component, 'OnClick');
      if Assigned(EventMethod.Code) and Assigned(EventMethod.Data) then
      begin
        EventBackup := TEventBackup.Create;
        EventBackup.Component := Component;
        EventBackup.OnClickMethod := EventMethod;
        EventBackupList.Add(EventBackup);
        EventMethod.Code := MethodAddress('OnSpecialClick');
        EventMethod.Data := Pointer(Self);
        SetMethodProp(Component, 'OnClick', EventMethod);
      end;
    end;
  end;
end;

procedure TForm1.RestoreOnClickEvents;
var
  I: Integer;
  EventBackup: TEventBackup;
begin
  for I := 0 to EventBackupList.Count - 1 do
  begin
    EventBackup := TEventBackup(EventBackupList[I]);
    SetMethodProp(EventBackup.Component, 'OnClick', EventBackup.OnClickMethod);
  end;
  EventBackupList.Clear;
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  if CheckBox1.Checked then
    ReplaceOnClickEvents
  else
    RestoreOnClickEvents;
end;

initialization
  EventBackupList := TObjectList.Create;
  EventBackupList.OwnsObjects := True;

finalization
  EventBackupList.Free;

end.

@Simon,我猜测可能是缺少GetMethodPropSetMethodProp方法,但是TypInfo.pas已经在Delphi 7中了,不是吗?也许这取决于发行版的类型(如果我没记错,有一些个人版本可能会缺少它,我不知道)。我无法验证,但请参见例如here(问题以“使用Delphi 7开发控制台应用程序”开始,在代码的uses子句中是TypInfo)。 - TLama
我不是在否定这个答案,它很好 :), 但Delphi 7并没有完整的RTTI。刚才查看了一下,你关于typinfo.pas类是正确的,但你不能访问属性等。D2010版本是实现大幅改进的版本。 - Simon
@Simon,Delphi 7确实没有扩展RTTI(即你提到的在Delphi 2010中引入的RTTI.pas),但是仍然有旧式RTTI(即TypInfo.pas单元,在新版Delphi中仍然可用)。无论如何,OnSpecialClick事件方法必须发布的原因只是旧式RTTI限制的结果。我的朋友证实这也适用于Delphi 7,所以我认为从Delphi 7到Delphi XE2都可以使用 :-) - TLama
1
@TLama:尝试了你的建议,在D7中运行良好。非常感谢;)我们将改变我们的基本想法以反映你提出的建议。 - Federico Zancan
很高兴能帮忙;-) 我已经多次使用这种方法,从翻译工具到内置表单编辑器,这是我认为最舒适的重定向事件的方式。 - TLama
1
@TLama +1并感谢,这是我不知道的东西,感谢您的提示。 - Simon

1

正如TLama和TOndrej所说,有几种方法可以完成您尝试的操作:

  1. 在您的OnClick事件处理程序上执行if Assigned(Control.OnMouseUp) then Exit;

  2. 在分配OnMouseUp(反之亦然)时“取消分配”OnClick事件

这两种方法都可以实现您详细描述的内容,但“取消分配”OnClick事件将对性能产生最好的影响(到一个极小的程度),因为您不会重复执行if语句。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接