views:

80

answers:

4

Hi there,

I have built a UserControl for display a list of other UserControls which themselves are bound to individual data objects. The link below shows an example implementation of this control.

alt text

Each of the individual user rows is its own UserControl/XtraUserControl, laid out in a FlowLayoutPanel.

The problem I have is with perfomance, to populate the list above takes around 500ms (excluding any data loading) - this is a combination of creating each control and then adding them to the FlowLayoutPanel using the AddRange(controls[]) method.

Does anyone know any way I can improve perfomance here? Do I have to manually paint the items instead of using User Controls?

Thanks in advance.

EDIT: I've added my own response below showing the solution I have stuck with for now.

+1  A: 

Whether manually painting would help is a guess. Even if it were right (which I doubt) it's better not to guess.

I've seen this kind of issue before, and chances are there's a lot of stuff that goes on in the binding.

The way I've solved the problem is with this approach, but it's definitely "out there" in terms of programmer acceptance.

Mike Dunlavey
I'm glad you mentioned Differential Execution as I hadn't previously come across it, I will certainly be looking into this in the near future. Thank you.
Tony Day
+1  A: 

I gues you are using devexpress controls because you mention XtraUserControl. If so, why don't use an XtraGrid?You can add images column and button columns, and I think you'll get better performance and simpler/less code

Andrea Parodi
The main reason I went with this type of control is that it gave me a great flexibility with building these flowing panels - with the use of generics to add simplicity and re-usability. If I could find a way to improve the performance it would be a very non-restrictive control for all of my needs. I really like DevExpress controls so I will certainly look at the XtraGrid also. Thank you for your help. :)
Tony Day
+1  A: 

First of all, try use pair SuspendLayout()/ResumeLayout(), then it has sense to stop painting at all by hiding the container control until all child usercontrols added.

Anyway, placing lots of child controls to a container is not a good idea. You can have the same result by using highly customized grid or by custom painting (which is preferable).

Good luck!

Dmitry Karpezo
Unfortunately I had been using SuspendLayout()/ResumeLayout() and the performance is still grim (though not awful for small "shots" of data). I will in fact look at some existing grids as well or consider painting, thank you for your help.
Tony Day
A: 

I had a brainwave for another solution which I'm not quite sure is appropriate. I would really appreciate any feedback on this.

Two rationales led to this solution:

  • Firstly I wanted the flexibility of creating rows like any other control.
  • Secondly the lists that would use this approach only intend to display brief chunks of data, never more than say 20 items - for anything larger, ListViews are used.

So anyway, what I decided to do was cache a set number of the Panels (I've referred to the custom controls or rows as Panels throughout the code) and to build up this cache as the control is created. When populating the control with BusinessObjects, the existing cached Panels are displayed with their bound BusinessObject. You can see how this works exactly from the code below, so there is no need for a in-depth description.

The fact of the matter is that I've managed to reduce the data population time (after the initial cache setup of around 180ms for 10 Panels) from 500ms to 150ms for the list shown in the image above.

private int cacheSize = 10;
private List<P> cachedPanels = new List<P>(10);
private void InitItems()
{
    this.contentPanel.SuspendLayout();

    // Create the cached panels from the default cache value.
    for (int i = 0; i < cacheSize; i++)
        cachedPanels.Add(new P() { Margin = new Padding(0), Visible = false });

    this.contentPanel.Controls.AddRange(cachedPanels.ToArray());

    this.contentPanel.ResumeLayout(true);
}

private void PopulateListFromCache()
{
    this.contentPanel.SuspendLayout();

    // Iterate against both BusinessObjects and Panels to ensure that nothing is missed, for
    // instance, where there are too many panels, the rest are hidden, and too many Business
    // Objects, then more Panels are created.
    for (int i = 0; i < this.businessObjects.Count || i < this.cachedPanels.Count; i++)
    {
        if (i >= this.cachedPanels.Count)
        {
            // Here, we have more BusinessObjects than Panels, thus we must create
            // and assign a new panel.
            this.cachedPanels.Add(new P() { Margin = new Padding(0) });
            this.cachedPanels[i].Item = this.businessObjects[i];
            this.contentPanel.Controls.Add(this.cachedPanels[i]);
        }
        else if (i >= this.businessObjects.Count)
        {
            // Here, we still have Panels cached but have run out of BusinessObjects,
            // let's just hide them and clear their bindings.
            this.cachedPanels[i].Item = default(T);
            this.cachedPanels[i].Visible = false;
        }
        else
        {
            // Here, we have both BusinessObjects and Panels to put them in, so just
            // update the binding and ensure the Panel is visible.
            this.cachedPanels[i].Item = this.businessObjects[i];
            this.cachedPanels[i].Visible = true;
        }
    }

    this.contentPanel.ResumeLayout(true);
}

Obviously, more optimizations can be made, such as un-caching Panels after a certain amount of time of not being used etc. Also, I'm not entirely sure if keeping these controls - which are rather simple - in a cache will affect memory usage much.

If anyone can think of any other pointers then please, be my guest. Oh, and if you got this far, then thank you for reading this.

Tony Day