views:

1492

answers:

9

We have a GUI of several frames that build their contents dynamically. Each frame creates panels, labels, edits, comboboxes etc to be used as input fields. This is working very well and we are also planning to let each frame build its content in separate threads.

However there is one big problem: it is rather slow! Creating the controls takes no time but setting the Parent property seems to be very time consuming.

I have tried several ways to speed up the process but with no luck. I have tried Enabled=False, Visible=False, DisableAlign, LockWindowUpdate, WM_SETREDRAW... but nothing seem to affect the time consuming process of setting the Parent of the controls.

Even if we use threads this will take time since the VCL functions must be called within Synchronize.

Is there any other way to speed up the creation and showing of controls?

Kind regards, Magnus

Edit: There are no data aware components or any events triggered in the GUI. I am only creating the controls and displaying them. Using timers I have identified the assignment of controls parent (AControl.Parent := AOwner) as the time consuming part.

Edit 2: As shown in the answer below the speed problem is not setting the parent but the painting of the control. When I tested the timing the container was visible and setting the parent caused immediate painting of the control.

Edit 3: Another time consuming part of our dynamic GUI is assigning items to comboboxes. ComboBox.Items.Assign(DataItems) where DataItems have no more than three to six items.

Thank you all for taking the time helping me!

+2  A: 

I dont know if this would work, but have you tried creating your forms as a .dfm text format and then using ObjectTextToBinary function load the .dfm straight to form. This may or may not work, worth investigating.

Toby Allen
Interesting thought, but it seems like a long way to go for such a simple task. The creation is not the problem but the painting/aligning of controls and I guess this would work the same way whether the frame is created from a binary stream or not. But I might try it.
@Magnus: You should indeed try it, judging from the procedure TWinControl.InsertControl() implementation a lot of stuff is simply skipped when csReading is active. This might be your best way to go forward.
mghie
+5  A: 

Don't try to use multiple threads for creating controls, or for working with the VCL in general. This is not going to improve speed anyway, but more importantly is a complete no-no with the VCL.

Edit: You should read the other questions and answers here on StackOverflow that deal with the VCL and multiple threads, but in short: The VCL is not thread-safe, all access to controls has to be done in the context of the main thread. So when using multiple threads you would have to wrap nearly everything in Synchronize() calls, which would actually serialize all threads and slow things down further.

Your best bet is to restructure your UI so that it does not need to be created all at once. Create all frames on-demand, only when they are to be shown for the first time.

Edit 2: Here is some test code to show that setting the Parent property isn't the real problem, but creating all the controls (with all message handling that entails) probably is.

procedure TForm1.Button1Click(Sender: TObject);
var
  i, j, x, y: integer;
  Edit: TEdit;
  Ticks: LongWord;
begin
  Visible := FALSE;
  DestroyHandle;

  try
    for i := 1 to 20 do begin
      y := 20 + i * 25;
      for j := 1 to 10 do begin
        x := (j - 1) * 100;

        Edit := TEdit.Create(Self);
        Edit.SetBounds(x, y, 98, 23);
        Edit.Parent := Self;
      end;
    end;
  finally
    Ticks := timeGetTime;
    Visible := TRUE;
    Caption := IntToStr(timeGetTime - Ticks);
  end;
end;

The code dynamically creates 200 TEdit controls, after freeing the handle of the parent form. Creating all those controls and setting their properties takes a few 10 milliseconds on my system, but finally showing the form (which will create all windows) takes a few 100 milliseconds. Since this can only be done in the main thread I doubt that using multiple threads will help you.

mghie
The purpose of multiple threads would be to let the user continue working in one part of the user interface while the content is loading in other parts (frames). I understand that it may not be faster but at least the user would not have to wait for it to finish.
Thanks, I will leave the threading for now, though it still seems like a good idea to me. :-)
+1  A: 

You could offload the actual retrieval of the data to background thread, but UI stuff must happen in one thread, the Main thread. So the actual setup of your frame will happen in the same thread.

Have you tried a Profiler? It could be that your GUI is too connected, and a update/wire-up causes a lot of unnecessary events/side-effects. Using a profiler you can get more insight in what actually causes the low performance. It could for instance signal that you spent a whole lot of time just waiting for the DB to return or it could be that every set triggers a event which triggers another event.

