Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

What does Application.ProcessMessages do, and how do I use it?

Performance

What does Application.ProcessMessages do, and how do I use it?

by  djjd47130  Posted    (Edited  )
From the Forms unit in Delphi, Application.ProcessMessages is a command which can save lives, yet can also kill. It has a very important yet sensitive task. It's simple actually, all it does is makes sure that Windows finishes processing all the commands sent to it before the application continues with the code. This especially applies for the drawing of controls.

Suppose you have a list. You are loading data from a database and populating hundreds of values into this list. While you loop through the dataset record by record, you're also doing some heavy work per record, which in turn adds a new item to this list. Let's say this one procedure of refreshing the list typically takes 10 seconds to finish. During those 10 seconds, your application will become unresponsive, and Windows will start calling your application "Not Responding", because really, it isn't. Doesn't mean your application isn't working though.

You may also notice that while this loop is being performed, each item which is added to the list doesn't actually visually appear in the list yet, until the procedure is 100% finished. This is a performance enhancement built in. It's sending messages to Windows to redraw the controls, but it's being put into a queue, waiting for your code to finish before it's actually processed. Application.ProcessMessages forcefully processes this queue of messages to windows to make sure everything you told it to do is actually done before continuing.

Put Application.ProcessMessages inside this loop at the end. After it's there, you will notice the program will start responding again, and that the items will appear in the list as they're actually added. No more wait until your procedure is finished to see the new list.

The problem is, Application.ProcessMessages can also be very dangerous. It's costly because it's of course forcefully making things happen which would have otherwise been queued until a time when it would be more legit. Using more of this command means a slower, yet more responsive application. Not using it results in faster, but nonresponsive applications. Use it only when you really need to. For example, when I need to set the caption of a particular label, and want to make sure this label is in fact displayed before the code is continued, I put a procedure around it, where just after I set the caption label, I call this command...

Code:
[b]procedure[/b] Form1.SetStatus(Value: String);
[b]begin[/b]
  lblStatus.Caption:= Value; [navy][i]//Set the label's caption
[/i][/navy]  Application.ProcessMessages; [navy][i]//Make sure it's actually displayed before continuing
[/i][/navy][b]end[/b];


This way, I just call 1 line of code to do 2 things, Set the caption, and make sure that caption is actually shown. Also of course is really handy when making properties, but that's another story.

On your program's startup, it can be used to forcefully show your main form before the OnCreate event is even finished (thus showing your form as it's loading)...

Code:
[b]procedure[/b] Form1.FormCreate(Sender: TObject);
[b]begin[/b]
  Self.Show;
  Self.BringToFront;
  Application.ProcessMessages;
  [navy][i]//Do some other heavy loading, knowing the form is already visible 
[/i][/navy]  [navy][i]//to the user, such as a progress bar showing the loading status
[/i][/navy][b]end[/b];

and on your form's close event...

Code:
[b]procedure[/b] Form1.FormClose(Sender: TObject;
  [b]var[/b] Action: TCloseAction);
[b]begin[/b]
  Hide;
  Application.ProcessMessages;
[b]end[/b];


Now if you decide to use it in a timer, there's an important flaw to be aware of. Take this scenario for example...

- You have a timer with an interval smaller than 50 ms
- OnTimer event of Timer has procedure which takes longer than 50 ms to finish
- Application.ProcessMessages is being called at the end of this OnTimer event procedure

If you have something like this, you're risking the same procedure being called many times in a row. Suppose it takes the procedure 500 ms to finish (100 times the timer's interval). Without using this command, (chances are) you'll be fine. But if you do use Application.ProcessMessages in the timer, be aware that the procedure can and most likely will be called many times in a row. I once got the same large bitmap to be loaded over 12,000 times in a row at the same time, thus filling my memory entirely up and crashing my computer (thank God for debug mode). Many would like to think and wish such a thing was not possible, but it is, and I have suffered from it myself, until I discovered the proper way to go about this...

Code:
[b]var[/b]
  fExecuting: Bool;

[b]procedure[/b] TForm1.FormCreate(Sender: TObject);
[b]begin[/b]
  fExecuting:= False; [navy][i]//Set fExecuting by default to False
[/i][/navy][b]end[/b];

