Search code examples
c#backgroundworker

Update textbox from loop in backgroundworker


I know this questions gets asked a bit (at least from what I found here so far), but I can't really wrap my head around it. Already tried it with the example from msdn but still no succes. Here is what I'm trying to do: I have a USB-Counter connected to a TLL-ruler. I want to read the value constantly in a loop and write the readout to a textbox without blocking the main UI. I know from other questions that I should use Invoke or Backgroundworker, but have not really found an example which I understood and could use to adjust my code accordingly. The code without any modification, to keep it simple, is as follows:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace USB_Counter
{
public partial class Form1 : Form
{
    [DllImport("HS_UC.dll", EntryPoint = "HS_UC_Close")] //+further DLL imports (driver for USBCounter)
    public static extern int HS_UC_Close(int CounterNo);

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e) //initialize COunter
    {
        int A = HS_UC_Init(1, 3);
        string B = Convert.ToString(A);
        MessageBox.Show(B); //to check if there is no error
    }

    private void button2_Click(object sender, EventArgs e)
    {
        HS_UC_SetDistCode(1, 20, 40, 4, 0, 0); //set Reference
        HS_UC_SetRefMode(1, 1);
    }

    private void button3_Click(object sender, EventArgs e)
    {
        int a = 1;
        int A = 0; //variable that takes counter value (the one I want)
        int B = 0; //variable that takes counter status
        do
        {
            HS_UC_GetCounter(1, ref A, ref B);
            decimal C = (Convert.ToDecimal(A) / 100);
            textBox1.Text = "Das ist: " + C;
            textBox1.Update();
        } while (a == 1);
    }
}

}

Now this works as intendet, but as mentioned it blocks the main UI thread (obviously). If anyone found a similar question with some helpful tips to get started with this multithreading topic or any helpful tips regarding my question directly, that would be greatly appreciated. Best regards from Berlin, Chris

Update: got it working with following attempt:

private void Counter_Read()
    {
        int a = 1;
        do
        {
            int A = 0;
            int B = 0;
            HS_UC_GetCounter(1, ref A, ref B);
            decimal C = (Convert.ToDecimal(A) / 100);
            UpdateTextBox(C);
        } while (a == 1);
    }

    public void UpdateTextBox(decimal C)
    {
        if (InvokeRequired)
        {
            this.Invoke(new Action<decimal>(UpdateTextBox), new object[] { C });
            return;
        }
        textBox1.Text = "Das ist: " + C;
        textBox1.Update();

    }

    private void button3_Click(object sender, EventArgs e)
    {
        System.Threading.Thread t = new System.Threading.Thread(() => Counter_Read());
        t.Start();
    }

From that I get a decimal output which i constantly updating and still am able to utilize the other buttons.


Solution

  • outsource the loop code into a method. Inside the method you will need to use BeginInvoke to write to the TextBox

    private void DoTheLoop()
    {
        int a = 1;
        int A = 0; //variable that takes counter value (the one I want)
        int B = 0; //variable that takes counter status
        do
        {
            HS_UC_GetCounter(1, ref A, ref B);
            decimal C = (Convert.ToDecimal(A) / 100);
            textBox1.BeginInvoke(new Action(()=>{textBox1.Text = "Das ist: " + C;}));
        } while (a == 1);
    }
    

    First version using a normal Thread:

    Create a Thread and start it with the new method when the button3 is clicked

    private void button3_Click(object sender, EventArgs e)
    {
        System.Threading.Thread t = new System.Threading.Thread(()=>DoTheLoop());
        t.Start();
    }
    

    This should not block your GUI and the textbox will show the values

    Second Version using a BackgroundWorker:

    Create a BackgroundWorker and register the DoWork event:

    System.ComponentModel.BackgroundWorker worker = new System.ComponentModel.BackgroundWorker();
    
    private void Form1_Load(object sender, EventArgs e)
    {
        worker.DoWork += Worker_DoWork;
    }
    

    inside the eventhandler call the same method DoTheLoop():

    private void Worker_DoWork(object sender, DoWorkEventArgs e)
    {
        DoTheLoop();
    }
    

    start the worker in the button click event:

    private void button1_Click(object sender, EventArgs e)
    {
        worker.RunWorkerAsync();
    }
    

    Same result in the end :)