1

I trying to fire my datagridview with records from sql server database using a store procedure with two parameters id. I have two comboboxes which takes the parameters and a button. As data is being fetched, I would like to see the progress so I assigned a progressBar to it but I am getting an Error: Invalid thread cross process : The access to the control ComboBox2 performed by a different thread than the thread for which it was created. My c# code:

 private void button2_Click(object sender, EventArgs e)
 {
     ProgressBar1.Visible = true;
     ProgressBar1.Style = ProgressBarStyle.Marquee;
     System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(loadTable));
     thread.Start(); 
 }

 private void loadTable()
 {
     // Load Table...        

     string C = ConfigurationManager..["DB"].ConnectionString;
     SqlConnection con = new SqlConnection(C);
     SqlCommand cmd = new SqlCommand();
     cmd.Connection = con;
     cmd.CommandText = ("[dbo].[spInfo]");
     cmd.CommandType = CommandType.StoredProcedure;
     cmd.Parameters.AddWithValue("@Periode2", comboBox2.SelectedValue.ToString());
     cmd.Parameters.AddWithValue("@Periode1", comboBox3.SelectedValue.ToString());

     try
     {
         // Open connection
         con.Open();

         SqlDataAdapter adapter = new SqlDataAdapter(cmd);

         // Save the results in the DT. Call the adapter statement and fill it in the DT
         DataTable dt = new DataTable();
         adapter.Fill(dt);

         setDataSource(dt);

     }
     catch (Exception ex)
     {
         MessageBox.Show(ex.Message);
     }
     finally
     {
         con.Close();
     }
 }

 internal delegate void SetDataSourceDelegate(DataTable dt);

 private void setDataSource(DataTable dt)
 {
     // Invoke method if required:
     if (this.InvokeRequired)
     {
         this.Invoke(new SetDataSourceDelegate(setDataSource), dt);
     }
     else
     {
         datagridview1.DataSource = dt;
         ProgressBar1.Visible = false;
     }
 }

I would really appreciate any help it thanks.

Konamiman
  • 49,681
  • 17
  • 108
  • 138
James
  • 23
  • 4
  • I did checked on that but it I didn't get it. – James Jun 10 '15 at 07:47
  • You can't modify a UI control from another thread. Anyway, instead of creating a raw thread, use `async/await` to load and return the datatable asynchronously, then modify the UI, eg `var table=await Task.Run(()=>LoadAndReturnTable()); gv.DataSource=dt;......`. The code is a *lot* simpler, as everything before and after the `await` still runs on the UI thread – Panagiotis Kanavos Jun 10 '15 at 07:52

3 Answers3

1

You can't access comboBox2 and comboBox3 in your loadTable() function as it runs in a different thread. (It's the same problem as for datagridView1 and ProgressBar1, which you solved with the delegate at the bottom of your code snippet) If there's just those two elements you want to access, try passing the SelectedValues to the loadTable() function from your _Click() method, instead of reading it in the loadTable() function (it's a quick and dirty solution though):

Use a parameterized thread start in your button_Click function:

System.Threading.Thread thread = new System.Threading.Thread(new ParameterizedThreadStart(this.loadTable));
thread.Start(new Tuple<string, string>(comboBox2.SelectedValue.ToString(), comboBox3.SelectedValue.ToString())); 

I chose Tuple as a quick and dirty object type for your two combobox values, but you could define a struct or something instead.

And then in your loadTable() function:

private void loadTable(object parameters) {
var comboBoxValues = parameters as Tuple<string, string>;
...
cmd.Parameters.AddWithValue("@Periode2",comboBoxValues.Item1);
cmd.Parameters.AddWithValue("@Periode1",comboBoxValues.Item2);
...
}

The key difference is that you access the combobox values before you start a new thread and pass the values of it to the thread instead of accessing the comboboxes inside the thread.

