Delphi: Freeing a dynamic control at runtime - delphi

Is there a failsafe way of freeing a Delphi control?
I have a TStringGrid descendent which I am "embedding" a custom control in it for an inplace editor. When the user navigates within the cells of the grid via the tab key or arrow keys, I need to create a dynamic control if the cell is editable. I have hooked the needed events and am utilizing the OnKeyDown event of my custom control to pass the navigation keys back to the parent TStringGrid.
Previously, the TStringGrid descendent would simply call FreeAndNil on the embedded control, but under some circumstances this would cause access violations inside of UpdateUIState/GetParentForm. Looking at the call stack, it appears that sometimes after the control was free'd, a WM_KEYDOWN (TWinControl.WMKeyDown) message was still happening.
I've all ready looked at and implemented the changes discussed in How to free control inside its event handler?. This appears to have resolved the issue, but I am wondering if there are any other cavet's to this approach.
In effect, this workaround has simply delayed the destruction of the control until after all the existing messages on the queue at the time the CM_RELEASE message was posted.
Would it not be possible that after the CM_RELEASE was posted, another WM_KEY* or similar message could all ready have been posted to the message queue?
My current CM_RELEASE handler looks like:
procedure TMyCustomControl.HandleRelease(var Msg: TMessage);
begin
Free;
end;
So, will this be safe in all instances or should I do something to clear any other messages from the queue? ( SendMessage(Self.Handle, WM_DESTROY, 0, 0) comes to mind )

In general you should not destroy a control in an event-handler of that control.
But since your function is a plain non virtual message handler which is never called from internal code in that control you should be ok. I don't like too too much from a style point of view, but I think it's ok for your use-case.
But a custom message might be cleaner.
Would it not be possible that after the CM_RELEASE was posted, another WM_KEY* or similar message could all ready have been posted to the message queue?
If messages in queue would cause big problems you could never safely destroy a control since messages can be posted from other threads and applications. Just make sure the correct functioning of your application doesn't depends on those messages being handles in every case.

SendMessage sends the message and wait for it to return, that's why you can not use it safely in the event handler of the control you are freeing.
PostMessage in the other hand, will send the message and will be processed after the event exits (if there are no more code in the event).

Related

Form destroy (OnDestroy) never called when using custom styles? XE7

