tags:

views:

182

answers:

4

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?

+3  A: 

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

Jeroen Pluimers
+2  A: 

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.

Larry Lustig
+3  A: 

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.

Alister
yes, thanks for the reply... I was trying to avoid going to the TThread way - i was thinking that for something so simple there must be another way, guess i was wrong - i don't know why but every time i look at some delphi's multithread code i think "there is something wrong here" :/
Jeff
the problem is that it is virtually impossible to get this really smooth. the only way to get it smooth is if you can *guarantee* that the pieces inside time_consuming_method to be very very short.
Jeroen Pluimers
A: 

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.
Mihaela