Delphi disable form while loading - delphi

In my application, I have a main form, with the ability to load some images in database.
While images is loading, I want to show a form, with the progress indicator (with bsNone border style).
But, if I show with form with ShowModal, execution of main form is stopped, so I can't to that.
If I call Show, user have access to all other form components, and it can be dangerous, while photo is not loaded completely.
I need to get the way, to disable everything on main form, while loading isn't completed.
Please, advice me, how it is possible.

Set the MainForm as the PopupParent for the progress form so that the MainForm can never appear on top of the progress form. Then simply set MainForm.Enabled := False while the progress form is open and set MainForm.Enabled := True when the progress form is closed.
procedure TMainForm.ShowProgressForm;
begin
with TProgressForm.Create(nil) do
begin
PopupParent := Self;
OnClose := ProgressFormClose;
Show;
end;
Enabled := False;
end;
procedure TMainForm.ProgressFormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
Enabled := True;
end;
This simulates howShowModal() behaves to the user (the MainForm is not user-interactive while the progress form is open), but without blocking the code.

First of all, the direct answer to your question.
I need to get the way, to disable everything on main form.
Set MainForm.Enabled to False to disable the window associated with the main form. And to re-enable set it to True instead.
Your fundamental problem however, is that you are executing long running tasks in the GUI thread. That's always a bad idea and the way out is to execute those long running tasks in a separate thread.
Once you move the long running tasks to a separate thread then you will find that ShowModal is exactly what you need to show your progress form.

As I explained in my other answer, putting the long running task into a thread other than the GUI thread is the ideal solution. The GUI thread should handle short running tasks so that it is always able to service the message queue in a timely fashion.
However, if you already have code that assumes that the long running task runs on the GUI thread, you may prefer to take a more expedient approach and postpone the re-factoring to threaded code. In which case, in my view, it is still better to use ShowModal to display your progress form.
But in order to make that work, you need to find a let the long running task execute inside the ShowModal call. You can do that as follows:
Before you call ShowModal, pass the task to the form. For example, pass a TProc to the constructor of the progress form.
Override the progress form's Activate method. This will get executed just before the ShowModal function starts its modal message loop. In the implementation of Activate, post a message to the form.
When the form handles that message, invoke the task that was passed to the constructor.
Obviously you'll need to call ProcessMessages in your long running task, in order to keep the main GUI thread message queue serviced. Clearly you must already be doing that.

Set the PopupParent of child form = ParentForm
procedure TParentForm.Button1Click(Sender: TObject);
begin
ParentForm.Enabled:=False;
with Tform1.create(nil) do show;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
ParentForm.Enabled := true;
form1.free;
end;

You may want to use DisableTaskWindows and EnableTaskWindows functions.
If you use simple ParentForm.Enabled := False for the parent form, you can still access all other forms, like the main form if it differs from ParentForm. It is obviously still dangerous.
Here is short sample:
interface
uses
Vcl.Forms, Winapi.Windows, ...;
type
TPleaseWait = class(TObject)
private
fWindowList: TTaskWindowList;
fActiveWindow: HWND;
fDialog: TPleaseWaitForm;
public
constructor Create;
destructor Destroy; override;
end;
implementation
constructor TPleaseWait.Create;
begin
// Disable all displayed windows
fWindowList := DisableTaskWindows(0);
// Save the last active window
fActiveWindow := GetActiveWindow;
fDialog := TPleaseWaitForm.Create(nil);
fDialog.Show;
end;
destructor TPleaseWait.Destroy;
const
INVALID_HANDLE = 0;
begin
fDialog.Close;
fDialog.Free;
// All windows are enabled now
EnableTaskWindows(fWindowList);
// That helps by enabling the last one form
if (fActiveWindow <> INVALID_HANDLE) and IsWindow(fActiveWindow) then
SetActiveWindow(fActiveWindow);
inherited;
end;
end.

Related

How to check if the Application.MainForm is valid?

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

OnShown event for TForm?

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);
......

Delphi: refer to control from thread in frame

There is a FRAME (not a form) and a thread. How to refer to Frame's control from the thread? For example I want to disable a button from a thread. But I don't have a pointer to the button, no global variables in the frame.
Thanks!
You should not in fact, call any method or modify any property of an VCL control at all, or anything visible to the user (the User interface of your application, which means VCL controls normally in Delphi, whether in a frame or not) directly from a background thread.
You can however send an event or notification to the main thread, either using PostMessage, or TThread.Synchronize or TThread.Queue.
Rather than having a reference to the frame or the control in your thread object, it might be better to just pass the handle of the form that contains your frame or other controls, to the thread, and use a user-message (WM_USER+10001) like this.
I prefer PostMessage to TTHread.Synchronize or Queue, because it's really simple and it works great. It's not exactly a cross-platform-friendly technique since it's tied to the Win32 API.
You should call synchronize like this:
TMyThread = class(TThread)
private
FFrame: TFrame;
...
public
constructor Create(AFrame: TFrame);
...
end;
constructor TMyThread.Create(AFrame: TFrame);
begin
FFrame := AFrame;
inherited Create;
end;
// do not call directly, only using Synchronize
procedure TMyThread.AMethodWithNoParameters;
begin
FFrame.Button1.Enabled := not FBusy;
end;
procedure TMyThread.DoWork; // called from Execute.
begin
FBusy := true;
Synchronize(AMethodWithNoParameters);
Sleep(100); //dummy;
FBusy := false;
Synchronize(AMethodWithNoParameters);
end;
As quite rightly pointed out, you cannot call any members of any visual component in a background thread.
To disable the button from inside the thread code you have to have a reference to the button OR a reference to an event which you can assign the thread object - you can then fire the thread inside the queued or synchronized procedure, like so :-
type
test=class(tthread)
ondisablebutton:tnotifyevent;
{...}
then, when in the procedure which you encapsulate with tthread.synchronize you can call the event, not forgetting to test if it is assigned....
procedure test.synchronisedprocedure;
begin
if assigned(ondisablebutton) then
ondisablebuttone(self);
end;
When you create the thread object you have designed, you then have to assign the ondisablebutton to a procedure of the form containing the button which looks like thus :-
procedure form1.threadwantstodisablebutton(sender:tobject);
begin
button1.enabled:=false;
end;
your thread creation then needs an extra line :-
mythread:=test.create;
test.ondisablebutton:=form1.threadwantstodisablebutton;
like so, obviously you have to have access to form1 (or the form containing the button) where you are defining and creating your thread, which is not necessarily good design but it works.

