5
votes

I have a c# application that uses a background worker thread, and quite successfully updates the UI from the running thread. The application involves shortest path routing on a network, and I display the network and the shortest path, on the UI, as the background worker proceeds. I would like to allow the user to slow down the display through use of a slider, while the application is running.

I found this as a suggestion, but it is in vb.net, I am not clear on how to get it to work in c#.

How can the BackgroundWorker get values from the UI thread while it is running?

I can pass the value of the slider to the backgroundworker as follows:

// Start the asynchronous operation. delay = this.trackBar1.Value; backgroundWorker1.RunWorkerAsync(delay);

and use it within the backgroundworker thread, but it only uses the initially-sent value. I am not clear on how to pick up the value from inside the backgroundworker when I move the slider on the UI.

I have previously used multiple threads and delegates, but if it is possible to utilize the background worker, I would prefer it for its simplicity.

5/10/2012

Thanks to all for your responses. I am still having problems, most likely because of how I have structured things. The heavy duty calculations for network routing are done in the TransportationDelayModel class. BackgroundWorker_DoWork creates an instance of this class, and then kicks it off. The delay is handled in TransportationDelayModel.

The skeleton of code is as follows:

In UI:

 private void runToolStripMenuItem1_Click(object sender, EventArgs e)
    {
        if (sqliteFileName.Equals("Not Set"))
        {
            MessageBox.Show("Database Name Not Set");
            this.chooseDatabaseToolStripMenuItem_Click(sender, e);

        }

        if (backgroundWorker1.IsBusy != true)
        {
            // Start the asynchronous operation.
            delay = this.trackBar1.Value;
            // pass the initial value of delay
            backgroundWorker1.RunWorkerAsync(delay);
            // preclude multiple runs
            runToolStripMenuItem1.Enabled = false;
            toolStripButton2.Enabled = false;
        }

    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;

        if (!backgroundWorkerLaunched)
        {
            // instantiate the object that does all the heavy work
            TransportationDelayModel TDM = new TransportationDelayModel(worker, e);               
            // kick it off
            TDM.Run(sqliteFileName, worker, e);
            backgroundWorkerLaunched = true;
        }


    }

The TransportationDelayModel constructor is:

 public TransportationDelayModel(BackgroundWorker worker, DoWorkEventArgs e)
    {
        listCentroids = new List<RoadNode>();
        listCentroidIDs = new List<int>();
        listNodes = new List<RoadNode>();
        listNodeIDs = new List<int>();
        listRoadLink = new List<RoadLink>();
        roadGraph = new AdjacencyGraph<int, RoadLink>(true); // note parallel edges allowed
        tdmWorker = worker;
        tdmEvent = e;
        networkForm = new NetworkForm();
    }

so I have the tdmWorker, which allows me to pass information back to the UI.

In the internal calculations in TransportationDelayModel, I sleep for the delay period

  if (delay2 > 0)
                                    {
                                        tdmWorker.ReportProgress(-12, zzz);
                                        System.Threading.Thread.Sleep(delay2);
                                    }

so the problem seems to be how to pass an updated slider value from the UI back to the object that is executing in the background worker. I have tried a number of combinations, sort of thrashing around, to no avail, either nothing happens or I get a message about not being allowed to access what is happening on the other thread. I realize that if I were doing all the work in the DoWork event handler, then I should be able to do things as you suggest, but there is too much complexity for that to happen.

Again, thank you for your suggestions and help.

6/2/2012

I have resolved this problem by two methods, but I have some questions. Per my comment to R. Harvey, I have built a simple application. It consists of a form with a run button, a slider, and a rich text box. The run button launches a background worker thread that instantiates an object of class "Model" that does all the work (a simplified surrogate for my TransportationModel). The Model class simply writes 100 lines to the text box, incrementing the number of dots in each line by 1, with a delay between each line based on the setting of the slider, and the slider value at the end of the line, something like this:

....................58

.....................58

......................58

.......................51

........................44

.........................44

The objective of this exercise is to be able to move the slider on the form while the "Model" is running, and get the delay to change (as in above).

My first solution involves the creation of a Globals class, to hold the value of the slider:

class Globals
{
    public static int globalDelay;
}

