Delphi: назначение обработчика тестовых событий

Я хочу назначить обработчик событий в конструкторе, если он не имеет назначенного. Следовательно, я хочу удалить в конечном итоге назначенный обработчик событий в деструкторе. Я написал код следующим образом, но он не может быть скомпилирован.

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. Но я не хочу, чтобы это было решением.

3 ответов


нет необходимости писать здесь пользовательский код, так как вы можете использовать уже существующие компараторы из 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;

это осложняется тем, что OnResize является свойством, а не переменной. И довольно сложно ссылаться на метод напрямую без того, чтобы компилятор не думал, что вы хотите вызвать метод. Это большой недостаток удобства Паскаля, позволяя вам вызывать процедуру без использования parens.

все это делает его довольно трудно сделать это в одной строке. Насколько я вижу, вам нужно сделать что-то вроде этого:

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;

это зависит от современного Делфи 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 совпадает с размером метода. Это не предложение безопасность. Вы можете вызвать этот метод, передав Int64 или Double. Мне было бы интересно посмотреть, может ли кто-нибудь придумать более чистый вариант.


нет необходимости использовать Generics.Defaults или любые дженерики вообще. Есть TMethod запись объявлена в System единица, так что это, вероятно, самый простой:

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