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
andAutoScroll
- Add a
VScrollBar
to the right of the FLP and keep itsHeight
the same as the FLP's - Calculate the
Height
(plusMargins
) of yourUserControl
. 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.