so there have been a hundred questions whether to Release vs Free forms in Delphi.
I have encountered an interesting issue that I couldn't explain, below is the (over-simplified) pseudo-code:
procedure SomeProc;
var vForm: TForm;
begin
vForm := create;
try
vForm.ShowModal;
finally
vForm.Release;
// more stuff
Application.MessageBox ('some message');
end;
end;
expected behavior:
upon closing my modal form, the messagebox would show up.
actual behavior:
Messagebox doesn't show, and execution proceeds as if user pressed 'No' ('No' is the default in the actual code)
tracing it:
ShowModal ends, no problems here
release is called, CM_RELEASE is posted in the event queue
MessageBox executes // begin internals of MessageBox code, VCL.Forms unit
TaskActiveWindow := ActiveWindow; // a handle to the active window is saved. who is the active window?
MessageBox invokes Application.ProcessMessages
our modal form destructor is finally invoked.
external call to Windows, with the saved handle TaskActiveWindow
If the call to Release is replaced by Free, the issue never occurs. Since the form is a Modal (internally, busy looping processing messages), there is no harm in calling Free, so that solves the immediate problem.
However it does not explain what is actually going on under the hood.
I can assume, but cannot prove, that the active window was still the modal form upon saving the handle. And since the handle's underlying form is then freed, this is why the MessageBox is not showing?
Would love some opinions / insights on the above.
PS: copy-pasting this may not reproduce the issue at your end, but what is definitely true is, no issues are faced when calling Free instead of Release.
Related
I'm starting to have issues with my main form disappearing behind other application windows on closing modal forms and I was hoping someone would have come across (and solved!) this issue previously or have suggestions on where to locate breakpoints to debug the problem.
My issues originally started with the classic 'shy dialog' problem with modal dialogs appearing under the main form which occurred intermittently. To try to sort this I changed all my modal forms' popupmode to pmAuto and also added
Application.ModalPopupMode := pmAuto;
and Application.MainFormOnTaskBar := true; to my application dpr.
Now I'm getting the main form disappearing behind other windows on closing the modal pop-ups. I have suspicion is that the behaviour is mainly caused when a modal form opens a second window (I've problems with both a MessageDlg and a straight Form.create(Application); Form.show;), though there's no obvious problems with the show/free code (ShowModal forms are created owner = nil, modeless with owner = application). In both cases the form disappears on closing the first original modal form, but manipulating the modal form without triggering a new form/dialog to appear seems to work as expected.
There are other nasties going on in the background on the main form with a refresh timer that activates a background thread, but usually this hasn't fired in the time it takes to see it not working. Other than that we are firing off calls to a remote server via a third-party DLL (the application is effectively a client-side GUI).
Annoyingly I can't get a mini program to mimic the behaviour and running in the IDE makes seeing the behaviour difficult, as the IDE itself contains a lot of windows that muddy the Z-ordering.
Edit - After writing my answer below, it appears I'm getting a deactivate event sent to the application (I can catch it through Application.OnDeactivate) - it seems similar to WPF App loses completely focus on window close Delphi doesn't have the Activate method that the c# solutions have, but I'll play with some windows messaging to see if I get anywhere
Following David's advice in comments I created a little logging form to be created on startup containing memo, timer and the following OnTimer event:
procedure TForm1.Timer1Timer(Sender: TObject);
function logtomemo(aHandle: HWND): TWinControl;
var
form: TWinControl;
begin
form := findControl(ahandle);
if form <> nil then
memo1.Lines.Add(format('handle %d - form %s', [ahandle, form.Name]));
result := form;
end;
var
handle: HWND;
form: TWinControl;
begin
memo1.Clear;
handle := application.ActiveFormHandle;
repeat
form := logtomemo(handle);
handle := GetWindow(handle, GW_OWNER);
until (handle = application.MainFormHandle) or (form = nil);
logtomemo(handle);
end;
Clicking around I noticed that as soon as I clicked outside of my application, our splash form appeared as the only form in the list. (Historically our splash screen used to only be freed after Application.Run, as they used to keep some other references on it for some reason - before my time and wasn't really needed anymore).
Changing the lifetime of the splashscreen to be destroyed before Application.Run appears to have sorted the issue - something that I'd never have guessed would be the cause in a million years.
Need a final sign-off that it doesn't reappear once I get rid of this little debug form, but hopefully a problem that's been frustrating me for a few days is now sorted - thanks!
Edit
As I noted in my edit and the comments to this question, the above debug didn't work, as the presence of the new form 'fixed' the problem. Changing the code so the output was sent to the Event Log or a text file rather than requiring a form also didn't reveal anything - all forms in the Z order remained in place.
In the end, I was able to fix the symptom rather than the cause by attaching the following code to Application.OnModalEnd
if Application.ModalLevel = 0 then
Windows.SetActiveWindow(Application.MainFormHandle);
This successfully sets the active window back to the main form after the last modal dialog has been closed.
This may have some side-effects if the user is expecting a non-modal form that isn't the main form to regain focus, but our application architecture doesn't really follow this structure and with Application.MainFormOnTaskbar, the main form won't hide the other forms (as long as they're not unparented)
I would like to terminate a Delphi application without executing any other code line and I'm wondering about which is the proper way to do this. Furthermore, I would like to know if there's something wrong in what I'm actually doing at the moment.
Basically, my code looks like this:
//Freeing all objects (Obj1.Free, etc..)
Application.Terminate;
Halt;
Is this the right way to stop a Delphi application or should it be done in another way?
Application.Terminate() breaks the message loops in TApplication.Run() and TForm.ShowModal(), allowing the main thread to exit normally, perform necessary cleanups, etc.
Vcl.Forms.TApplication.Terminate
Ends application execution.
Call Terminate to end the application programmatically. By calling Terminate rather than freeing the application object, you allow the application to shut down in an orderly fashion.
Terminate calls the Windows API PostQuitMessage function to perform an orderly shutdown of the application. Terminate is not immediate.
Terminate is called automatically on a WM_QUIT message and when the main form closes.
Halt(), on the other hand, is an immediate abnormal termination. Basically, ripping the process out of memory. Use it only in extreme situations where no other option is available.
System.Halt
Initiates the abnormal termination of a program.
Halt performs an abnormal termination of a program and returns to the operating system.
To perform a normal termination of a Delphi application, call the Terminate method on the global Application object. If the application does not use a unit that provides an Application object, call the Exit procedure from the main Program block.
I would like to terminate a Delphi application without executing any other code.
Neither Application.Terminate nor Halt will achieve that. The former performs an orderly termination. Lots of code will execute. Calling Halt is more hopeful. That is an abnormal termination. But unit finalization code is executed.
If you wish to exit as quickly as possible, executing the minimum amount of code along the way, call ExitProcess. That's the final step of Halt and by calling ExitProcess directly you avoid all the steps that Halt takes before it calls ExitProcess.
I had some problems with Application.Terminate, because I had to start the Form Close procedure, so I did only:
Form1.Close;
I found a new solution inside .dproj
begin
ReportMemoryLeaksOnShutdown := True;
Application.Initialize;
Application.CreateForm(TFormMain, FormMain);
if Not(VerifyCode()) then
begin
ShowMessage('Software unregistered!');
Application.Terminate;
end
else
Application.Run;
end.
Just to leave a point on a extra problem if code must be on main form OnCreate.
Try such code on the Main Form OnCreate event. It does not work as expected, main form is shown, then the application is finished.
To be able to see it, add another form and put on its creation a long loop.
It seems like all the Application.CreateForm on the main project source are executed.
Sample code:
procedure TMyMainForm.FormCreate(Sender: TObject);
begin
ShowMessage('[1] This must allways be shown');
if mrOK=MessageDlg('Exit?',mtConfirmation,[mbOK,mbCancel],0)
then begin
Application.Terminate;
Exit;
end;
ShowMessage('[2] This must not allways be shown');
end;
procedure TMyOtherForm.FormCreate(Sender: TObject);
begin
ShowMessage('[3] This must not allways be shown');
end;
With that code messages [1] and [3] are allways shown.
Only way to not show [3] is to call Halt.
Note: Why such code on MainForm OnCreate? Simple answer could be, the exe checks conditions to be run and see they are not meet (missing files, etc), rude one (sorry for that), just because i want/need to.
I know this is an old thread, but would appreciate comments on this addition to the thread if anyone is still listening.
For quite some time I have called Application.Terminate followed by ExitProcess(0). My theory is that Application.Terminate does the graceful cleanup, but ExitProcess prevents any other code from executing. It seems to work, and I don't get reports of memory leaks or other ill effects. Code would be something like:
Procedure (KillTheApp);
begin
Application.Terminate;
Application.ProcessMessages;
ExitProcess(0);
end;
Well, here I am again, trying to resolve an old problem.
Briefly, I get an AV when I try to free a modal form which does not have any owner, and didnt have been freed before.
frmItensVenda := TfrmItensVenda.Create(nil);
frmItensVenda.vtipo := vtipo;
frmItensVenda.vcontrole := strtoint(edit1.Text);
frmItensVenda.Ednota.Text := Edit5.Text;
frmitensvenda.lbvend.Caption := combobox3.Text;
frmitensvenda.lbnome.Caption := combobox1x.Text;
frmItensVenda.limite := limite;
if label10.caption <> '' then
frmItensVenda.vcli := strtoint(label10.caption);
frmItensVenda.ShowModal;
Frmitensvenda.Close;
frmItensVenda.Free;
If I just activate it and then close(without doing a thing), no AV happens. Putting a break-point before the 'free' command, it shows me the variable inside the form if I put the mouse cursor on it.
But if I insert one item in the grid, using the breakpoint at the same place, when I move the cursor to the same line, it doesnt show the variables anymore, but says 'inacessible value'.
And if I proceed running the code, as the next line has the 'free' command I get an AV.
What makes me believe there is some piece of code on that procedure that is doing something unexpected to the code, but I can tell you that there is no 'free' or similar command to the form in question there.
My solution(temporary) was to just comment the '.free' command, but if I run MadException I got a memory leak when I close the application (hey, anything is better than this EAccessViolation thing for me right now..)
Any sugestions?
Ok, found the answer, finally.
The problem was a global array.
It was declared
vm1 : array[1..100] of currency;
but it was assigned a value at position 0.
To my despair, there was no error when the variable was assigned, just when I tried to free the form.
So simple when you find it.. (!!!)
Well, at least I figured it out. Thanks everyone for the support!
OP : frmItensVenda is a global variable automatic created(but not initialized).
I see you do frmItensVenda := TfrmItensVenda.Create(nil);
Look for Application.CreateForm(TfrmItensVenda, frmItensVenda); in your .dpr file.
If it is there you creating a new instance !
{$R *.RES}
begin
Application.Initialize;
Application.Title := 'AServer';
...
Application.CreateForm(TfrmItensVenda, frmItensVenda);
...
Application.Run;
end.
And yes, dynamic form management is a must really (expecially in large applications.).
At trouble with large Forms, part of my solution was to, as much as possible to create dynamic.
Only when they are needed and then free them straight after.
frmItensVenda := TfrmItensVenda.Create(nil);
frmItensVenda.ShowModal;
OP : My solution(temporary) was to just comment the '.free' command,
don't do that : use instead
frmItensVenda.Release;
The "Method Release" removes the form and frees its associated memory.
Release procedure;
description
With release, you can remove the form from memory.
Release is the form to be taken until after the execution of event handlers of the form and its child components is completed.
In all event handlers release should be used instead free to avoid access violations.
The cases where you need to use Release are times when you're in the middle of an event handler (eg, OnClick), where further processing after the event will have to access the form.
In that case calling Release instead posts a WM_RELEASE message which doesn't free the event until the event handler is done and control has returned to the message pump (ProcessMessages/Application.Run).
Reading the delphi help though, it is recommended that you use the release command.
As for the Release v.s Free method. My understanding is that "Release" is specific to forms, and allows form-related even-handlers to finish before freeing resources.
Whereas "Free" is a generic method of freeing an object from memory (so should also work with forms.)
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Splash Screen Programatically
Show a splash screen while a database connection (that might take a long time) runs
Which is the best place to initialize code such as loading INI file? I want first to show the form on screen so the user know that the app is loading and ONLY after that I want to call lengthy functions such as LoadIniFile or IsConnectedToInternet (the last one is REALLY slow).
The OnCreate is not good because the form is not yet ready and it will not show up on screen.
I do this I DPR but not working always:
program Test;
begin
Application.Initialize;
Application.Title := 'Test app';
Application.CreateForm(TfrmTest, frmTest);
frmTest.Show; <---------------------- won't show
LateInitialize;
Application.Run;
end.
The form will not show until LateInitialize (4-5 seconds) is executed.
procedure LateInitialize;
begin
CursorBussy;
TRY
// all this won't work also. the form won't show
frmTest.Visible:= TRUE;
Application.ProcessMessages;
frmTest.Show;
Application.ProcessMessages;
frmTest.BringToFront;
frmTest.Update;
Application.ProcessMessages;
DoSomethingLengthy; {4-5 seconds}
FINALLY
CursorNotBussy;
END;
end; <--------- Now the form shows.
And yes, frmTest it is my only form (the main form).
After calling frmTest.Show, you can call frmTest.Update to let it render onscreen, before then calling LateInitialize. But until Application.Run is called, the main message loop will not be running, so the form will not be able to do anything else until then.
Another option is to use the form's OnShow event to post a custom window message back to the form via PostMessage(), then have the form call LateInitialize when it receives that message at a later time. That will allow the form to process painting messages normally until LateInitialize is called.
Anything that blocks the main thread for more than a few milliseconds/seconds really should be moved into a separate worker thread instead (especially things like IsConnectedToInternet). The main thread should be used for running the UI.
An easy way to do this, is to send a message to yourself.
I do this all the time
const
MSG_AFTERCREATE = WM_APP + 4711;
...
procedure OnCreate(Sender: TObject);
procedure AfterCreate(var message: TMessage); message MSG_AFTERCREATE;
...
Implementation
procedure OnCreate(Sender: TObject);
begin
PostMessage(Self.Handle, MSG_AFTERCREATE, 0, 0);
end;
procedure AfterCreate(var message: TMessage);
begin
//Do initializing here... the form is done creating, and are actually visible now...
end;
Variant 1: Use TTimer with a 1 second delay, run it from main form's OnShow
In TTimer do the initialisation
This will give time for most components to initialize and draw themselves
Variant 1.1: use message method in function and call Win API PostMessage (but not SendMessage aka Perform) from OnShow. This is seemilar but more cheap and fast. However that message "do init now" sometimes may be received before some complex component on the form would fully draw itself.
Variant 2: use threads (OmniThreadsLib or even plain TThread)
Launch it from MainForm OnCreate and let it prepare all data in background, then enable all needed buttons, menus, etc
That is truly the best way if you have long and blocking functions, liek you described IsConnectedToInternet.
Variant 3: use SplashScreen before showing main form.
That is good because users see that application not read yet.
That is bad for that very reason - people start feeling your program is slow. Google Chrome was told to draw their main form as picture in 1st moments just to make look "we are already started" even the actual control would be ready a bit later.
A long time ago in another forum far far away, someone posted the following to document the life cycle of a form. I have found it useful, so am sharing it here.
Create OnCreate
Show OnShow
Paint OnPaint
Activate OnActivate
ReSize OnResize
Paint OnPaint
Close query OnCloseQuery
Close OnClose
Deactivate OnDeactivate
Hide OnHide
Destroy OnDestroy
Try the OnActivate event.
i want to halt my application when my bitmap is completely saved?
Unless you are calling that method in a separate thread I'm fairly certain that it will be completely saved at the point at which the method returns:
bitmap.SaveToFile(..);
// file is completely saved at this point
What is it that leads you to believe that it isn't completely saved at this point?
The only wrinkle might be that with "lazy writes" Windows itself might not yet have completed the business of physically writing the file to the disc, but as far as your application (or any other application) is concerned, that is irrelevant. If you attempt to access the file before Windows has completed committing it to disc that application will simply "block" until it can safely access the file, but you don't need to specifically handle this, it "just works".
If you want to halt your application cleanly there are several options:
1) Close the main form. When you close the main form, the application will close.
procedure TForm1.SaveAndClose(Filename:String);
begin
Bitmap.SaveToFile(filename);
Application.Mainform.Close;
end;
2) Call Application.Terminate. This shuts down your application abruptly, but does run any finalization code. It is normally invoked when the application receives a WM_Quit message or the main form closes.
procedure TForm1.SaveAndClose(Filename:String);
begin
Bitmap.SaveToFile(filename);
Application.Terminate;
end;
3) Post wm_Close to your application/main form. This has the added advantage that any other messages in queue for the application are processed. It is equivalent to pressing CTRL-F4 or pressing the "X" in the upper right on your main form. The only issue is that if the message queue is full then it may not ever reach the form (rare).
procedure TForm1.SaveAndClose(Filename:String);
begin
Bitmap.SaveToFile(filename);
postMessage(Application.MainForm,wm_close,0,0):
end;
4) Call Halt( ExitCode:Integer ). This is a bit more extreme and will terminate the application. Finalization code will still be run, but memory is not guaranteed to be freed. Optionally you can set an exit code that will be returned back to the calling application.
procedure TForm1.SaveAndClose(Filename:String);
begin
Bitmap.SaveToFile(filename);
Halt(0);
end;