views:

526

answers:

6

This question is about keeping the GUI responsive during longer-running tasks (a few seconds in most cases).

I extensively use threads and a task pattern to execute expensive task in a background thread. But what about GUI updates that takes some time? For example, filling a large string grid or tree view? A thread does not help here because everything needs to be synchronized with the main thread anyway.

I know the problems of Application.ProcessMessages, but currently it seems like the only solution to put calls to ProcessMessages inside the GUI update methods.

Any better ideas?

+1  A: 

Take a look at ActiveSleep.

gabr
A: 

Have you tried out the AntiFreeze component from Indy?

Crudler
He was referring to problems when filling GUI components, not freezing due to the blocking nature of the Indy TCP/IP framework.
Ritsaert Hornstra
What do you think AntiFreeze does internally?
cjrh
+3  A: 

What's wrong with Application.ProcessMessages in your case? The Application.ProcessMessages method is exactly for such cases. The problem with Application.ProcessMessages is the code like this:

repeat
  Application.ProcessMessages;
until SomethingHappens;

That is bad since it is useless CPU load, and should be replaced by

repeat
  Application.HandleMessage;
until SomethingHappens;

which yields processor time to other threads. Single Application.ProcessMessages calls (not in loop) are OK.

Serg
That's not the only problem. You could also easily get reentrance problems and stuff like that.
Smasher
Reentrace is the problem of application logic. You can't blame a general message processing method for it. Sure some events should be handled differently while you are performing something time-consuming in GUI thread.
Serg
I know that and I'm using `ProcessMessages` by now. I don't know if there's something wrong with it..that's why I asked.
Smasher
Well, the wrong thing is to perform a time-consuming operation in GUI thread. In theory, it is always possible to avoid it. In practice, not always. The Application.ProcessMessages is for such cases.
Serg
@Serg: The re-entrance problems come from the fact that every time you call these methods you provide an opportunity for the application to respond to the USER in a way that it is probably not expecting. So unless you then put in additional code to disable sources of user input just in case the user is "click-storming", using these methods simply to keep a UI responsive (which in this case, as is typical, seems to be about "updating in a timely fashion", not actually "responding to the user") is asking for trouble.
Deltics
@Deltics - in practice the best UI for the case discussed is a modal window with a single "Cancel" button and a progress indicator. Anything else is difficult to maintain. If possible, I use such a UI even if the long process is performed in a background thread - that makes programmer's life easier.
Serg
The question mentions nothing about a modal, cancellable operation. It talks about a string grid updating as it is being populated.
Deltics
+5  A: 

When filling Grids, Lists, DataSets etc., call BeginUpdate/EndUpdate DisableControls/EnableControls. This will save you time. I also had a thread that did some calculations, but nevertheless the GUI was slow, until I called DisableControls on the datasets that were being modified and not visible because the controls are on another tabsheet.

Also, when updating controls, have all the data you need prepared, so you can just fill your list, without doing calculations that can slow this down.

The_Fox
I already do that. Still, updating can take a few seconds.
Smasher
@Smasher: If so you are probably using unsuitable controls for large data sets. Using for example the virtual tree control (or similar virtual controls) there shouldn't be delays. On-demand loading of data (for example when tree nodes are expanded) would help too. Also, maybe there's too much data visible at once, which the user can't process all anyway? Filtering might be an option then.
mghie
@mghi: I use TMS string grid component and the default treeView (which I know to be very slow). Anyway, I want to avoid (if possible) switching to other component since it will be very hard to keep the same look and feel (I'm mainly concerned about the string grid here). Also, there are filtering options but I can't force the user to actually use them.
Smasher
@Smasher: Well, using `OnCollapsed` and `OnExpanding` will help for speeding up the standard tree view.
mghie
The standard tree view sucks. Change to VirtualTreeView (see below)
Warren P
Can I use virtual tree view in commercial applications?
Smasher
+1  A: 

If you only want to process paint messages and nothing else: use the following instead of Application.ProcessMessages:

procedure ProcessPaintMessages;
var
  Msg: TMsg;
  i: Integer;
begin
  i := 0;
  repeat
    if Windows.PeekMessage(Msg, 0, 0, 0, PM_REMOVE or (QS_PAINT shl 16)) then begin
      TranslateMessage(Msg);
      DispatchMessage(Msg);
    end else Break;
    Inc(i);
  until i > 1000; // Breakout if we are in a paint only loop!
end;
Ritsaert Hornstra
Interesting. But where does the loop condition come from? It would be pretty expensive if each time I call that function 1000 repaints have to be performed...
Smasher
I use it in a place where I call it only every 200 ms so processing 1000 messages seems not so excessive. You might give the max loop count as a parameter. Note that normally this function processes no or 1 message. The condition is only to avoid a "runaway" by the paint messages.
Ritsaert Hornstra
+4  A: 

IMO if the GUI update is the bottleneck (even if BeginUpdate/EndUpdate is used as @The_Fox suggests) then it is time to reconsider the GUI controls used. The standart grids, treeviews, listboxes aren't simply cut for handling very large number of items. There are many performant thirdparty controls both free and commercial for that purpose.

For a starter, check out VirtualTreeview if the bottleneck is in a grid, treeview or a listbox context.

utku_karatas
Warren P
+1 VirtualTreeView was my first thought!
Remko