0

I am filling a DataGridView by running a SQL query with BackgroundWorker. My code works perfectly if I run it directly in a button event handler, but when I put in a DoWork BackgroundWorker it doesn't update the UI. My code is below.

DataTable tab1table = new DataTable();

public Form1()
{
    InitializeComponent();
    Instantiatesearch1Thread();
}

private void Instantiatesearch1Thread()
{
    search1Thread.WorkerReportsProgress = true;
    search1Thread.WorkerSupportsCancellation = true;
    search1Thread.ProgressChanged += search1Thread_ProgressChanged;
    search1Thread.DoWork += search1Thread_Dowrk;
    search1Thread.RunWorkerCompleted += search1Thread_RunWorkerCompleted;
}

    private void sbutton1_Click(object sender, EventArgs e)
{
    search1Thread.RunWorkerAsync();
}
void search1Thread_Dowrk(object sender, DoWorkEventArgs e)
{
    int percentprogress = 0;
    percentprogress++;
    Thread.Sleep(1000);
    // Search1 button event handler
    using (SqlConnection conn = new SqlConnection(connectionstring))
    {

        conn.Open();
        using (SqlDataAdapter cmd = new SqlDataAdapter(comboBox1SQL, conn))
        {
            if (comboBox1.Text.Contains("ID"))
            {
                long para = long.Parse(search1.Text);
                cmd.SelectCommand.Parameters.Add(new SqlParameter
                {
                    ParameterName = "@combo1Par",
                    Value = para,
                    SqlDbType = SqlDbType.BigInt
                });
            }

            else if (comboBox1.Text.Contains("Other Thing") || comboBox1.Text.Contains("Other Stuff"))
            {
                string para = search1.Text;
                cmd.SelectCommand.Parameters.Add(new SqlParameter
                {
                    ParameterName = "@combo1Par",
                    Value = "%" + para + "%",
                    SqlDbType = SqlDbType.NVarChar,
                });
            }
            // Clear datatable if it contains any information and then fill it
            // tab1datatable is a DataGridView
            if (tab1table != null)
                tab1table.Clear();
            cmd.Fill(tab1table);
            //tab1datatable.DataSource = tab1table;

        // A bunch of expensive calculations 
        }
    }
}

void search1Thread_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    search1Progress.Value = e.ProgressPercentage;
}

void search1Thread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    tab1datatable.DataSource = tab1table;
    tab1datatable.Refresh();
    MessageBox.Show("All Done!");
}

By using a MessageBox I discovered that the code is not accessing the if comboBox1.Text.Contains() statements, which I think makes sense, because that information comes from the UI and BackgroundWorker can't directly access the UI. This would also explain why then tab1datatable and tab1table go untouched.

I think I need to use the Invoke method somewhere, but I am not sure how. I took a look at c# - Pass information to BackgroundWorker From UI during execution but it doesn't really answer my question. If Invoke is what I need, how do I implement in this code to allow it to grab information from the UI and subsequently update it with the filled DataGridView?

DrakeMurdoch
  • 765
  • 11
  • 26
  • Why did you commend out datasource? You need to set datasource to null and then back to table to get the paint method to update control : cmd.Fill(tab1table); tab1datatable.DataSource = null; tab1datatable.DataSource = tab1table; – jdweng Mar 25 '19 at 16:08
  • Because I updated it in the `search1Thread_RunWorkerCompleted` function, instead. – DrakeMurdoch Mar 25 '19 at 16:09
  • Update `DataGridView` in UI thread. `Application.Current.Dispatcher.Invoke(() => // Insert data into grid));` – fhnaseer Mar 25 '19 at 16:21
  • @fhnaseer it won't even access the if statements in the background thread, so I can't get to the data to fill the `DataGridView`. Would I put that into `search1Thread_Dowrk`? – DrakeMurdoch Mar 25 '19 at 16:30
  • Then set to null in search1Thread_RunWorkerCompleted – jdweng Mar 25 '19 at 16:35
  • Do this. `ComboBox.CheckForIllegalCrossThreadCalls = false;` And hopefully you will be able to access it from the `BackgroundThread`. – fhnaseer Mar 25 '19 at 16:41
  • @fhnaseer - Nooooooo. – Enigmativity Mar 27 '19 at 10:57

