In any (VCL) Delphi application more stuff happens behind the scenes than you might be aware of.
As known, every (TWinControl descendant) control placed on a form (like TButton or TEdit) and even the (T)Form itself is actually a wrapper for a Windows screen object. Every such object is “defined” by something called Window Handle. You can use the Handle value to do stuff with the underlying Windows screen object like hide or show it or move around its parent (another Windows screen object) or change some of its properties.
For example when setting the Left property for an Edit control – you would move the control left or right inside its parent control. You can also use the Windows API call “MoveWindow()” or “SetWindowPos()” and each of those have a HWND (THandle) as the first parameter – the handle value of the window.
The tricky thing with those handles is that they are not set in stone once a WinControl is created. The underlying Window Handle for an Edit or a Button or a Form can change (read: underlying Windows screen object is recreated – Window handle destroyed, recreated and therefore value changed) during the lifetime of the application. This change of the Handle can be caused by various actions.
For example changing the Alignment property of an Edit control would recreate the underlying Windows screen object – change the Handle value. Changing the WordWrap property of a TMemo would do the same for the Memo control. The method doing all the hard work is RecreateWnd and is implemented in Vcl.Controls unit for TWinControl.
More examples of when the Handle will be changed include: changing the Parent of a control or using the TStyleManager to change a control’s Style.
Relying on the Handle value for a Control to be non-changeable is a bad practice. While you and I might be aware of it – there are moments when we kind of ignore or forget about this.
Behind the scenes: DestroyWnd / CreateWnd (CreateWindowHandle)
As said, changing the Alignment property will result in a call to RecreateWnd (starting the recreation of the Handle – the underlying screen object). RecreateWnd will in turn send the CM_RECREATEWND message to the control. This will then set the csRecreating flag into the ControlState property. The handle will be destroyed and a series of calls to protected methods including DestroyWnd, DestroyHandle, CreateWnd, CreateWindowHandle, HandleNeeded will happen.
An Edit or a Button control has some text on it. This Text is actually what’s displayed by the control. If you take a look at how DestroyWnd and CreateWnd are implemented you will notice the Text property will be preserved (along with some other properties like Modified for a TEdit) – so when the underlying screen object is recreated – the preserved properties get applied back to the control (/window).
Some more complex controls, like TWebBrowser (actually an ActiveX control) will override the DestroyWnd / CreateWnd to preserve the LocationURL property of the underlying IWebBrowser2 interface.
In short, the DestroyWnd / CreateWnd mehods are the ones to look into if you need to preserve some kind of state (some values or properties) of a control when the control’s handle is recreated.
The Tricky Case of TOleControl
And now finally for what this post is about 😊
If you ever used some ActiveX controls (and yes, they are still being offered) – you might have noticed how stuff can go wrong when their handle gets recreated. There are quite a few ActiveX controls available all around – those providing some specialized functionality like charting, spreadsheet, graphics controls, PDF or any special format type (DWG, XPT, …) viewers/editors and alike. All ActiveX controls in Delphi are derived from TOleControl which is derived from TWinControl + implementing interactions with COM (Component Object Model).
When an Ole control’s parent is changed or when you change the Style of your application – the underlying TOleControl’s TWinControl handle will be destroyed (and re-created).
Now, depending on how the ActiveX is implemented destroying (WM_Destroy message sent to it) the handle would probably mess up the internal state of the control. Never mind that the handle will be created again (as result of CreateWindow API) – the ActiveX does not know your Delphi application is “only” re-creating the underlying screen object – and whatever there was to preserve should be preserved (the fact that csRecreating is in Control state means nothing to the ActiveX).
Here’s an example: let’s say you have Adobe Reader installed. Drop a TWebBrowser on a form, and use the Navigate() method to navigate to a PDF on your disk. Since IE uses Adobe’s ActiveX to display PDF document – now IE uses another ActiveX inside it. Change the Parent of the TWebBrowser – the document that was loaded in the PDF Viewer would be lost. Yes, TWebBrowser would preserve its LocationURL – but this makes no value for Adobe’s ActiveX actually displaying some PDF document (or even maybe more of them in tabs).
Preserving the Handle (Window) of TOleControl when csRecreating in ControlState
When RecreateWnd is called and the underlying Windows screen object is destroyed and then recreated – all this is done so that some special window “properties” are set – those used in TWinControl.CreateParams for example.
When an ActiveX’s handle gets recreated due to RecreateWnd – there’s really no need to destroy the underlying Windows screen object, as when (re-)creating it – no window creation parameters will be changed ().
So, to preserve the internal state of a more complex ActiveX control – preserving it’s underlying screen object (so not destroying it) and the Handle – should do the trick.
And here’s how I do it: let’s say the ActiveX control is named TComplexActiveX. The magic is in overriding the DestroyWnd / CreateWnd methods of (the inherited) TWinControl. Now, without changing the actual implementation of the TComplexActiveX (most probably defined in some ComplexActiveX-TLB.pas) by creating the inherited control, I’m using what’s commonly called Delphi interposer/interceptor class implementation. In a unit where TComplexActiveX is used, have the following:
//unit where TComplexActiveX is used and ComplexActiveX-TLB is in the interface uses list type TComplexActiveX = class(ComplexActiveX-TLB.TComplexActiveX) strict private TheHandle : THandle; protected procedure DestroyWnd; override; procedure CreateWnd; override; end;
With the following implementation:
procedure TComplexActiveX.DestroyWnd; begin if csRecreating in self.ControlState then begin WinApi.Windows.SetParent(WindowHandle, Application.Handle); TheHandle := WindowHandle; WindowHandle := 0; end else inherited DestroyWnd; end; procedure TComplexActiveX.CreateWnd; begin if (TheHandle <> 0) AND IsWindow(TheHandle) then begin WindowHandle := TheHandle; TheHandle := 0; WinApi.Windows.SetParent(WindowHandle, Parent.Handle); WinApi.Windows.MoveWindow(WindowHandle, 0, 0, Parent.Width, Parent.Height, true); Parent.Realign; end else inherited CreateWnd; end;
In short: when the control’s handle is to be recreated (as a call to RecreateWnd for example) we inject our code into DestroyWnd to save the value of the WindowHandle and change who the parent is (as the parent will also be recreated and all its children and therefore our TComplexActiveX) when csRecreating in ControlState – without calling the actual TWinControl’s DestroyWnd method. When CreateWnd get’s called – we “reuse” the saved underlying screen object (TheHandle value) and set the Parent property back to what it was originally.
And that’s all folks. As always, I’m open for better ideas …