I have a C # application that uses a desktop thread and quite successfully updates the user interface from a working thread. The application includes the shortest route routing in the network, and I display the network and the shortest path in the user interface when the background worker is running. I want the user to be able to slow down the display using the slider while the application is running.
I found this as a suggestion, but it is in vb.net, I donβt understand how to make it work in C #.
How can BackgroundWorker get values ββfrom a user interface thread while it is running?
I can pass the value of the slider into the wallpaper as follows:
// Run the asynchronous operation. delay = this.trackBar1.Value; backgroundWorker1.RunWorkerAsync (delay);
and use it in the backgroundworker thread, but it uses only the original value. I donβt understand how to get the value from inside the background worker when I move the slider in the user interface.
I used to use several threads and delegates, but if you can use a background worker, I would prefer it for its simplicity.
10/10/2012
Thank you all for your answers. I'm still having problems, most likely due to the fact that I have structured things. High load calculations for network routing are performed in the TransportationDelayModel class. BackgroundWorker_DoWork instantiates this class and then turns it off. The delay is handled by TransportDelayModel.
The code skeleton is as follows:
In the user interface:
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; } }
Constructor TransportationDelayModel:
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);
so I have tdmWorker that allows me to pass information back to the user interface.
In internal calculations in TransportationDelayModel, I sleep for a delay period
if (delay2 > 0) { tdmWorker.ReportProgress(-12, zzz); System.Threading.Thread.Sleep(delay2); }
therefore, the problem is how to pass the updated value of the slider from the user interface back to the object that is running in the background worker. I tried several combinations, seemingly turning around, to no avail, nothing happens, or I get a message that I am not allowed to access what is happening in another thread. I understand that if I did all the work in the DoWork event handler, I would have to do everything that you offer, but there are too many difficulties for this.
Thanks again for your suggestions and help.
6/2/2012
I solved this problem in two ways, but I have some questions. In my commentary on R. Harvey, I created a simple application. It consists of a form with a start button, a slider and a rich text field. The start button starts a workflow thread that creates an object of the Model class that does all the work (a simplified surrogate for my transport model). The Model class simply writes 100 lines to the text field, increasing the number of points in each line by 1, with a delay between each line based on the slider setting and the value of the slider at the end of the line, something like this:
.................... 58
..................... 58
...................... 58
....................... 51
........................ 44
......................... 44
The goal of this exercise is the ability to move the slider in the form during the operation of the "Model" and obtain a delay for the change (as described above).
My first solution involves creating 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 scrollbar scrolls:
private void trackBar1_Scroll(object sender, EventArgs e) { Globals.globalDelay = this.trackBar1.Value; }
and in the Model, I just raise the value of 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 great. My question is: are there any flaws in this approach that seem very simple to implement and fairly general.
The second approach, based on the proposals of R. Harvey, uses delegates and calls.
I am creating a class for delegates:
public class MyDelegates { public delegate int DelegateCheckTrackBarValue();
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 started by the start button, the calling form is submitted
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) {
Finally, in the Model, the Invoke method is called in the passed form to get the value of the trackbar. public void Run (BackgroundWorker employee, DoWorkEventArgs e) {
for (int i = 1; i < 100; i++) { int delay = (int)passedForm.Invoke(m_CheckTrackBarValue,null);
This also works. I kept getting the error until I made the member variable of the variable static, for example. public static MyDelegates.DelegateCheckTrackBarValue m_CheckTrackBarValue = null;
My questions about this solution: are there any advantages to this solution in relation to the previous version? Am I too complicated on how I did this? Why m_CheckTrackBarValue should be static.
I apologize for the length of this edit, but I thought the problem and solutions might be of interest to others.