Delphi XE7 - When using custom styles (Project, Options, appearance) OnDestroy never gets called. Using default-native-windows skin-theme, form destroy is called as expected, is this normal?
If so, what are other alternatives besides OnClose?
Blank project, OnDestroy():
procedure TForm1.FormDestroy(Sender: TObject);
begin
ShowMessage('destroy called only when not using styles');
end;
Solution and advice:
When using styles (see #andreas advice): onDestroy() is not a good place to put code, since application termination will not wait for all code to finish, some code might be executed but there is a chance not all.
The OnDestroy event is called irrespective of whether styles are used or not, when the form is in the process of being destroyed. You can confirm it by putting a break point on the ShowMessage() line (works of course only when running under the debugger) or by adding a call to Beep() (assuming your sound system is OK).
When the main form is destroyed, the program starts to prepare for termination. The call to ShowMessage() makes a furious attempt to show the message box but the process is already going down. The message box can even be seen briefly as a flash, in the case of not succeeding in staying visible. It is close to a miracle that the message box shows up in some OS's under any conditions.
Anyway, The best place to show any messages at the end, is in either OnCloseQuery() or OnClose() events. The OnDestroy() event is only meant to clean up any resources aquired in the OnCreate() event.

TWebBrowser and ProcessMessages

Sample code for working with TWebBrowser typically inserts a loop to call Application.ProcessMessages until ReadyState is READYSTATE_LOADED.
For example, from the Embarcadero forums, some code to load an HTML string into a TWebBrowser:
mWebBrowser->Navigate(L"about:blank");
while (mWebBrowser->ReadyState< READYSTATE_LOADED)
Application->ProcessMessages();
// load mWebBrowser from TStreamAdapter
As I understand it, calling ProcessMessages is risky, because it can create reentrancy problems. (For example, if the user clicks the Close button on the form, then that message could get handled by ProcessMessages, such that the TWebBrowser instance no longer even exists when the function returns.)
Am I understanding this correctly?
If so, is there a "safe" way to handle TWebBrowser's needs to have messages processed, without ProcessMessages' risks? Or do I need to redesign all of my TWebBrowser code to be asynchronous?
You don't need to call ProcessMessages at all. You can let the main application message loop deal with the messages.
But the code here is waiting for a page to load before doing the next task. To retain that sort of behaviour you switch to an asynchronous event driven approach. Remove the loop and instead handle the browser's OnDocumentComplete event. Only then do you load the actual content from your stream.

Accessing TWinControl.Handle on TForm.Create

I have this code:
procedure TForm1.FormCreate(Sender: TObject);
begin
DoSomethingWithCombobox(ComboBox1.Handle);
end;
Q: Is it guaranteed that the ComboBox1.Handle is always created on TForm.FormCreate (Form1 is the parent of ComboBox1)? Maybe on OnFormShow?
from my tests, it seems that the Handle is always available at that point.
I know that ComboBox1.Handle will call HandleNeeded at this point. but can I assume that Handle will always be available at this point?
I also know that the TWincontrol can safely access it's own handle on CreateWnd. My question is specific to a scenario where I cannot control CreateWnd of the child control and only have access to the parent events/messages.
Hope my question is clear.
If your tests show that it's OK to access the control's handle there, then it should be OK. You're the application developer, so if you later change anything to break that assumption, you'll also have the power to fix it.
Accessing a control's Handle property will either yield a valid window handle or throw an exception. You won't get a null handle. The exception would typically come when the control's parent window is unable to exist.
The handle you get at that point isn't guaranteed to be the last handle the control will ever have, because controls' underlying windows may be recreated, but since you're the application developer (as opposed to a component-library developer), you have reasonable control over how often windows will be recreated after the form has finished being created. This is because you're handling the OnCreate event. Had you been overriding the Loaded method, for example, there's be less confidence that all window-creation activity had finished.

How to free a control in its own event?

I have a list of TPanels in a FMX application and I want to free a panel if I click on it.
To free them directly in the Onclick-handler is not the right way, because I get an access-violation. And I don't want to use windowsmessages (recommended in How to free control inside its event handler? and Why does my program crash when I destroy a button in its own OnClick handler?) because it is a firemonkey application and I do not know how these messages work on android and mac.
Is there another solution?
Use myObject.Release:
Marks this TFmxObject object for delayed deletion.
Immediate actions in this method:
set Parent = nil
insert object into delayed delete list
Delayed action:
free object from list (vPurgatory).
Remember, that method Free (and procedure FreeAndNil) does not remove the object itself in mobile platforms:
// under ARC, this method isn't actually called since the compiler translates
// the call to be a mere nil assignment to the instance variable, which then calls _InstClear

Prevent Form Deactivate in Delphi 6

We have a Delphi 6 application that uses a non modal form with in-grid editing. Within the FormClose event we check that the entries are square and prevent closure if they're not.
However, if the user clicks on the main form behind then the original form disappears behind (as you'd expect) but this allows the user to move to a new record on the main screen, without their changes in the grid having been validated.
I've tried the FormDeactivate event, which does fire but doesn't seem to have any mechanism to prevent deactivation (unlike the FormClose events Action parameter).
I tried the OnExit from the grid, but it doesn't fire on deactivation.
I tried trapping the WM_ACTIVATE message and setting Msg.Result = 1 but this has no effect (possibly because another WM_ACTIVATE message is being sent to the main form?).
So, I'm looking for ideas on how to (conditionally) prevent the deactivation of a form when the user clicks on another form.
(PS I don't want to change the form style to fsStayOnTop)
Thanks
A classic rule in Windows is that you can't change the focus during a focus-changing event. The OnDeactivate event occurs during a focus-changing event. Your form is being told that it is being deactivated — the OS is not asking permission — and at the same time, the other form is being told that it is being activated. Neither window has any say in the matter, and attempting to change the focus while these events are going on will only get all the windows confused. Symptoms include having two windows painting themselves as though they have focus, and having keyboard messages go nowhere despite the input cursor blinking. MSDN is even more dire, although I've never witnessed anything that bad:
While processing this message [WM_KILLFOCUS], do not make any function calls that display or activate a window. This causes the thread to yield control and can cause the application to stop responding to messages. For more information, see Message Deadlocks.
Since you can't deny a focus change after it's already started, the thing to do is to delay handling of the event until after things have settled down. When your editing form gets deactivated and the data on it isn't valid yet, post the form a message. Posting puts the message on the end of the message queue, so it won't get handled until all previous messages — the focus-changing notifications in particular — have already been handled. When the message arrives, indicate that data is invalid and set focus back to the editing form:
const
efm_InvalidData = wm_User + 1;
type
TEditForm = class(TForm)
...
private
procedure EFMInvalidData(var Msg: TMessage); message efm_InvalidData;
end;
procedure TEditForm.FormDeactivate(Sender: TObject);
begin
if DataNotValid then
PostMessage(Handle, efm_InvalidData, 0, 0);
end;
procedure TEditForm.EFMInvalidData(var Msg: TMessage);
begin
Self.SetFocus;
ShowMessage('Invalid data');
end;
I should point out that this answer doesn't technically answer your question since it does nothing to prevent form deactivation, but you rejected my other answer that really does prevent deactivation.
When you call ShowModal, all the forms except the one being shown get disabled. They're re-enabled just before ShowModal returns.
Display your editing form nonmodally, and when data starts being edited, have the form make itself modal by disabling the other form. Enable the other form when editing is complete. Apparently, disabling windows isn't always quite as simple as setting the Enabled property. I'd suggest using DisableTaskWindows, but it would disable all windows, including your editing form. Nonetheless, take a look at how it's implemented in Forms.pas. It keeps a list of all the windows it disables so that only they get re-enabled afterward.
Delphi 2006 introduced OnMouseActivate events. The main form's OnMouseActivate would let you prevent the main form from being activated if the other form is visible.
This doesn't work with D6 of course.
It not a helpful answer David, but I think I would have to agree with other respondents that this is not the correct approach. There are so many ways that it can all go wrong that stopping and taking a look at a different way to solve your problem might be better.
Even if you did manage to find the event/method/message that does what you want, you would still need to be able to deal with the scenario where the electricity goes off.
On a slightly more useful note, have you tried disabling your mainform until ready? You could put all controls on a panel and then just do
panel1.enabled := false;
You may also introduce a state in your model that tracks if the window needs the focus as you describe it here and use onFocus handlers on the other forms that programmatically set the focus back to your grid window.
[Edit] Copy of my Comment:
You could register for the onShow Event of the forms with the grid. (If you implement it be sure to make it somehow configurable to minimize the grid dependency on the present layout of the application. Maybe by providing a method that is called by the forms which in turn triggers the event registration of the grid at the calling form for the onShow event)
To elaborate on the event registration:
You can attach event handlers programatically. There are plenty howtos in the net about this. I have no Delphi available here, so I can't copy working code now.
Pseudocode for programatically attaching the event!
myform.onShow=myGrid.formOnShowHandler;
formOnShowHandler has the same signature as the functions that are generated by the IDE for onShow Events. It has a parameter that you can use to figure out which form has called the handler so you can reuse the function and simply put the form in the background and show your gridform (which will be for example the parent of the grid) again.
Thanks to everyone for their help and suggestions.
Here's the solution I went with:
In the 'Grid Form' (eg Form2) ...
public
PricesNotSquare: boolean;
In the FormDeactivate event, set PricesNotSquare to true if they don't match.
In the OnActivate event of the Main Form, ...
if Assigned(Form2) and (Form2.PricesNotSquare) then
begin
ShowMessage( 'Please ensure the total Prices match before leaving the form' );
Form2.Show;
exit;
end;
// other form activate stuff here
Turned out to be a simple solution, just took a while to get it.
Seems to work fine, but if it has issues then I'll incorporate the idea of sending a message.

Resources