LInsoDeTeh
  • 150
  • 2
  • @LlnsoDeTeh. Can u please edit my code for my understanding please? – James Jun 10 '15 at 07:50
  • @James - No, we cannot edit your code. That would change the question. Stack Overflow is not just here to answer your question - it's here to provide a future reference for other users. If your code is edited the question's value is lost. – Enigmativity Jun 10 '15 at 07:55
  • @LInsoDeTeh - If you are going to edit the OP's code can you please put the edit in your answer. – Enigmativity Jun 10 '15 at 07:56
  • Thanks Enigmativity. That's exactly what I meant. Edit and post as an answer. – James Jun 10 '15 at 07:58
  • I edited my answer. Notice it's a quick and dirty solution though. You could define a struct instead of using the Tuple for the thread function parameter. – LInsoDeTeh Jun 10 '15 at 08:05
  • It's much simpler to use `Task.Run` and `await` to do the same, and doesn't require `Invoke` in the end to update the UI. This works both in .NET 4.5 and .NET 4 with the Async package – Panagiotis Kanavos Jun 10 '15 at 08:08
  • Just wanna thank you all for your contributions. Really appreciate that. – James Jun 10 '15 at 11:53
0

You can't modify a UI control from another thread. If you want to load the datatable in the background, you can use Task.Run and async/await to load the data in a background task, then continue working on the UI thread. You don't need to create a raw thread or use Invoke at all.

Just make sure you pass the combo selections as values to the background task:

private async void button2_Click(object sender, EventArgs e)
{
    ProgressBar1.Visible = true;
    ProgressBar1.Style = ProgressBarStyle.Marquee;
    var period1=comboBox3.SelectedValue.ToString();
    var period2=comboBox2.SelectedValue.ToString();

    //This will run in the background
    var table=await Task.Run(()=>LoadTable(period1,period2));

    //We're back in the UI
    datagridview1.DataSource = table;
    ProgressBar1.Visible = false;    
}

private async Task<DataTalbe> LoadTable(string period1, string period2)
{
     string C = ConfigurationManager.["DB"].ConnectionString;
     using(var con = new SqlConnection(C))
     using(var cmd = new SqlCommand())
     {
         cmd.Connection = con;
         cmd.CommandText = ("[dbo].[spInfo]");
         cmd.CommandType = CommandType.StoredProcedure;
         cmd.Parameters.AddWithValue("@Periode2",period2);
         cmd.Parameters.AddWithValue("@Periode1", period1);

         con.Open();

         SqlDataAdapter adapter = new SqlDataAdapter(cmd);

         DataTable dt = new DataTable();
         adapter.Fill(dt);

         return dt;
    }
}
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • Was very nice of you helping me out.Thnx. – James Jun 10 '15 at 11:52
  • Thnx once again for your example but I am now encountering a different problem with the Thread.I am trying to copy (ctrl+c) some cells after the datagridview loads but I am getting this ERROR: **For the current thread the STA mode ( single-threaded apartment ) must be set before OLE calls can be performed. Make sure that the main function is marked with STAThreadAttribute safe .** – James Jun 12 '15 at 11:15
  • At this level `var table = await Task.Run(() => loadTable(period1, period2));` – James Jun 12 '15 at 11:21
  • Can't help without the code. Anyway, Clipboard operations are supposed to be performed by the UI thread. That's more or less what this message says as well. – Panagiotis Kanavos Jun 12 '15 at 11:25
  • The code is jsut like the example you gave me. And it's working fine just that I can't copy the values or can't even export to excel using a btn. And to be frank I have no much ideas on Threads. – James Jun 12 '15 at 11:38
  • The code I posted doesn't involve the Clipboard. And as I said, you can't access the Clipboard from a background thread. – Panagiotis Kanavos Jun 12 '15 at 11:41
-3

You can try to use

this.Invoke(() => setDataSource(dt););

That should work...

LG

LuxGiammi
  • 631
  • 6
  • 20