Click here to Skip to main content
15,437,139 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more:
HI All,

Background info: I have an application where I continually send commands to a microcontroller via a serial port and processing the responses from the Serial port. I get and set various control properties on the main form during this process.
When I start this main process I cannot click any buttons on the main form or check/uncheck any checkboxes etc etc. I guess this is because everything runs on a single thread...?
So now I created a seperate thread to run this process in but I still need to update and get info from controls on the main Form.
My question is: Do I need to create a "Delegate" function for each action I perform on a control on the main Form or is there some other way of doing this. There is quite a lot of stuff I do on the main Form so it will involve creating a LOT of delegate functions.

Any help will be appreciated.

Thanks
Newbie Andre :)

Here is some code to illustrate:

C#
protected void start_tests()   //this method runs in its own thread
        {
            string input = string.Empty;
            timer1.Enabled = false;     // this would need delegate?
            if (ResetFlag == true)      //I guess this would need delegate
            {
                reset_selectedindex(); //delegate allready created
                checkedListBox1.SelectedIndex = 0; //this would need delegate
                ResetFlag = false;   //I think this would need delegate
            }
            progressBar1.Maximum = checkedListBox1.CheckedItems.Count;  //this needs delegate
            progressBar1.Value = 0;  //this needs delegate

            while (checkedListBox1_selectedindex() < checkedListBox1_Items_Count())  //delegates used here
            {
                if (checkedListBox1.GetItemCheckState(checkedListBox1.SelectedIndex) == CheckState.Checked)  //delegates needed here
                {
                    UpdateText(checkedListBox1.SelectedItem.ToString() + "\n", UpdateType.TXdata,Color.Lime);  //delegates needed here
                    progressBar1.Value++;   //delegate needed here ....etc etc etc
                    try
                    {
                        show_message_box(checkedListBox1.SelectedIndex,PrePostCommand.precommand);
                        input = ExecCommand("TESTSEQ" + Convert.ToDecimal(checkedListBox1.SelectedIndex).ToString().PadLeft(2, '0') + "\r", 3000, "No responese from OVPT");
                        if (input.Contains("REQUEST"))
                        {
                            if (DialogResult.Yes == show_message_box(checkedListBox1.SelectedIndex,PrePostCommand.postcommand))
                            {
                                UpdateText("TEST:" + Convert.ToDecimal(checkedListBox1.SelectedIndex).ToString().PadLeft(2, '0') + " - " + checkedListBox1.SelectedItem.ToString() + "\r\n", UpdateType.MessageData, Color.Lime);
                                UpdateText("TEST PASS.", UpdateType.MessageData, Color.Lime);
                            }
                            else
                            {
                                UpdateText("TEST:" + Convert.ToDecimal(checkedListBox1.SelectedIndex).ToString().PadLeft(2, '0') + " - " + checkedListBox1.SelectedItem.ToString() + "\r\n", UpdateType.MessageData, Color.Red);
                                UpdateText("TEST FAIL.", UpdateType.MessageData, Color.Lime);
                                TestPass = false;
                            }

                        }
                        if (input.Contains("FAIL"))
                        {
                            UpdateText("TEST:" + Convert.ToDecimal(checkedListBox1.SelectedIndex).ToString().PadLeft(2, '0') + " - " + checkedListBox1.SelectedItem.ToString() + "\r\n", UpdateType.MessageData, Color.Red);
                            UpdateText(input, UpdateType.MessageData, Color.Red);
                            TestPass = false;
                        }
                        if (input.Contains("PASS"))
                        {
                            UpdateText("TEST:" + Convert.ToDecimal(checkedListBox1.SelectedIndex).ToString().PadLeft(2, '0') + " - " + checkedListBox1.SelectedItem.ToString() + "\r\n", UpdateType.MessageData, Color.Lime);
                            UpdateText(input, UpdateType.MessageData, Color.Lime);
                        }
                    }
                    catch (Exception e)
                    {
                        UpdateText(e.Message, UpdateType.MessageData,Color.Red);
                        TestPass = false;
                    }

                }
                if (checkedListBox1.SelectedIndex == (checkedListBox1.Items.Count - 1))
                {
                    break;
                }
                checkedListBox1.SelectedIndex++;

            }
            if (TestPass == false)
            {
                label_Result.Text = "FAILED!!";
                label_Result.BackColor = Color.Red;
                label_Result.ImageIndex = 0;
            }
            else
            {
                label_Result.Text = "PASS!!";
                label_Result.BackColor = Color.Lime;
                label_Result.ImageIndex = 1;
            }

            if (checkBox3.Checked == true)
            {
                Thread.Sleep(100);
                button2_Click_1(this, EventArgs.Empty);
                Thread.Sleep(20);
                //start_tests();
            }
            else
            {
                timer1.Enabled = true;
                oThread.Abort();
            }

        }
Posted
Updated 11-Oct-11 23:47pm
v2

Event-handlers and Control.Invoke Method (Delegate) is a good approach but if you are using .net 4.0 and don't have necessity to use events then use SynchronizationContext it is very easy and simple to use, use following code on your main form to get SynchronizationContext
C#
context = SynchronizationContext.Current;

and use this callback to update components of your main form from your thread
C#
SendOrPostCallback callback = delegate(object obj)
{
//Update all the objects here which need delegate
checkedListBox1.SelectedIndex = 0; //no more need delegate
ResetFlag = false;   //no more need delegate
};
context.Post(callback, null);
 
Share this answer
 
