Display progress from time consuming process - delphi

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.

Related

why does tmediaplayer delay a caption changing on a tpanel?

I am a novice programmer, so apologies if this sounds very basic to you all.
I have a procedure which looks (essentially) like this:
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
panel1.caption:='This is a sentence';
with MediaPlayer1 do
begin
filename:='f:\untitled.wma';
open;
wait:=true;
play;
close;
end;
end;
The problem is that the caption on panel1 doesn't change until the mediaplayer has played the sound file; I need the caption to change, and the player to start playing, simultaneously. How can I ensure this?
I thought that the procedure would execute each line of code sequentially, meaning that the caption of panel1 changes, followed by the mediaplayer springing into action. Where have I gone wrong?
Explanation:
VCL components (like TPanel) usually have an internal method called Invalidate() that is called when a property (like Caption) changes and that change requires repainting part of the control (eg. to draw the new caption text).
This method only sets a flag inside the window control, but does not invoke the repaint method itself. The reason for that is to avoid calling the Repaint() method multiple times, if many properties are changed at once (sequentially, in short time).
The Repaint() method is actually called when the component receives a message to repaint via the main message loop (processed from the main thread of the application - the GUI thread).
The way you start playing the media player is blocking, because you set the Wait property to True, which makes the player block the calling thread (again the main thread) until the file has been played.
This does not give a chance to the main thread to process it's message queue and initiate the repaint.
Quick fix:
A quick fix to the problem is either the one suggested by becsystems, or this one:
panel1.Caption := 'This is a sentence';
Application.ProcessMessages();
Calling ProcessMessages() will give the main thread the opportunity to process the message queue and perform the update, just before starting to play the file.
This is a quick fix, as the main thread will still be blocked after starting to play, which will prevent other portions of the window to repaint (eg. Try moving the window around or minimizing and maximizing it while playing).
The code suggested by becsystems is similar, but instead of processing the message queue, just forces the control to repaint.
Proper fix:
To properly fix the problem you should not use the Wait property and instead handle the OnNotify event of the media player.
Here is an example, adapted from Swiss Delphi Center (not tested as I do not have Delphi installed at the moment):
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
panel1.Caption := 'This is a sentence';
with MediaPlayer1 do
begin
Notify := True;
OnNotify := NotifyProc;
Filename := 'f:\untitled.wma';
Open;
Play;
end;
end;
procedure TForm1.NotifyProc(Sender: TObject);
begin
with Sender as TMediaPlayer do
begin
case Mode of
mpStopped: {do something here};
end;
// Set to true to enable next-time notification
Notify := True;
end;
end;
Side notes:
There is a short explanation of the VCL message loop (part of Delphi Developer's Guide) published here:
Anatomy of a Message System: VCL
Also, not related to the problem, but take a look at Delphi Coding Style Guide. It's just nice when code posted is formatted.
Add a Refresh call after setting the caption, i.e.:
panel1.caption:='This is a sentence';
Refresh;

Delphi: Repaint form in FormShow

I open Form2.ShowModal in FormMain. I want the application to show Form2 intact while doing some database access (this is not about the new data to be shown). However, while FormShow is executed, just the outer border and some broken parts are displayed, some broken parts of FormMain show through. It's ugly.
I have not been able to find a way to make Delphi repaint the Form immediately and then doing the time-consuming MyOpenData procedure. After concluding MyOpenData everything is fine.
procedure TForm2.FormShow(Sender: TObject);
begin
Invalidate;
Refresh;
MyOpenData; { needs some seconds of database accesses }
end;
Alternative:
procedure TForm2.FormShow(Sender: TObject);
begin
Invalidate;
Refresh;
SendMessage(Handle, wm_paint, 0, 0);
PostMessage(Handle, wm_OpenMyData, 0, 0); { executes well, but no solution)
end;
This doesn't work either. I thought SendMessage() waits for the message being done. But no Paint is done before MyOpenData. The form always looks broken till the procedures finishes. Besides this, the routines are executed fine. I tried all these commands combined or separately.
What am I missing? Thanks in advance!
How do you start time-consuming routines that need to run when opening a form?
(Delphi XE7 on Windows 7 64 bit)
uses
WinApi.Windows;
const
WM_AFTER_SHOW = WM_USER + 1; // custom message
WM_AFTER_CREATE = WM_USER + 2; // custom message
private
procedure WmAfterCreate(var Msg: TMessage); message WM_AFTER_CREATE;
procedure WmAfterShow(var Msg: TMessage); message WM_AFTER_SHOW;
procedure TForm1.WmAfterCreate(var Msg: TMessage);
begin
DoSomeThingAfterCreate();
ShowMessage('WM_AFTER_CREATE received!');
end;
procedure TForm1.WmAfterShow(var Msg: TMessage);
begin
DoSomeThingAfterShow();
ShowMessage('WM_AFTER_SHOW received!');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
// Some code...
PostMessage(Self.Handle, WM_AFTER_CREATE, 0, 0);
end;
procedure TForm1.FormShow(Sender: TObject);
begin
// Some Code...
PostMessage(Self.Handle, WM_AFTER_SHOW, 0, 0);
end;
There simply isn't enough information given to make any specific recommendations here, IMO.
I'm going to guess that MyOpenData() sets up some kind of data state that Form2 relies upon. If this is the case, then you probably want to call it BEFORE calling Form2.ShowModal. In no case should you be calling either invalidate or refresh inside of the OnShow handler because they both trigger OnShow.
Watch the video I made for CodeRage 9 entitled, "Have You Embraced Your Software Plumber Lately?" (search YouTube for 'coderage software plumber') as this is the exact topic I'm addressing in this video -- the whole plethora of issues involved in initializing forms and objects, along with the timing issues specific to forms.
I don't discuss issues with data-aware controls specifically, but they're pretty much the same. It can be problematic setting up the DB state from inside the form that depends upon that state in order to properly initialize itself. There's an inherent race-condition that is easily avoided by doing your dependent initialization first, then injecting that dependency into the form.
If DBs are involved, you have to inject SOMETHING into the form: either the DB reference (usually through a global variable); a table (either a global variable or a form variable); or a current record (usually a form variable). The nice part about using DB-aware controls is that the initialization is always implicit, and you don't have to inject anything. The bad part about using DB-aware controls is, the initialization is ALWAYS IMPLICIT, and you have no EXPLICIT control over initialization sequences. By doing EXPLICIT injection of DB dependencies, you side-step the timing issues. It's a little more work (not much), but you don't have to deal with stuff like this.
In any case, if the form needs a current record to initialize its fields, you can't display the form until the record has been selected, and you can't make the record selection part of the form's initialization process without risking concurrency issues. It can be done, but you're making one heck of a mess out of it.

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 disable form while loading

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.

'working, please wait' screen with thread?

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.

Resources