Brad
Brad

Reputation: 2195

Wait message while populating a datagrid control

I have a C# WinForms application with a tab control and several tabs. One of the tabs contains a data grid control - it only has about 10 elements in it but the data is populated by querying multiple servers and thus is slow to load.

When I run my application and select the tab with the datagrid control, the application appears to hang, while its trying to query all the servers and populate the grid.

Instead of hanging I'd like the application to be responsive and for it to display a "please wait..." message which will disappear after the datagrid is populated.

What I've tried to do is create a background worker as such:

if (tabctrl.SelectedTab == tabctrl.TabPages["tabServices"])
{

    this.dgrdServices.RowPrePaint += new DataGridViewRowPrePaintEventHandler(dgrdServices_RowPrePaint);
    this.dgrdServices.CellContentClick += new DataGridViewCellEventHandler(dgrdServices_CellClick);

    BackgroundWorker bw = new BackgroundWorker();
    lblLoading.Visible = true;
    bw.RunWorkerAsync();
    bw.DoWork += new DoWorkEventHandler(bw_DoWork);
    bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);


}

private void bw_DoWork(object sender, DoWorkEventArgs e)
{

    PopulateServicesDataGrid();
}

private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    lblLoading.Visible = false;
}

private void PopulateServicesDataGrid()
{
    int x = 0;

    foreach (Service Service in Globals.Services)
    {
        // Add a row to the datagrid for each service
        this.dgrdServices.Rows.Add();

        // Update the current service status
        Service.Status = Service.Query(Service.Server, Service.Name);
        if (Service.Status == "running")
        {
            this.dgrdServices.Rows[x].Cells[0].Value = Properties.Resources.green_dot;
            this.dgrdServices.Rows[x].Cells[4].Value = Properties.Resources.stop_enabled;
        }
        else
        {
            this.dgrdServices.Rows[x].Cells[0].Value = Properties.Resources.grey_dot;
            this.dgrdServices.Rows[x].Cells[4].Value = Properties.Resources.start_enabled;
        }

        this.dgrdServices.Rows[x].Cells[1].Value = Service.Server.ToUpper();
        this.dgrdServices.Rows[x].Cells[2].Value = Service.FreindlyName;
        this.dgrdServices.Rows[x].Cells[3].Value = Service.Status;
        this.dgrdServices.Rows[x].Cells[5].Value = "Uninstall";
        this.dgrdServices.Rows[x].Cells[6].Value = Service.Name;
        x++;
    }
}

PopulateServicesDataGrid() contains code which iterates through some objects and queries several different servers for service status.

When I try and run the above though the grid doesn't get populated. If I don't use a background worker and just call PopulateServicesDataGrid directly it does work (albeit the app hangs).

Why isn't the background worker/datagrid populate working?

Upvotes: 0

Views: 928

Answers (2)

davidallyoung
davidallyoung

Reputation: 1332

In your PopulateServicesDataGrid I imagine you're interacting with a UI control, which doesn't work out because the background worker is operating on a different thread than your UI context. You'll need to work out a mechanism to do the work in a way that returns the information you want to put in the grid and then back in your UI thread context (RunWorkerCompleted), populate the grid with the information you come up with in DoWork.

Anytime you're using a background worker, you'll need to split out your interactions with the UI controls, and after the backgroundworker completes resume interaction with your UI.

You're also hooking up the events after calling RunWorkerAsync, hook up your events first then call RunWorkerAsync.

Edit to reflect comment with an example:

Rough example of how you could do this, based on the code I see.

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
     QueryServices()
}

private void QueryServices()
{
   foreach (Service Service in Globals.Services)
   {
       Service.Status = Service.Query(Service.Server, Service.Name);
   }
}

    private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        PopulateServicesDataGrid();
        lblLoading.Visible = false;
    }

    private void PopulateServicesDataGrid()
    {
        //Do everything else you are doing originally in this method minus the Service.Query calls.
    }

Upvotes: 1

reptile
reptile

Reputation: 185

Method bw_DoWork running in another thread from ThreadPool. Accessing WinForms object from other threads requires synchronization. The best way to do this - use AsyncOperationManager. You should create AsyncOperation in GUI thread and use it inside PopulateServicesDataGrid to send or post results.

Another way - update DataGrid by prepared data inside bw_RunWorkerComplete - it's already synchronized by BackgroundWorker component.

More modern way to do the same - use async tasks, but it requires base level of TPL knowledge.

Upvotes: 1

Related Questions