2 Answers2

1

Here's what you need to do:

    private string search1_Text = "";
    private string comboBox1_Text = "";
    private void Instantiatesearch1Thread()
    {
        search1_Text = search1.Text;
        comboBox1_Text = comboBox1.Text;
        search1Thread.WorkerReportsProgress = true;
        search1Thread.WorkerSupportsCancellation = true;
        search1Thread.ProgressChanged += search1Thread_ProgressChanged;
        search1Thread.DoWork += search1Thread_Dowrk;
        search1Thread.RunWorkerCompleted += search1Thread_RunWorkerCompleted;
    }

You basically take a copy of the data that you need before you run the background worker.

Then you just access the fields in your DoWork code:

    void search1Thread_Dowrk(object sender, DoWorkEventArgs e)
    {
        int percentprogress = 0;
        percentprogress++;
        Thread.Sleep(1000);
        // Search1 button event handler
        using (SqlConnection conn = new SqlConnection(connectionstring))
        {
            conn.Open();
            using (SqlDataAdapter cmd = new SqlDataAdapter(comboBox1SQL, conn))
            {
                if (comboBox1_Text.Contains("ID"))
                {
                    long para = long.Parse(search1_Text);
                    cmd.SelectCommand.Parameters.Add(new SqlParameter
                    {
                        ParameterName = "@combo1Par",
                        Value = para,
                        SqlDbType = SqlDbType.BigInt
                    });
                }

                else if (comboBox1_Text.Contains("Other Thing") || comboBox1_Text.Contains("Other Stuff"))
                {
                    string para = search1_Text;
                    cmd.SelectCommand.Parameters.Add(new SqlParameter
                    {
                        ParameterName = "@combo1Par",
                        Value = "%" + para + "%",
                        SqlDbType = SqlDbType.NVarChar,
                    });
                }
                // Clear datatable if it contains any information and then fill it
                // tab1datatable is a DataGridView
                if (tab1table != null)
                    tab1table.Clear();
                cmd.Fill(tab1table);
                //tab1datatable.DataSource = tab1table;

                // A bunch of expensive calculations 
            }
        }
    }
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
-1

I am not sure if this is a good solution or not but this works. Set CheckForIllegalCrossThreadCalls to false when you initialize the BackgroundWorker.

private void Instantiatesearch1Thread()
{
    // Initialize other stuff
    CheckForIllegalCrossThreadCalls = false;
}

And set it to false once task is completed.

private void search1Thread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // Do your work,
    CheckForIllegalCrossThreadCalls = true;
}
fhnaseer
  • 7,159
  • 16
  • 60
  • 112
  • No idea. I am not a winform person. I do WPF with MVVM. And in MVVM background thread can easily access UI properties. Have a look at this question. There are some details. https://stackoverflow.com/questions/142003/cross-thread-operation-not-valid-control-accessed-from-a-thread-other-than-the?rq=1 – fhnaseer Mar 25 '19 at 16:56
  • @DrakeMurdoch **Definitely avoid this**. You just need to set the DataSource to null, then reset it with the new DataTable. Or use a BindingSource, which will do this for you. **In the `RunWorkerCompleted` handler.** – Jimi Mar 25 '19 at 16:57
  • @Jimi if I do `tab1datatable.DataSource = null; tab1datatable.DataSource = tab1table; tab1datatable.Update();` in `search1Thread_RunWorkerCompleted` I get an empty table. – DrakeMurdoch Mar 25 '19 at 16:59
  • Find out what happens to your DataTable in the meanwhile, then. Your code never calls `ReportProgress`, thus you have somthing that you're not showing here. Do not try to hack the background Thread, you'll have more problems that you bargained for. – Jimi Mar 25 '19 at 17:12