[b]procedure[/b] TForm1.TimerOnTimer(Sender: TObject);
[b]begin[/b]
  [b]if[/b] [b]not[/b] fExecuting [b]then[/b] [b]begin[/b] [navy][i]//Check if executing
[/i][/navy]    fExecuting:= True; [navy][i]//Set fExecuting to True
[/i][/navy]    [b]try[/b]


      [navy][i]//Do your heavy work here
[/i][/navy]

    [b]finally[/b] [navy][i]//If exception occurs, fExecuting still reverted back to False
[/i][/navy]      Application.ProcessMessages;
      fExecuting:= False;
    [b]end[/b];
  [b]end[/b];
[b]end[/b];


In the end, it won't overlap.

Here's a test I put together to validate this claim, and see that I'm not kidding...

Form1
lblStatus: TLabel
Timer1: TTimer

Code:
[b]var[/b]
  fRunning: Integer; [navy][i]//For tracking the number of times procedure is running
[/i][/navy]
[b]procedure[/b] TForm1.FormCreate(Sender: TObject);
[b]begin[/b]
  fRunning:= [navy]0[/navy]; [navy][i]//Set fRunning to 0 by default (not yet running)
[/i][/navy]  Timer1.Interval:= [navy]1[/navy];
  Timer1.Enabled:= True;
[b]end[/b];

[b]procedure[/b] TForm1.Timer1Timer(Sender: TObject);
[b]const[/b]
  FN = [navy]'D:\Media\Pictures\GForce\Toxic.bmp'[/navy];
[b]var[/b]
  X: Integer;
  B: TBitmap;
[b]begin[/b]
  fRunning:= fRunning + [navy]1[/navy];
  lblStatus.Caption:= IntToStr(fRunning)+[navy]' running at a time'[/navy];
  Application.ProcessMessages;
  [b]try[/b]
    [b]for[/b] X:= [navy]0[/navy] [b]to[/b] [navy]50[/navy] [b]do[/b] [b]begin[/b]
      B:= TBitmap.Create;
      [b]try[/b]
        B.LoadFromFile(FN);
        Canvas.StretchDraw(Rect(X,X,Width,Height), B);
        Application.ProcessMessages; [navy][i]//Try commenting this out
[/i][/navy]      [b]finally[/b]
        B.Free;
      [b]end[/b];
    [b]end[/b];
    [b]for[/b] X:= [navy]50[/navy] [b]downto[/b] [navy]0[/navy] [b]do[/b] [b]begin[/b]
      B:= TBitmap.Create;
      [b]try[/b]
        B.LoadFromFile(FN);
        Canvas.StretchDraw(Rect(X,X,Width,Height), B);
        Application.ProcessMessages; [navy][i]//Try commenting this out
[/i][/navy]      [b]finally[/b]
        B.Free;
      [b]end[/b];
    [b]end[/b];
  [b]finally[/b]
    lblStatus.Caption:= IntToStr(fRunning)+[navy]' running at a time'[/navy];
    Application.ProcessMessages;
    fRunning:= fRunning - [navy]1[/navy];
  [b]end[/b];
[b]end[/b];

Now I'm not saying it's OK to create/load/draw/destroy a bitmap repeatedly in a loop like above, that's merely a sample of a very heavy process being performed. Replace the bitmap path with your own large bitmap. Comment out the Application.ProcessMessages inside the 2 loops as mentioned in the commented code to see the picture stretch and move on the form (as a sample of heavily redrawing the image). You will see the label displaying the number of times it's running will steadily grow faster than it shrinks. Then check the Task Manager and watch the memory usage. The line will grow to the top and finally result in a Windows Error saying it's out of memory.

This is simply because by calling Application.ProcessMessages inside the loop, you're telling Windows that it's "OK" to go ahead and execute the timer again.

This theory can still use more extensive testing, and it's still not a definite conclusion on the reaction of Application.ProcessMessages. It's also been mentioned that a timer such as the one mentioned above will "stop executing" any prior procedure and forcefully restart that procedure. If this is true, then objects created at the beginning of the timer execution will never get destroyed. I'm sure that has already been identified as an issue and has been accounted for, but you never know.


Register to rate this FAQ  : BAD 1 2 3 4 5 6 7 8 9 10 GOOD
Please Note: 1 is Bad, 10 is Good :-)

Part and Inventory Search

Back
Top