Search code examples
delphidelphi-7

TWinControl - What is the last stage before Destructor when the WinControl has a Valid handle?


Just before my TCustomWinControl is destroyed permanently, I need to do somthing with its handle.

If I try to access its handle in the destructor I get an error:

"Control "xxx" has no parent window".

So what is the last stage before TWinControl Destructor where its handle (HandleAllocated) is still valid?

type
  TPanel = class(ExtCtrls.TPanel)
  protected
    procedure DestroyWindowHandle; override;
  public
    procedure BeforeDestruction; override;
  end;

procedure TPanel.DestroyWindowHandle;
begin
  Beep;
  if csDestroying in ComponentState then Beep;      
  inherited;
end;

procedure TPanel.BeforeDestruction;
begin
  if HandleAllocated then Beep;
  inherited;
end;

There is no Beep.


Solution

  • Update

    It is more complex than I originally thought. Your control lives on a form, and the control's death is provoked by the death of that form. When a form is destroyed, the child windows are also destroyed. So, the Win32 API is responsible for destroying your window. The VCL keeps track of this by responding to the WM_NCDESTROY message:

    procedure TWinControl.WMNCDestroy(var Message: TWMNCDestroy);
    begin
      inherited;
      FHandle := 0;
      FShowing := False;
    end;
    

    So, I guess you could handle WM_NCDESTROY yourself. Look for csRecreating in ControlState to switch behaviour based on whether or not the window destruction is related to VCL window re-creation.

    An interesting point to note here is that there's no reason why the destructor of your control has to be called. If it is not owned by the form then your control won't be destroyed. You could then re-parent it onto another form. So WM_NCDESTROY really is the right hook.


    Original answer

    The source code of the destructor looks like this:

    destructor TWinControl.Destroy;
    var
      I: Integer;
      Instance: TControl;
    begin
      Destroying;
      if FDockSite then
      begin
        FDockSite := False;
        RegisterDockSite(Self, False);
      end;
      FDockManager := nil;
      FDockClients.Free;
      if Parent <> nil then RemoveFocus(True);
      if FHandle <> 0 then DestroyWindowHandle;
      I := ControlCount;
      while I <> 0 do
      begin
        Instance := Controls[I - 1];
        Remove(Instance);
        Instance.Destroy;
        I := ControlCount;
      end;
      FBrush.Free;
    {$IFDEF LINUX}
      if FObjectInstance <> nil then WinUtils.FreeObjectInstance(FObjectInstance);
    {$ENDIF}
    {$IFDEF MSWINDOWS}
      if FObjectInstance <> nil then Classes.FreeObjectInstance(FObjectInstance);
    {$ENDIF}
      inherited Destroy;
    end;
    

    The call to the Win32 API DestroyWindow is made in this line:

    if FHandle <> 0 then DestroyWindowHandle;
    

    So you need to run your code before then.

    You could override DestroyWindowHandle and do your work there. That would work well so long as the event you need to deal with is the destruction of the window. But bear in mind that DestroyWindowHandle will be called when the window is re-created.

    If you need to do something related to the destruction of the VCL control, then you would be best overriding BeforeDestruction. Or as an alternative, you could override DestroyWindowHandle and in there test for csDestroying in the ComponentState.