Comments
andre@electrocomp.co.za 13-Oct-11 3:51am     CRLF
Ok Thank you, I can now update controls on my Form ("post") but how can I read properties from controls on my form? i.e how can I check from my current thread if a Checkbox on the main Form is checked? For instance here is some code in my thread: while(checkedListBox1.SelectedIndex < checkedListBox1.Items.Count) { if (checkedListBox1.GetItemCheckState(checkedListBox1.SelectedIndex) == CheckState.Checked) { UpdateText(checkedListBox1.SelectedItem.ToString() + "\n", UpdateType.TXdata,Color.Lime); // progressBar1.Value++; How would I do this using context.post() method or should I be using something else? Thanks for the help! Much appreciated!
Xeshan Ahmed 13-Oct-11 5:26am     CRLF
you can access your controls of form from any child thread, so simply read them childthread { if(checkedListBox1.Items.Count>0) { //do something here } } only use SynchronizationContext when you want to update main form control from its child threads
There is some horrible mixing of UI and business logic going on here. It's a good thing you tripped over this cross threading issue and are discovering that you have all those places where you'd need to Invoke, because it shows up the real problem – you're trying to run the tests and do UI work all in the same place, and that leads to messy code.

"So now I created a seperate thread to run this process in but I still need to update and get info from controls on the main Form."
No you don't. You still need to get information from the data model which is also being displayed on the main form, and you need to be able to signal progress and completion. The thread running the tests shouldn't know anything about the UI.

Broadly speaking what you want is something like
class TestRunner {
 public event EventHandler<IntEventArgs> Completed;
 public event EventHandler<IntEventArgs> Progress;
 public event EventHandler<TestCaseEventArgs> CaseComplete;
 
 public List<decimal> TestCases { get; set; }

 public void RunTests(){
  int fails = 0;

  for(int i = 0; i < TestCases.Count; i++){
   decimal d = TestCases[i];
   input = ExecCommand("TESTSEQ" + d.ToString().PadLeft(2, '0') + "\r", 3000, "No responese from OVPT");
   // ... etc

   if(!TestPassed) fails++;
   
   EventHandler<TestCaseEventArgs> completeHandler = CaseComplete; 
   if(null != completeHandler) completeHandler(this, new TestCaseEventArgs(i, d, TestPassed);

   EventHandler<IntEventArgs> progressHandler = Progress; 
   if(null != progressHandler) progressHandler(this, new IntCaseEventArgs(i);

  }

  EventHandler<IntEventArgs> completedHandler = Completed; 
  if(null != completedHandler) completedHandler(this, new IntCaseEventArgs(fails);
 }

 public class TestCaseEventArgs: EventArgs {
  public int Index { get; private set; }
  public decimal Value { get; private set; }
  public bool Passed { get; private set; }
  public TestCompleteEventArgs(int index, decimal value, bool passed) { Index = index; Value = value; Passed = passed; }
 } 
}


public class IntEventArgs: EventArgs {
 public int Value { get; private set; }
 public TestCompleteEventArgs(int value) { Value = value; }
}


Then your main form should construct the input data for the test run and hook the events. The event handlers will need to use Invoke or BeginInvoke, but that's only 3 methods.

class MainForm {
 // ...

 TestRunner testRunner = new TestRunner();

 private void StartTests(){
  Thread t = new Thread(testRunner.RunTests);
  SetControlState(false); // You'll want to disable most of the UI when a test is running

  // Set up the test cases
  List<decimal> cases = new List<decimal>();
  for(int i = 0; i < checkedListBox1.Items.Count; i++){
   if(checkedListBox1.Items[i].Selected) cases.Add(i);
  }
  progressBar.MaxValue = cases.Count;
  testRunner.TestCases = cases;

  t.Start();
 }

 public MainForm(){
  // some stuff already here

  testRunner.Completed += TestComplete;
  testRunner.Progress += TestProgress;
  testRunner.CaseComplete += CaseComplete;
 }
 
 private void TestProgress(object sender, IntEventArgs e){
  Invoke( () => progressBar.Value = e.Value );
 }

 private void CaseComplete(object sender, TestRunner.TestCaseEventArgs e){
  Invoke ( () => {
   // stuff to do in the UI for a case
  });
 } 

 private void TestComplete(object sender, IntEventArgs e){
  Invoke( ()=> {
            if (e.Value > 0)
            {
                label_Result.Text = "FAILED!!";
                label_Result.BackColor = Color.Red;
                label_Result.ImageIndex = 0;
            }
            else
            {
                label_Result.Text = "PASS!!";
                label_Result.BackColor = Color.Lime;
                label_Result.ImageIndex = 1;
            }

    SetControlState(true);
  } );

  void SetControlState(bool state){
   // Set various controls to enabled or disabled in here
  }
 }


(If you're not using .Net 4 you'll have to Invoke on a proper method or anonymous delegate, instead of this rather neat lambda trick I was shown a while back.)

It's probably possible to have a BackgroundWorkerThread for the test runner, which reports progress and completion, but I'm not sure it gains a lot (the thread class is pretty simple here).
 
Share this answer
 
v2
Comments
andre@electrocomp.co.za 12-Oct-11 6:20am    
Thank you, this is quite a mouthfull for me but I will have a go! :) Appreciated!
BobJanova 12-Oct-11 6:49am    
Thanks. I missed a logical step (setting up the test case list from the form), please check the updated solution (MainForm.StartTests). Ideally your data would be in a model and the controls would read from that, instead of essentially the opposite which is what happens here, but that is too much for one post!
Xeshan Ahmed 12-Oct-11 7:10am    
which .net framework you are using currently ??
BobJanova 12-Oct-11 10:13am    
Lambdas are 3.5, I think. Apart from that (and those can be rewritten as anonymous delegates) it should work in 2.0.
Xeshan Ahmed 12-Oct-11 7:13am    
@BobJanova

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

  Print Answers RSS
Top Experts
Last 24hrsThis month


CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900