huMpty duMpty
huMpty duMpty

Reputation: 14470

Keeping responsive UI

I have a winform which have several combo boxes and a gridview.

Initially I am creating the gridview with Rows and Columns and no data filled in with.

enter image description here

The filling data to grid is a long running task which will loop through all rows and read column header and depending on that it will apply different color and data to each cell.

What I am trying to achieve is to load the grid as above in form load event, and after the form loaded start filling data to grid so user can see what happening. Same things apply to the combo box value change since I will load data according to the combo value.

What I have tried is something like this...

In form load I am calling method

 private void LoadForm()
        {                        
            DataBind(); // this will load the initial grid without cell data            

            this.BeginInvoke((MethodInvoker)this.LongRunningProcess1);

            this.BeginInvoke((MethodInvoker)this.LongRunningProcess2);

         }

But still it taking long time and I don't have the responsive UI.

I also tried something like this but no luck....

            ThreadStart ts = LongRunningProcess1;
            Thread t1 = new Thread(ts);
            t1.Start();

Also using a background worker to complete the long running operation causes "Cross thread operation" issue.

 private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
                LongRunningProcess1();
                LongRunningProcess2();           
        }

Any help to make this working is appreciate..

Thanks

UPDATE

I found a really cool solution Updating Your Form from Another Thread without Creating Delegates for Every Type of Update

Thanks for the answers!!!

Upvotes: 0

Views: 2540

Answers (4)

Nilzor
Nilzor

Reputation: 18603

To avoid the CrossThreadException in the completed-event of the background worker, wrap your callback method like this:

public void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new RunWorkerCompletedEventHandler(WorkerCompleted), new {sender, e});
    }
    else 
    {
        // Business logic goes here
    }
}

Typically you will populate the data loaded into the GridView in the else-block. If you on the other hand want to populate the GridView progressively from the long-running background task, you can achieve this with a similar technique using callbacks from the background worker:

public void Worker_DoWork(object sender, DoWorkEventArgs e)
{
   foreach (var someParameter in parameterList) // Long-running loop 
   {
     var data = LoadData(someParameter); // Load data for row X
     this.Invoke(new Action<object>(UpdateRow),new[]{data});  // Update on UI-thread
   }
}


public void UpdateRow(object data)
{
   // Code to populate DataGrid row X with data from argument
}

Note that you can call BeginInvoke instead of Invoke if you want to do the UI-updating asynchronously. This will usually not make a difference in this case.

Upvotes: 1

Phil
Phil

Reputation: 2365

Simple example using BackgroundWorker and RunWorkerAsync. Hope this helps.

public partial class Form2 : Form
{

    BackgroundWorker worker = new BackgroundWorker();

    public Form2()
    {

        worker.WorkerReportsProgress = true;
        worker.WorkerSupportsCancellation = true;

        worker.DoWork += new DoWorkEventHandler(worker_DoWork);
        worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
        worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);

        InitializeComponent();
    }

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        int totalSteps = 5;
        for (int i = 1; i <= totalSteps; i++)
        {
            Thread.Sleep(1000);
            worker.ReportProgress(i);
        }
    }

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

    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        resultText.Text = "Worker complete";
        btnDoWork.Enabled = true;
        progressBar1.Visible = false;
    }

    private void btnDoWork_Click(object sender, EventArgs e)
    {
        progressBar1.Visible = true;
        worker.RunWorkerAsync();
    }

}

Upvotes: 0

Zyn
Zyn

Reputation: 1

If you want to use the backgroundworker/multithreading, you can use delegates updating your form(they run on ui thread). See example here: How to update the GUI from another thread in C#?

Upvotes: 0

Bob Vale
Bob Vale

Reputation: 18474

The backgroud worker process is the correct way to go, you just need to eliminate all the "Cross thread operation" exceptions by making sure that all the calls that modify the form elements use the Control.Invoke method wrapper.

Upvotes: 0

Related Questions