Amrit Sharma
Amrit Sharma

Reputation: 1916

Background Worker to populate combobox in windows form application

I want to implement background worker in my windows application. Currently I am using button event handler to load the combo box with data. As the query hangs the user interface, i would like to implement background worker as that the query runs in different thread. I have never used this background worker in any of my application. I did some research on this and still unable to implement this. Any help or advice will be greatly appreciated.

This is how my button event handler looks like

private void button6_Click(object sender, EventArgs e)
        {
            if (comboBox1.SelectedItem.ToString() == "All")
            {

               findAllUser();

            }

            else
            {
                //Do Something!!!

            }
        }     

findAllUser() will fetch all the user from active directory which normally takes time and makes the UI unresponsive. Code for findAllUser() looks like this.

public void findAllUser()
    {
        System.DirectoryServices.DirectoryEntry entry = new System.DirectoryServices.DirectoryEntry("LDAP://DC=xyz, DC=com");
        System.DirectoryServices.DirectorySearcher mySearcher = new System.DirectoryServices.DirectorySearcher(entry);
        mySearcher.Filter = "(&(objectClass=user))";

        foreach (System.DirectoryServices.SearchResult resEnt in mySearcher.FindAll())
        {
            try
            {
                System.DirectoryServices.DirectoryEntry de = resEnt.GetDirectoryEntry();
                comboBox2.Items.Add(de.Properties["GivenName"].Value.ToString() + " " + de.Properties["sn"].Value.ToString() + " " + "[" + de.Properties["sAMAccountName"].Value.ToString() + "]");
            }

            catch (Exception)
            {
                // MessageBox.Show(e.ToString());
            }
        }

    }

Below is how the background worker looks now..all empty

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {


        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {


        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

        }

Any advice how can i implement the code above so that the background worker will populate the combobox2 with the active directory user list.

enter image description here

Upvotes: 0

Views: 3465

Answers (4)

Jim Mischel
Jim Mischel

Reputation: 134025

The easiest way would be to have your findAllUser method build a list of items that need to be added to the combo box, and then have the RunWorkerCompleted method populate the combo box. For example, modify your findAllUser like this:

private List<string> items;
public void findAllUser()
{
    items = new List<string>();

    System.DirectoryServices.DirectoryEntry entry =
        new System.DirectoryServices.DirectoryEntry("LDAP://DC=xyz, DC=com");
    System.DirectoryServices.DirectorySearcher mySearcher =
        new System.DirectoryServices.DirectorySearcher(entry);
    mySearcher.Filter = "(&(objectClass=user))";

    foreach (System.DirectoryServices.SearchResult resEnt in mySearcher.FindAll())
    {
        try
        {
            System.DirectoryServices.DirectoryEntry de = resEnt.GetDirectoryEntry();
            items.Add(de.Properties["GivenName"].Value.ToString() + " " +
                de.Properties["sn"].Value.ToString() + " " + "[" +
                de.Properties["sAMAccountName"].Value.ToString() + "]");
        }

        catch (Exception)
        {
            // MessageBox.Show(e.ToString());
        }
    }
}

Then, have your DoWork call findAllUser to do the actual work.

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    findAllUser();
}

And finally, have your RunWorkerCompleted populate the combo box:

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    foreach (var item in items)
    {
        comboBox2.Items.Add(item);
    }
}

If you want to show progress, then you need to call ReportProgress from time to time while the worker is doing its business. Since you don't know exactly how long the process will take or exactly how many users it will find, you can't really report accurate progress. In that case, you have to guess. Since you say it takes "like 30 seconds," then you can use that as the 100% mark. So you start a StopWatch when the worker starts its processing, and update every half second or so. Modify your findAllUser function like this:

public void findAllUser()
{
    const int ExpectedTime = 30000; // 30,000 milliseconds
    // stopwatch keeps track of elapsed time
    Stopwatch sw = Stopwatch.StartNew();
    // Create a timer that reports progress at 500 ms intervals
    System.Timers.Timer UpdateTimer;
    UpdateTimer = new System.Threading.Timer(
        null,
        {
            var percentComplete = (100 * sw.ElapsedMilliseconds) / ExpectedTime;
            if (percentComplete > 100) percentComplete = 100;

            ReportProgress(percentComplete);

            // Update again in 500 ms if not already at max
            if (percentComplete < 100)
                UpdateTimer.Change(500, Timeout.Infinite);
        }, 500, Timeout.Infinite);
    items = new List<string>();

    // rest of findAllUser here

    // dispose of the timer.
    UpdateTimer.Dispose();
}

And then, in your ProgressChanged event handler, update your progress bar.

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // Update the progress bar with the value from e.ProgressPercentage

    }

Again, because you don't know exactly how long things are going to take, you make an estimate. Above, I estimated 30 seconds, and the code blindly assumes that if 15 seconds have gone by, then it's half done.

Note that I create the timer as a one-shot and re-initialize it after every tick. I do this because I want to prevent concurrent updates. The timer fires on a separate thread, and the ReportProgress method marshals the call to the ProgressChanged event to the UI thread. If the UI thread is busy with other things, then another timer event could come in and you could end up with a bunch of threads all trying to marshal calls to the UI. Probably not a problem in this case because we're only talking a maximum of 60 calls (two calls per second for 30 seconds), but in general it's a good idea to prevent that kind of thing from happening.

Upvotes: 3

Tu Le Hong
Tu Le Hong

Reputation: 95

Using BackgroundWorker is convenient because it automagically invokes the ProgressChanged and RunworkerCompleted event handlers in the UI thread. You can use it like below.

    private void AddItem(DirectoryEntry de)
    {
        comboBox2.Items.Add(de.Properties["GivenName"].Value.ToString() + " " + de.Properties["sn"].Value.ToString() + " " + "[" + de.Properties["sAMAccountName"].Value.ToString() + "]");
    }

    private void button6_Click(object sender, EventArgs e)
    {
        if (comboBox1.SelectedItem.ToString() == "All")
        {

            this.backgroundWorker1.RunWorkerAsync();

        }

        else
        {
            //Do Something!!!

        }
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        // Bind to the users container.
        DirectoryEntry entry = new DirectoryEntry("LDAP://CN=xyz,DC=com");
        // Create a DirectorySearcher object.
        DirectorySearcher mySearcher = new DirectorySearcher(entry);

        try
        {
            // Create a SearchResultCollection object to hold a collection of SearchResults
            // returned by the FindAll method.
            SearchResultCollection result = mySearcher.FindAll();
            int count = result.Count;

            for(int i = 0; i < count; i++)
            {
                SearchResult resEnt = result[i];

                try
                {
                    DirectoryEntry de = resEnt.GetDirectoryEntry();

                    BeginInvoke(new Action<DirectoryEntry>(AddItem), de);
                }
                catch (Exception)
                {
                    // MessageBox.Show(e.ToString());
                }

                this.backgroundWorker1.ReportProgress(i / count);
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        this.progressBar1.Value = e.ProgressPercentage;
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        this.progressBar1.Value = 100;
    }

Upvotes: 1

jadavparesh06
jadavparesh06

Reputation: 946

You can use below logic to implement background worker for your code.

var startListenerWorker = new BackgroundWorker();
                startListenerWorker.DoWork += new DoWorkEventHandler(this.StartListenerDoWork);
                startListenerWorker.RunWorkerAsync();


private void StartListenerDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
        {

// Your logic to load comboBox will go here for running your query
}

you can also implement thread to have your logic run on a separate thread.

Upvotes: 0

DevEstacion
DevEstacion

Reputation: 1977

Place your code on the backgroundWorker1_DoWork. But I suggest you either use Thread or Task Parallel Library

If you're on .NET 4.0, use the TPL.

You can do it like this:

    Task runner = new Task(() =>
    {
        // do process here
    });
    runner.Start();

of if you're on older frameworks, use the Thread like this.

    Thread thread = new Thread(() =>
    {
        // do process here
    });
    thread.IsBackground = true;
    thread.Start();

read more about the TPL and Thread.

Upvotes: 1

Related Questions