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.
精彩评论