Display progress from time consuming process

Sorry for my bad English...
Using Delphi 7 I want to create a dialog window to show that something is happening in my application when i have to run slow processes.
My idea was to do something that i can use like:
with TMyDialog.Create do
begin
//call the time consuming method here
Free;
end;
When i create the dialog, a window with an animation or something will show and will disappear after the time consuming method ends (on the free method) - it would be nice if I could manually update the progress from that dialog, in cases when the process give me such information:
with TMyDialog.Create do
begin
while time_consuming_method do
begin
UpdateStatusOnMyDyalog();
end;
Free;
end;
but normally it would only be a animation to show that something is happening.
Has someone did something like that, knows a component or have any suggestions on whats the best way to do it in the most clean and simple way?
The bad but easy way to do this is to call Application.ProcessMessages or UpdateWindow(Handle) (to update the form) and increment a progressbar during your time_consuming_method. A slightly better method would be to wrap your time_consuming_method up into a class with an OnProgress event. Finally as other people have suggested you could use a separate thread for your time_consuming_method - which is the most powerful technique, but has the worst learning curve.
You will need to run your time consuming process in a separate thread, and have that thread report its' progress to your main UI thread using synchronization.
Here is an example that shows you how to start a new thread and have that thread do the synchronized progress reporting.
--jeroen
It's quite common to report progress in this way (using, for instance, a progress bar).
Your "time consuming process" needs to receive either a callback function that will be called every time it has some progress to report or, if you are willing to bind it more tightly with your user interface design, a reference to a component of some kind that it will know how to update. This can be a progress bar which it will step, a listbox or memo field that will receive a new line with status updates, a label control the caption of which will get updated, and so on.
Displaying a progress during long operations depend on several factors (limitations) :
Defined/undefined progress (you know,
may calculate, how many steps does
the operation take)
Interruptibility/segmentation (you
will be able, or have to, interrupt
the operation to refresh the progress
to the user)
The operation is thread-able (you may
put the operation a thread)
For defined progress it's common to display a segmented progress bar, and for undefined an animation or progress bar with the style "Marquee".
The main consideration is whether the operation is segmented/interruptible or not. Because if it's not, and you don't take care of it, your application will freeze until the operation finishes.
Searching for files is one example of segmented operation. Each found file is one segment , and it gives you the ability to display the progress to the user, and refresh the display.
Example:
TFrmUndefinedProgress = class(TForm)
private
FCallbackProc : TNotifyEvent;
protected
procedure WndProc(var Message:TMessage); override;
public
constructor Create(aCallbackProc: TNotifyEvent);
procedure UpdateProgress(const aStr : string; aPercent : integer);
...
constructor TFrmUndefinedProgress.Create(aCallbackProc: TNotifyEvent);
begin
inherited Create(nil);
FCallbackProc := aCallbackProc;
end;
...
procedure TFrmUndefinedProgress.FormShow(Sender: TObject);
begin
Update;
PostMessage(Handle, WM_START_UNDEFPROG, 0, 0);
end;
Send message to window procedure on your form's OnShow, to make sure that it will be rendered first.
procedure TFrmUndefinedProgress.WndProc(var Message: TMessage);
begin
if (Message.Msg = WM_START_UNDEFPROG) then begin
if Assigned(FCallbackProc) then
FCallbackProc(Self); --> Call your callback procedure
PostMessage(Handle, WM_CLOSE, 0, 0); --> close when finished
end
else
inherited;
end;
And if you make a regular procedure in your form's unit...
procedure ShowUndefinedProgress(aCallbackProc : TNotifyEvent);
var
FrmUndefinedProgress : TFrmUndefinedProgress;
begin
FrmUndefinedProgress := nil;
try
FrmUndefinedProgress := TFrmUndefinedProgress.Create(aCallbackProc);
FrmUndefinedProgress.ShowModal;
finally
FreeAndNil(FrmUndefinedProgress);
end;
end;
You then may call progress form like this:
ShowUndefinedProgress(HandleSomeOperation);
where you pass your aCallbackProc.
Inside you put your operation:
procedure TForm1.HandleSomeOperation(Sender: TForm);
var
aProgress : TFrmUndefinedProgress;
begin
--> Do something
aProgress := TFrmUndefinedProgress(Sender);
aProgress .UpdateProgress(SomeMessage, Percent);
Update the display for each found file ...
If you have operation that takes long time, but you have no way of interrupting it, then you should put it in a thread.
Create a descendant of the TThread object.
Override it's Execute method
Do your thing inside Execute
And use it:
Create a form
Start some animation on it's OnShow
Then run your thread
Close when thread finishes.

How Do I Show A Final Form On Exiting In Delphi?

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.

Resources