The situation is the following one: on the application main form create event some conditions are not respected, so the application needs to close.
Yes, this is a bad design but how the application should be closed? Using the Application.MainForm.Close generates an AV. Application.Terminate is not a very good choice. Other ideas?
Application.Terminate works just fine. However, keep in mind that it is a delayed termination, all it does is posts a WM_QUIT message to the calling thread's message queue, so the app will not actually terminate until Application.Run() is called to start processing the main thread's message queue. Because of that, you might see the MainForm flicker onscreen momentarily before the app is actually terminated. If you want to avoid that, you can set the Application.ShowMainForm property to false, eg:
procedure TMainForm.FormCreate(Sender: TObject);
begin
if (some condition) then
begin
Application.ShowMainForm := False;
Application.Terminate;
end;
end;
However, as others have stated, a better design is to do the check in the project's DPR file instead and not even create the MainForm at all if necessary, eg:
Application.Initialize;
if not (some condition) then
begin
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end;
Well if you want to stick to your bad design, here's a bad answer:
try
Application.Terminate
except
end;
Application.Terminate
is good enough unless you care to refine your design to check these conditions before the form is created (in the dpr).
Related
How can I be sure that in some point of my VCL application lifetime the Application.MainForm is valid so I could post a message to it (from a MadExcept ExceptionHandler).
This could be at any point (in the context of any thread) in my application (also initialization, finalization etc...)
I was thinking:
if Assigned(Application)
and (not Application.Terminated)
and Assigned(Application.MainForm)
and Application.MainForm.HandleAllocated then
begin
PostMessage(Application.MainForm.Handle, MyMessage, 0, 0);
end;
Is this correct?
How can I be sure that in some point of my VCL application lifetime the Application.MainForm is valid so I could post a message to it.
OK.
This could be at any point (in the context of any thread) in my application (also initialization, finalization etc...)
Uh oh.
....
Is this correct?
No it certainly is not. Your code can never be made threadsafe because it is not permitted to access VCL objects from outside the main thread.
In your particular case consider the following sequence of events:
You perform your tests in the if culminating in your evaluating Application.MainForm.HandleAllocated as True. Ignore for a moment the fact that you are doing this outside the main thread.
You then set about preparing the call to PostMessage. But at this very instance, the main form is destroyed.
By the time your thread gets round to accessing Application.MainForm, it has gone.
You are going to need to work a little harder here. You'll need to do something like this:
// interface section of some unit
procedure RegisterMainFormHandle(Wnd: HWND);
procedure UnregisterMainFormHandle;
procedure PostMessageToMainForm(...);
// implementation section
var
MainFormHandleLock: TCriticalSection;
MainFormHandle: HWND;
procedure RegisterMainFormHandle(Wnd: HWND);
begin
MainFormHandleLock.Acquire;
try
MainFormHandle := Wnd;
finally
MainFormHandleLock.Release;
end;
end;
procedure UnregisterMainFormHandle;
begin
MainFormHandleLock.Acquire;
try
MainFormHandle := 0;
finally
MainFormHandleLock.Release;
end;
end;
procedure PostMessageToMainForm(...);
begin
MainFormHandleLock.Acquire;
try
if MainFormHandle <> 0 then
PostMessage(MainFormHandle, ...)
finally
MainFormHandleLock.Release;
end;
end;
You also need to create and destroy the critical section, but I assume that you know how to do that.
In your main form you override CreateWnd and DestroyWnd and arrange that they call RegisterMainFormHandle and UnregisterMainFormHandle.
Then you can call PostMessageToMainForm from any thread at any time.
Of course, if the main form's window is recreated then you'll lose some messages. Which sounds like it could be a problem. Using AllocateHwnd to have a window whose lifetime you control is usually a better option than using the main form's window like this.
Make some global variable flag = false at the beginning.
Make your mainform turn it to true.
Check that flag to see if main form was already initialised or not yet.
You can make it from such places as mainform's OnActivate event or overridden TMainForm.Loaded method
Similarly when your application would be terminating and the mainform would get hidden (and later even destroyed) - you would reset the flag back to false
At program start, in the OnActivate event handler, I need to do something which blocks the program for a few seconds. During this time the form's client area is still not completely painted, which looks ugly for the user. (During this blocked time I don't need the program to respond to clicks or other user actions, so there is no need to put the blocking operation into a thread - I just need the form to be completely painted). So I use TForm.Update and Application-ProcessMessages to update the form before the blocking operation which works very well:
procedure TForm1.FormActivate(Sender: TObject);
begin
Form1.Update;
Application.ProcessMessages;
Sleep(7000);
end;
However, I wonder whether there is not another more elegant solution for this problem. This could be for example a OnShown event implemented in a descendant of TForm which will be fired AFTER the form has been completely painted. How could such an event be implemented?
Your real problem is that you are blocking the UI thread. Simply put, you must never do that. Move the long running task onto a different thread and thus allow the UI to remain responsive.
If you are looking for event which is fired when application finishes loading/repainting you should use TApplication.OnIdle event
http://docwiki.embarcadero.com/Libraries/XE3/en/Vcl.Forms.TApplication.OnIdle
This event is fired once application is read to recieve users input. NOTE this event will be fired every time application becomes idle so you need to implement some controll variable which will tel you when OnIdle even was fired for the first time.
But as David already pointed out it is not good to block your UI (main thread). Why? When you block your main thread the application can't normally process its messages. This could lead to OS recognizing your application as being "Hanged". And aou definitly wanna avoid this becouse it could cause the users to go and forcefully kill your application whihc would probably lead to data loss. Also if you ever wanna design your application for any other platforms than Windows your application might fail the certification proces becouse of that.
In the past a simple PostMessage did the trick.
Essentially you fire it during DoShow of the base form:
procedure TBaseForm.DoShow;
begin
inherited;
PostMessage(Handle, APP_AFTERSHOW, 0, 0);
end;
then catch the msg and create an AfterShow event for all forms inherited from this base form.
But that no longer works, well not if you are skinning and have a good number of VCL controls.
My next trick was to spawn a simple thread in DoShow and check for IsWindowVisible(Handle) and IsWindowEnabled(Handle). That really sped things up it cut 250ms from load time since db opening and other stuff was already in the AfterShow event.
Then finally I thought of madHooks, easy enough to hook the API ShowWindow for my application and fire APP_AFTERSHOW from that.
function ShowWindowCB(hWnd: HWND; nCmdShow: Integer): BOOL; stdcall;
begin
Result := ShowWindowNext(hWnd, nCmdShow);
PostMessage(hWnd, APP_AFTERSHOW, 0, 0);
end;
procedure TBaseForm.Loaded;
begin
inherited;
if not Assigned(Application.MainForm) then // Must be Mainform it gets assigned after creation completes
HookAPI(user32, 'ShowWindow', #ShowWindowCB, #ShowWindowNext);
end;
To get the whole thing to completely paint before AfterShow it still needed a ProcessPaintMessages call
procedure TBaseForm.APPAFTERSHOW(var AMessage: TMessage);
begin
ProcessPaintMessages;
AfterShow;
end;
procedure ProcessPaintMessages; // << not tested, pulled out of code
var
msg: TMsg;
begin
while PeekMessage(msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE) do
DispatchMessage(msg);
end;
My final test was to add a Sleep to the AfterShow event and see the Form fully painted with empty db containers since the AfterShow events had not yet completed.
procedure TMainForm.AfterShow;
begin
inherited;
Sleep(8*1000);
......
I have a simple Delphi program that I'm working on, in which I am attempting to use threading to separate the functionality of the program from its GUI, and to keep the GUI responsive during more lengthy tasks, etc. Basically, I have a 'controller' TThread, and a 'view' TForm. The view knows the controller's handle, which it uses to send the controller messages via PostThreadMessage. I have had no problem in the past using this sort of model for forms which are not the main form, but for some reason, when I attempt to use this model for the main form, the message loop of the thread just quits.
Here is my code for the threads message loop:
procedure TController.Execute;
var
Msg : TMsg;
begin
while not Terminated do begin
if (Integer(GetMessage(Msg, hwnd(0), 0, 0)) = -1) then begin
Synchronize(Terminate);
end;
TranslateMessage(Msg);
DispatchMessage(Msg);
case Msg.message of
// ...call different methods based on message
end;
end;
end;
To set up the controller, I do this:
Controller := TController.Create(true); // Create suspended
Controller.FreeOnTerminate := True;
Controller.Resume;
For processing the main form's messages, I have tried using both Application.Run and the following loop (immediately after Controller.Resume)
while not Application.Terminated do begin
Application.ProcessMessages;
end;
I've run stuck here - any help would be greatly appreciated.
I tested your code basically as-is and it worked fine. Try adding a call to GetLastError after GetMessage returns -1 to see what the problem is.
It's not completely clear from the code whether you're creating windows within the controller thread, but if not, I'd suggest passing -1 instead of 0 as the HWND to GetMessage, and remove the TranslateMessage/DispatchMessage calls, since the case statement that follows them should handle any messages you receive.
Also, you don't need to do "Synchronize(Terminate)" on an error. Terminate just sets the "Terminated" boolean to true, so you don't need to synchronize it, and you could just as easily use "Break" to break out of the loop with the same effect.
Where's the "end" for the while loop?
I think you're missing and end. So maybe (depending on the actual code), you're stuck in the while not Terminated do looping endlessly on a single statement.
Perhaps, it is very easy for you, but I am hard working on a project (for educational purposes) that is querying adsi with TADSISearch component, for several days. I'm trying to show a 'Working, Please wait..' splash screen with a man worker animated gif on Form2 while TADSISearch is searching the Active Directory. Although i tried every possibilities according to me, but i couldn't succeed. I tried to use TADSISearch in a thread, but thread is terminating before ADSIsearch finishes. I think TADSISearch is not thread safe. What do you think? Also, another way that I created Form2 and used a thread for updating it but the animated gif is stopping while main form gone adsi searching. What can you say about these? How can i make a please wait screen while ADSISearch is working and keep main form responding. Application.ProcessMessages or timer is not a way too. Thanks a lot for reading and answers.
The graphical user interface should be updated by the main thread. You should put your search code into a separate thread, and while the searcher thread is working, your main thread can show the animation along with "Please wait" message.
Your searcher thread can notify the main thread when search is done by any of the available synchronization techniques. The simplest one is to define a method in your thread class which stops the animation in user interface, and pass that method to Synchronize at the end of Execute method of your searcher thread.
Your searcher thread code will be something like this:
type
TMyThread = class(TThread)
private
procedure NotifyEndOfThread;
protected
procedure Execute; override;
end;
implementation
uses MainFormUnit;
procedure TMyThread.NotifyEndOfThread;
begin
MainForm.ShowAnimation := False;
end;
procedure TMyThread.Execute;
begin
try
{Add your search code here}
finally
Synchronize(NotifyEndOfThread);
end;
end;
And your main thread's code will be like this:
TMainForm = class(TForm)
...
private
FShowAnimation : Boolean;
procedure SetShowAnimation(Value: Boolean);
public
property ShowAnimation : Boolean read FShowAnimation write SetShowAnimation;
end;
procedure TMainForm.SetShowAnimation(Value: Boolean);
begin
FShowAnimation := Value;
if FShowAnimation then
{Add animation code here}
else
{Stop animation}
end;
Maybe you can try this:
Threaded Splashscreen for Delphi
http://cc.embarcadero.com/Item/20139
I use this on a touchscreen/terminal application (thin client, Wifi, RemObjects, etc) and it works nice!
Also got an animated gif working.
How can the thread terminate before the search is finished? If the search is executed in the thread and you have only one instance of the thread it should work.
Can you not just do a
f := TMyWaitForm.Create(self);
try
f.Show();
...start the TADSISearch...
finally
FreeAndNil(f);
end;
Putting an animated GIF on the TMyWaitForm (which displays itself) ?
I have a progress form when building websites in my web creation program, and this works like a charm.
You even may consider showing some state information on the wait form (if the TADSISearch component/software has a call back function or event which can be assigned).
Displaying a running clock showing the amount of time the process is taking, is also a nice touch.
This should be a simple one for someone. I just can't figure out how to do it.
Upon exiting of my program, I want to hide the main form and make a final "Thank You" form appear on its own, like this:
procedure TMainForm.ExitExecute(Sender: TObject);
begin
MainForm.Visible := false;
ThankYouForm.Show;
MainForm.Close;
end;
But when I do that, I get the Exception:
EInvalid Operation: Cannot change Visible in OnShow or OnHide
So how do I show a final form, while hiding the main form when exiting a program in Delphi?
Conclusion: Mghie confirmed that what I was trying was correct and should have worked. That indicated that I had a bug somewhere in my procedures of exiting and closing from my forms that was bringing up this exception.
Now that I know that, it won't take me long to find and fix the problem.
Found the problem: I was closing my main form from within the ThankYouForm, and that somehow looped back through into ExitExecute and, well, it got all bunged up.
But all's well again. The MainForm.Hide before the ThankYouForm.ShowModal works perfectly.
Thanks again, guys.
Instead of trying to shoehorn something into the main form, go to the place where you know everything else is finished running: the point where Application.Run returns. Create a new procedure that creates, shows, and destroys your farewell form, and then call it in your DPR file like this:
begin
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
TThankYouForm.Execute;
end.
The display function can be along the lines of what Mghie's answer demonstrated:
class procedure TThankYouForm.Execute;
begin
with Create(nil) do try
ShowModal;
finally
Free;
end;
end;
You could do that in the OnClose handler of the main form. Be sure to ShowModal the other form, because otherwise it will be closed immediately when the closing of the main form terminates the application:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Hide;
with TThankYouForm.Create(nil) do try
ShowModal;
finally
Free;
end;
Action := caFree;
end;
or even
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Hide;
with TThankYouForm.Create(Application) do
ShowModal;
Action := caFree;
end;
And be sure to make the behaviour optional - when the user closes the app they are finished with it, and not everybody is pleased with programs that are so reluctant to go away.
Edit:
OK, showing such a form at the end of the trial period does indeed make sense. And while I can't really say why your code raises the exception - you should be able to find out by compiling with debug DCUs, setting a breakpoint on the line that raises the exception, and examine the stack trace. I assume some combination of the form properties and your code leads to another change of the Visible property higher up the stack, and you need to find out what it is and correct that. The code above should really work.
I would put (try) any of the code supplied above in the main form's OnCloseQuery event. Ensure that can close := false until you are ready to close the main form.
This may be caused by difference between order of method calls with order of message handler processing. After your method has completed there are still messages in operating system queue and they are being dispatched and handled by VCL.