Adding Controls during Scroll Event (Improved)

Yesterday I posted a solution for dynamically adding controls onto a form when the canvas is constantly changing the coordinates (i.e. during the scroll event). Well, to put it bluntly, that solution sucks. It works perfect as long as the thread is busy and you can’t scroll while its adding the controls, but the minute you move it to a background worker so you can offer continuous scrolling it works only intermittently. The problem is actually easy to see, and I should have seen it…the canvas changing is not in sync with where you think the next image should be placed.

I have to admit that I didn’t test my solution out completely before posting it yesterday. I even added a comment (that I later removed, once I implemented it in my project) at the end of the article that basically said if you need this during the scroll event then put it in a background event so the user would have smoother scrolling and the UI wouldn’t lock up while the controls where being created and added. So, today I have a better solution (one that I like better anyway), and I have tested it more. Yesterday’s solution would work if you were using a button instead of the scroll event, but today’s solution is a little more elegant…I think anyway.

Today we will use the same example as yesterday, but with the better solution.

Step one – create a windows form with a panel in it.

Because I went over the details (and example of) the problem yesterday, I am just going to post my final code and then I’ll explain what we’re doing. I’ll even include the background worker in this example and remove the limitation on the scroll event, so you will need to add a background worker to your form also.

You will need the following using statements:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Threading;

And the following variables in your global scope:

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

//we need to keep track of the number of the control.
int number = 0;

//the number of controls we can place on one line to avoid
// horizontal scrollbars.
int rowLength = 0;

//since we are using a background worker we need a list
// that can be accessed by both threads.
List<Label> labelList = new List<Label>();

//since our list is shared between threads, and we are using
// the scroll event, we need to know who is using it when
// so we don’t get any exceptions if they both try to use it
bool listInUse = false;

And inside your load event:

//Get the number of controls we can place per row. 
//60 is the width of our control + a 5px padding.
rowLength = (int)Math.Floor((double)Panel1.Width / 60);
AddControls();

And the AddControls function:

if (!backgroundWorker1.IsBusy)
{
    backgroundWorker1.RunWorkerAsync();
}

And inside the panel’s scroll event:

//only add new controls if the vertical scrollbar scrolls
if (e.ScrollOrientation == ScrollOrientation.VerticalScroll)
{
    //gives a little bit of a cushion…
    //not perfect but works
    if ((e.NewValue - e.OldValue) >= 15)
    {
        AddControls();
    }
}

And the background worker DoWork event:

//if our shared list is in use, wait until it is not anymore
while (listInUse == true)
{
    //wait 500ms and check again
    Thread.Sleep(500);
}

//set listInUse to true so the main thread doesn’t try to use it
listInUse = true;

//clear the list
labelList.Clear();

//add 500 labels to our list
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;

    //add the label to the list
    labelList.Add(lbl);
}

//free up the list for our main thread
listInUse = false;

Inside the background worker RunWorkerCompleted function:

//if our shared list is in use, wait until it is not anymore
while (listInUse == true)
{
    //wait 500ms and check again
    Thread.Sleep(500);
}

//set listInUse to true so the other thread doesn’t try to use it
listInUse = true;

foreach (Label lbl in labelList)
{
    //create a point at 0,0
    Point location = new Point(0, 0);

    //we need to know if we are adding the first one or not
    if (this.number > 0)
    {
        //get the last control on the form
        Label lastLbl = (Label)Panel1.Controls["lbl" + (number-1).ToString()];

        //set the location so that it is where our last control is
        location = lastLbl.Location;

        //are we about to use the last column in this row?
        if (counter == (this.rowLength - 1))
        {
            //set the label to the left side of the panel
            location.X = 0;

            //move down one row
            location.Y += lastLbl.Size.Height;

            //reset our column counter
            counter = 0;
        }
        else
        {
            //same row, to the right of the last label
            location.X += lastLbl.Size.Width;
            counter++;
        }
    }

    //we need to name our label so we can find it later
    //we could just use the default name
    //but you can use anything, as long as it includes the number
    lbl.Name = “lbl” + number.ToString();

    //set the location, add to the panel and then paint
    lbl.Location = location;
    Panel1.Controls.Add(lbl);
    Application.DoEvents();
    number++;
}

//free up the list
listInUse = false;

Now, once we scrolled a few times we get the expected results:

Advertisements

One thought on “Adding Controls during Scroll Event (Improved)

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