user1306322 user1306322 - 2 months ago 34
C# Question

Fake-scrolling containers with very many controls

I'm trying to optimize populating and scrolling a FlowLayoutPanel, but I've had issues with similar controls before, where if they have too many controls inside, it takes a really long while for the container to populate and get ready for use (and the scroller gets shorter and shorter, you might be familiar with that).

I've read that you can use a pool of just the controls within visible boundaries of the container rectangle and simulate scrolling by repopulating them with corresponding contents, as if they would be without this optimization. So you scroll as usual but the population doesn't take nearly as long. But how do I implement that for a general case?

I'm using custom controls to populate the FlowLayoutPanel container, so I'm looking for a generic enough solution that can be applied to both my control and the standard .Net controls.

TaW TaW
Answer

Display and scrolling performance are good reasons to try a virtual paging, although they can be overcome by replacing the Controls.Add with a Controls.AddRange call and a double-buffered container..

..but there is another: Any Winforms control is limited to 32k pixels in its display dimensions. Even if you make it larger nothing will be displayed beyond this limit.

Here is a quick list of things to do, when implementing virtual paging:

  • Use a double-buffered FlowLayoutPanel subclass to simplify the layout and make it flicker-free.
  • Turn off AutoSize and AutoScroll
  • Add a VScrollBar to the right of the FLP and keep its Height the same as the FLP's
  • Calculate the Height (plus Margins) of your UserControl. I assume you add your control wrapped up in a UC, to make things easier.
  • Calculate the paging numbers
  • Create a List<yourUserControlClass> theUCs
  • Now create your UCs but add them only to the list theUCs
  • Code a scrollTo(int ucIndex) function, which clears the FLP's controls and adds the right range from the list.
  • code KeyPreview for the FLP to allow scrolling with the keyboard.

Setting the right values for the VScrollBar's properties, i.e. Minimum, Maximum, Value, SmallChange, LargeChange is a little tricky and setting the page size must be done whenever the FLP is resized or elements are added to or removed from the list.

In my test the setting up and the scrolling results were instantaneous. Only complete UCs are visible from the top, which is fine with me. I have added 1000 UCs with a bitmap in a Panel, a Label and a CheckedListBox.

Here is how I calculate the setting for Maximum:

float pageSize =  flowLayoutPanel2.ClientSize.Height / 
                  (uc1.Height + uc1.Margin.Top + uc1.Margin.Bottom);
vScrollBar1.Maximum = (int)( 1f * theUCs.Count / (pageSize)) + 9;

The extra 9 is a workaround for the quirky offset of a ScrollBar's theoretical and actual Maximum values.

In the ValueChanged event I wrote:

private void vScrollBar1_ValueChanged(object sender, EventArgs e)
{
    int pageSize = flowLayoutPanel1.ClientSize.Height / theUCs.First().Height;
    int v = Math.Min(theUCs.Count, vScrollBar1.Value);

    flowLayoutPanel1.SuspendLayout();
    flowLayoutPanel1.Controls.Clear();
    flowLayoutPanel1.Controls.AddRange(theUCs.Skip( (v- 1) * pageSize)
                                             .Take(pageSize + 1).ToArray());
    flowLayoutPanel1.ResumeLayout();
}

This scrolls to a certain item:

void scrollTo(int item)
{
    int pageSize = flowLayoutPanel1.ClientSize.Height / theUCs.First().Height;
    int p = item / pageSize + 1;
    vScrollBar1.Value = p;
}

For even smoother scrolling use a DoubleBuffered subclass:

class DrawFLP : FlowLayoutPanel
{
    public DrawFLP() { DoubleBuffered = true; }
}

This is probably a bit rough at the edges, but I hope it'll get you on the right track.

enter image description here