Davy Landman
+1  A: 

If you use dataware controls, make sure that you call DisableControls at the TDataSet. That can also cause many repaints.

Roland
A: 

Just a wild guess: Maybe it helps to set the controls' parents in reverse hierarchical order, i.e. first set the parent of the most deeply nested controls, then that of their parents and so on. That might cut off some superfluous refreshes because Windows doesn't yet "know" about your controls.

Ulrich Gerhardt
Thanks, I tried but same result.
+1  A: 

What are you setting the DisableAlign on? Try doing a DisableAlign on every control that can hold child controls (e.g. panels). I have seen DisableAlign result in a huge speedup for dynamically-built forms before.

Edit: Thinking about this some more, my answer was partially speculative. I don't know if the effect of setting DisableAlign on the root of a tree of controls will flow on to it's children or not. I assumed that it doesn't, but maybe it does. I'd have to look at the VCL code. (The part about the speedup was true however.)

dangph
Thanks, I was only disabling the frame itself.
A: 

Another wild guess: try to create your container (form, panel or frame) with Visible := false. Then attach all dynamically created controls onto it then set Visible := true

Thanks I have tried this.
A: 

Something that has helped me in the past, along with creation on demand as mghie suggested, was to use the call LockWindowUpdate around the frame creation. This call is global in nature but will reduce the initial flicker as the paint operations will not get invoked until after you relase the lock on the parent handle. The general code template would look something like the following:

LockWindowUpdate(Form1.Handle)
try
  // your frame create/display code goes here
finally
  LockWindowUpdate(0);
end;

When running with the window locked, it will NOT respond to paint messages. Also the function is global, so anything else that calls lockwindowupdate(0) will break your lock. When the lock is released, then the window is immediately invalidated, which saves you from performing that call in addition.

EDIT As pointed out in the comments below, care should be taken when using the lockwindowupdate method, however I have used this method with great success in the past and still recommend it, if used sparingly. What you DON'T want to do with this method is to call application.processmessages inside this lock, or pop up a modal dialog or a showmessage call (or even populate from a dataset). Just restrict the portion within the lock to what your creating. If you play nice, your users won't notice anything odd.

I also used form1.handle as a reference, but in reality I generally only lock handle of the container that the frame is being placed within.

You could try the WM_SETREDRAW method mentioned in the article in the comments, but this requires that the message loop is also processed and generally my calls to lockwindowupdate are extremely tight.

skamradt
Please, please, don't use LockWindowUpdate this way. See here for more: http://blogs.msdn.com/oldnewthing/archive/2007/02/21/1735472.aspx and http://blogs.msdn.com/oldnewthing/archive/2007/02/22/1742084.aspx and http://blogs.msdn.com/oldnewthing/archive/2007/02/23/1747713.aspx
Mihai Limbășan
@moocha - Would be helpful if the links actually worked. Just goes to an error page.
skamradt
They work for me, but blogs.msdn.com has not been having a good few days lately (I intermittently got some errors too.) Until they fix it, you can use the Google cache or Web Archive version (links coming up in a new comment, space issues :D).
Mihai Limbășan
http://google.com/search?q=cache:http://blogs.msdn.com/oldnewthing/archive/2007/02/21/1735472.aspx http://google.com/search?q=cache:http://blogs.msdn.com/oldnewthing/archive/2007/02/22/1742084.aspx http://google.com/search?q=cache:http://blogs.msdn.com/oldnewthing/archive/2007/02/23/1747713.aspx
Mihai Limbășan
@skamradt: The message loop does not enter into it. Just call Perform(WM_SETREDRAW, integer(DoRedraw), 0); and it will work, even in a tight loop. moocha is right, there's no place for LockWindowUpdate() in such code.
mghie
+1  A: 

I found this problem some time ago and improved the creation time significantly by simply hosting the controls within a 'temporary' frame (i.e one that is not assigned to a form). I believe that the slowness is down to each control communicating with the parent form (finally) for lots of calls such as SetBounds, SetVisible etc. By using a floating frame you can get this over and done with and then assign the frame to the form that you require.

Brian Frost