Adding Controls during Scroll Event

So I have a need to add controls (Picture Boxes) to a panel during runtime. Easy enough, create a picture box, set the properties and add to the panel. Getting started was really easy, but when I wanted to add more controls as the user scrolled the panel, things began going bad fast.

First, create a form with a panel inside it. Be sure to set the Autoscroll property to true on the panel.

Then add 500 controls to your panel. For this example we will use a label with the text “1,2,3,4,5”.
    //Get the number of controls we can place per row.   
    //60 is the width of our control + a 5px padding.
    int rowLength = (int)Math.Floor((double)panel1.Width / 60);

    //keep a tally of our location 
    int x = 0;
    int y = 0;

    //counter so we know what column we are in.
    int counter = 0;

    //add 500 labels to our panel, one right after another
    for (int i = 0; i < 500; i++)
    {
        //create the label and set some properties
        Label lbl = new Label();
        lbl.Text = "1,2,3,4,5";

        //size is important with labels
        lbl.Size = new Size(55, 13);

        //border so we can see the bounds of the object
        lbl.BorderStyle = BorderStyle.FixedSingle;

        if (counter == rowLength)
        {
            //we are at the end of the row so set the new y coordinate 
            //(height of the control + padding)

            y += 15; //height + 2px padding
            x = 0;

            //reset x to 0 so we will start back at the left side
            counter = 0; //reset our column counter
        }

        //now that we know out coordinates we can set the location
        lbl.Location = new Point(x, y);

        //add the width of the control + padding to x
        x += 60; //width + 5 px padding

        panel1.Controls.Add(lbl); //add the control to the panel
        counter++; //increment our row counter

This gives us 500 labels as we would expect.

Now it gets really fun. How do we add more of the controls as we are scrolling? Well, my first thought was to keep track of our x, y, and counter variables by moving them outside the scope of our function. We also need to extract our code to its own function so we can call that in our scroll event. For the purposes of this demo we use a flag (cont) so that we only call our AddControls function once on scroll. Otherwise I could never show you what the problem is, and why.

private void panel1_Scroll(object sender, ScrollEventArgs e)
{
     //cont is a bool that is initially set to true so 
     //that we don't get stuck in a loop. 
     if (cont)  
     {
          //only add new controls if the vertical scrollbar scrolls
          if (e.ScrollOrientation == ScrollOrientation.VerticalScroll)
          {
               //gives a little bit of a window...not perfect but works
               if ((e.NewValue - e.OldValue) >= 15)
               {
                    AddControls();
                    cont = false;
               }
          }
     }
}

Clearly you can see the problem. There is some unusual white-space between the last control and the first control of the new call. After stepping through the code several dozen time I realized the problem wasn’t with my code, per se, but with my background as a web app developer. In a Windows Forms app the location is always the visible location. For example, 0, 0 is always the VISIBLE top left corner. This means that as we scroll our location logic becomes wrong.

In order to keep track of our next location, we can use a placeholder. Place a label on your form and set the size to (0, 0) and its location to (0, 0). Name the label placeHolder. Now, in your form’s load event set the visibility to false for the placeHolder.

Now, inside our AddControls function set the location of the placeholder to x and y after each control is added.

//now that we know out coordinates we can set the location
lbl.Location = new Point(x, y);

//add the width of the control + padding to x
//width + 5 px padding
x += 60; 

//add the control to the panel
Panel1.Controls.Add(lbl); 

//increment our row counter
counter++; 

this.placeHolder.Location = new Point(x, y);

Now, at the top of our AddControls function we set x and y to the x and y of the placeholder.

x = this.placeHolder.Location.X; 
y = this.placeHolder.Location.Y;

So, now our AddControls function looks like this:

private void AddControls()         
{
    x = this.placeHolder.Location.X;
    y = this.placeHolder.Location.Y;

    //add 500 labels to our panel, one right after another
    for (int i = 0; i < 500; i++)
    {             
        //create the label and set some properties    
        Label lbl = new Label();  
        lbl.Text = i.ToString();    

        //size is important with labels       
        lbl.Size = new Size(55, 13); 

        //border so we can see the bounds of the object      
        lbl.BorderStyle = BorderStyle.FixedSingle;        

        if (counter == rowLength)
        {
            //we are at the end of the row so set the new y coordinate  
            //(height of the control + padding)
            y += 15; //height + 2px padding      
            x = 0;       

            //reset x to 0 so we will start back at the left side       
            counter = 0; //reset our column counter  
        }

        //now that we know out coordinates we can set the location   
        lbl.Location = new Point(x, y);   

        //add the width of the control + padding to x     
        x += 60; //width + 5 px padding     

        Panel1.Controls.Add(lbl); //add the control to the panel     

        counter++; //increment our row counter     
        this.placeHolder.Location = new Point(x, y);  
    } 
}

I changed the text of our new labels from “1, 2, 3, 4, 5” to the value of i so we can see the fix in action.

Edit (8/21/2011): After I published this and implemented it into my own project, I realized it is a terrible solution, well once you add it to a background worker anyway.  I came up with a better solution that I think is more elegant and I would suggest it over this one for every situation.  It can be found here.

Advertisements

One thought on “Adding Controls during Scroll Event

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s