【问题标题】:Delphi: Test event handler assignmentDelphi:测试事件处理程序分配
【发布时间】:2014-06-08 16:17:14
【问题描述】:

如果没有分配一个事件处理程序,我想在构造函数中分配一个事件处理程序。因此,我想删除析构函数中最终分配的事件处理程序。我写的代码如下,但无法编译。

constructor TSomeControl.Create(Panel: TPanel);
begin
  inherited Create;
  FPanel := Panel;
  if not Assigned(FPanel.OnResize) then
    FPanel.OnResize := HandlePanelResize;
end;

destructor TSomeControl.Destroy;
begin
  if @FPanel.OnResize = @HandlePanelResize then // [dcc32 Error] E2036 Variable required
    FPanel.OnResize := nil;
  FPanel := nil;
  inherited;
end;

如何正确测试它?我知道一个解决方案是使用一个变量来记录,我是否已经分配了OnResize。但我不希望这是解决方案。

【问题讨论】:

标签: delphi event-handling


【解决方案1】:

无需在此处编写任何自定义代码,因为您可以使用来自Generics.Defaults 的现有比较器:

destructor TSomeControl.Destroy;
begin
  if Assigned(FPanel) and TEqualityComparer<TNotifyEvent>.Default.Equals(
    FPanel.OnResize, HandlePanelResize) then
    FPanel.OnResize := nil;
  FPanel := nil;
  inherited;
end;

【讨论】:

  • TEqualityComparer 也适用于 Event&lt;T&gt;,如下代码也适用:function TForm1.Button1OnClickIsMultiCast: Boolean; begin Result := TEqualityComparer&lt;TNotifyEvent&gt;.Default.Equals( Button1.OnClick, FMultiCastEvents); end;
  • @EdwinYip 那是因为Event&lt;T&gt;T 有一个隐式运算符;)
【解决方案2】:

OnResize 是一个属性而不是一个变量这一事实使情况变得复杂。如果编译器不认为您要调用该方法,则很难直接引用该方法。这是 Pascal 方便您在不使用括号的情况下调用过程的一大缺点。

所有这些都使得在单行中做到这一点相当困难。据我所知,您需要执行以下操作:

destructor TSomeControl.Destroy;
var
  Method1, Method2: TNotifyEvent;
begin
  if Assigned(FPanel) then
  begin
    Method1 := FPanel.OnResize;
    Method2 := HandlePanelResize;
    if TMethod(Method1) = TMethod(Method2) then
      FPanel.OnResize := nil;
  end;
  FPanel := nil;
  inherited;
end;

这依赖于现代 Delphi 的 TMethod 记录,其中包括一个重载的相等运算符来使 = 测试工作。

如果我不止一次这样做,我会将这一切都包装在一个通用方法中。它可能看起来像这样:

type
  TEventComparer = class
    class function Equal<T>(const lhs, rhs: T): Boolean; static;
  end;

class function TEventComparer.Equal<T>(const lhs, rhs: T): Boolean;
begin
  Assert(SizeOf(T)=SizeOf(TMethod));
  Result := TMethod((@lhs)^)=TMethod((@rhs)^);
end;

你可以这样称呼它:

destructor TSomeControl.Destroy;
begin
  if Assigned(FPanel) and TEventComparer.Equal<TNotifyEvent>(FPanel.OnResize, 
    HandlePanelResize) then
    FPanel.OnResize := nil;
  FPanel := nil;
  inherited;
end;

这里强调的一件事是,您可以使用的通用约束不允许您将类型约束为方法指针。因此,T 的大小与方法的大小相同的基本完整性检查。但是,这并不能提供太多安全性。您可以通过Int64Double 调用此方法。我很想看看是否有人能想出一个更干净的变体。

【讨论】:

  • 您的代码运行良好。我很想知道,如果代码可以写成一些通用的辅助函数?方法类型可能不限于TNotifyEvent。我自己尝试过,但是当我将其包装为通用辅助函数时,编译器会抱怨 TMethod(Method1) = TMethod(Method2)
  • 好手艺。我的错误是使用lhs 而不是(@lhs)^
  • 类似:var p: PTypeInfo; begin p := TypeInfo(T); Assert(p.Kind = tkMethod);
  • @StefanGlienke 是的,这很好。尤其是现在 Emba 修复了他们的方法比较器中的错误!
  • @DavidHeffernan 由于 Stefan 的解决方案更本土化,我接受他的回答。我希望你会同意。
【解决方案3】:

根本不需要使用Generics.Defaults 或任何泛型。 System 单元中声明了 TMethod 记录,所以这可能是最简单的一个:

destructor TSomeControl.Destroy;
var
  Event: TNotifyEvent;
begin
  Event := HandlePanelResize;
  if TMethod(FPanel.OnResize).Code = Addr(Event) then
    FPanel.OnResize := nil;
  FPanel := nil;
  inherited;
end;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-23
    • 1970-01-01
    相关资源
    最近更新 更多