then, in the form, I update this value whenever the trackbar is scrolled:

private void trackBar1_Scroll(object sender, EventArgs e)
    {
        Globals.globalDelay = this.trackBar1.Value;
    } 

and in the Model, I just pick up the value of the global:

 public void Run(BackgroundWorker worker, DoWorkEventArgs e)
 {

     for (int i = 1; i < 100; i++)
     {
         delay = Globals.globalDelay; // revise delay based on static global set on UI
         System.Threading.Thread.Sleep(delay);
         worker.ReportProgress(i);
         string reportString = ".";
         for (int k = 0; k < i; k++)
         {
             reportString += ".";
         }
         reportString += delay.ToString();
         worker.ReportProgress(-1, reportString);

     }
 }
}

This works just fine. My question: are there any drawbacks to this approach, which seems very simple to implement and quite general.

The second approach, based on suggestions by R. Harvey, makes use of delegates and invoke.

I create a class for delegates:

 public class MyDelegates
{
    public delegate int DelegateCheckTrackBarValue(); // create the delegate here
}

in the form, I create:

  public int CheckTrackBarValue()
    {
        return this.trackBar1.Value;

    } 

and the Model class now has a member m_CheckTrackBarValue

 public class Model 
{

#region Members

Form1 passedForm;
public static MyDelegates.DelegateCheckTrackBarValue m_CheckTrackBarValue=null;      
#endregion Members

#region Constructor
public Model(BackgroundWorker worker, DoWorkEventArgs e, Form1 form)
{
    passedForm = form;
}

When the background thread is launched by the run button, the calling form is passed

private void button1_Click(object sender, EventArgs e) { if (backgroundWorker1.IsBusy != true) {

            backgroundWorker1.RunWorkerAsync();

        }
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {

        BackgroundWorker worker = sender as BackgroundWorker;

        if (!backgroundWorkerLaunched)
        {
            // instantiate the object that does all the heavy work
            Model myModel= new Model(worker, e, this);

            Model.m_CheckTrackBarValue = new MyDelegates.DelegateCheckTrackBarValue(this.CheckTrackBarValue);

            // kick it off
            myModel.Run(worker, e);
            backgroundWorkerLaunched = true;
        }
    }

Finally, in the Model, the Invoke method is called on the passed form to get the value of the trackbar. public void Run(BackgroundWorker worker, DoWorkEventArgs e) {

     for (int i = 1; i < 100; i++)
     {
         int delay = (int)passedForm.Invoke(m_CheckTrackBarValue,null); // invoke the method, note need the cast here
         System.Threading.Thread.Sleep(delay);
         worker.ReportProgress(i);
         string reportString = ".";
         for (int k = 0; k < i; k++)
         {
             reportString += ".";
         }
         reportString += delay.ToString();
         worker.ReportProgress(-1, reportString);

     }
 }

This works as well. I kept getting an error until I made the member variable static, e.g. public static MyDelegates.DelegateCheckTrackBarValue m_CheckTrackBarValue=null;

My questions on this solution: Are there advantages to this solution as regards to the previous version? Am I making things too complicated in the way I have implemented this? Why does m_CheckTrackBarValue need to be static.

I apologize for the length of this edit, but I thought that the problem and solutions might be of interest to others.

3
Your user card (that light blue box to the right and below your question) serves as your signature --^^^. You can customize your user card here.Robert Harvey
any final solution with full source code ?Kiquenet

3 Answers

2
votes

You have to pass the TrackBar object to the BackgroundWorker, not delay. delay doesn't change once you set it.

To simplify the needed Invoke(), you can use a helper method, such as this one:

Async.UI(delegate { textBox1.Text = "This is way easier!"; }, textBox1, true);
1
votes

I will assume that you are already familiarized with cross-thread invocation to update the UI. So, the solution is very simple: in your worker thread, after each iteration, invoke the UI to get the slider thumb position.

1
votes

To use a backgroundworker, you add a method to the DoWork property, like this:

this.backgroundWorker1.WorkerSupportsCancellation = true;
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);

In the DoWork method, you need to check the variable where the updated delay is set.

This could be an integer field that is available on the containing Form or UI control, or it could be the TrackBar itself.