I want to assign an event handler in constructor, if it does not have one assigned. Consequentially I want to remove the eventually assigned event handler in destructor. I wrote the code as follows, but cannot be compiled.
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;
How to test it properly? I know a solution is to use a variable to record, whether I have assigned OnResize
. But I do not want this as solution.
No need to write any custom code here as you can use the already existing comparers from 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;
This is complicated by the fact that OnResize
is a property rather than a variable. And it's quite hard to refer to a method directly without the compiler thinking you want to call the method. This is the big drawback of Pascal's convenience of allowing you to call a procedure without using parens.
All this makes it rather hard to do it in a one-liner. As far as I can see you will need to do something like this:
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;
This relies on modern Delphi's TMethod
record which includes an overloaded equality operator to make the =
test work.
I would wrap this all up in a generic method if I was doing it more than once. It might look like this:
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;
You'd call it like this:
destructor TSomeControl.Destroy;
begin
if Assigned(FPanel) and TEventComparer.Equal<TNotifyEvent>(FPanel.OnResize,
HandlePanelResize) then
FPanel.OnResize := nil;
FPanel := nil;
inherited;
end;
One thing that this highlights is that the generic constraints available to you do not allow you to constraint a type to being a method pointer. Hence the basic sanity check that the size of T
is the same as the size of a method. This doesn't offer much safety though. You can call this method passing Int64
, or Double
. I'd be interested to see if anyone can come up with a cleaner variant.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With