Fake-scrolling containers with very many controls

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

Leave a Comment