C# BackgroundWorker and ProgressBar issue - c#

I have two files. One which contains a variable of type DataProgressBar and calls the startAsyncWorker() method before loading its own components. The other is the DataProgressBar displayed below. When I run my program and call startAsyncWorker() in the other files the behavior
EXPECTED: small window with a progressbar is displayed, loading from 0 to 100 as the work in WorkerDatabaseInsertion is performed. Then when finished my first class file which contains the DataProgressBar, will move on to its next instruction.
EXPERIENCED: small window with a progressbar is displayed, no change is made to the UI, thread seems to freeze as no output or evidence of processing is shown. I close the window, the calling file resumes.
public partial class DataProgressBar : Window
{
private BackgroundWorker bgw;
private String _path;
private LFPReader _lfp;
private Access db;
public DataProgressBar(String p, LFPReader reader, Access database)
{
InitializeComponent();
/* Set private class level variables */
_path = p;
_lfp = reader;
db = database;
db.open();
/* Set up worker and progressbar */
bgw = new BackgroundWorker();
SetUpWorker(bgw);
progressbar.Maximum = 100;
progressbar.Minimum = 0;
progressbar.Value = 0;
}
public void startAsyncWorker()
{
if(bgw.IsBusy != true)
{
bgw.RunWorkerAsync();
}
}
/// <summary>
/// This methods exists for completeness, but we will
/// probably not need to directly cancel the worker from here.
/// --Kurtpr
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void cancelAsyncWorker(object sender, EventArgs e)
{
if (bgw.WorkerSupportsCancellation == true)
{
bgw.CancelAsync();
}
}
private void SetUpWorker(BackgroundWorker worker)
{
worker.WorkerReportsProgress = true; // we need this in order to update the UI
worker.WorkerSupportsCancellation = true; // Not sure why, but we may need this to cancel
worker.DoWork += new DoWorkEventHandler(WorkerDatabaseInsertion);
worker.ProgressChanged += new ProgressChangedEventHandler(WorkerProgressChanged);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerBurnNotice);
}
private void WorkerDatabaseInsertion(object sender, DoWorkEventArgs e) {
BackgroundWorker worker = sender as BackgroundWorker;
ImageInfo ImageData = new ImageDB.ImageInfo();
double size = _lfp.GetImageData().ToArray().Length;
int index = 0;
//_path is setup in loadAsLFP() before this call.
foreach (var image in _lfp.GetImageData())
{
index++;
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
image.FullPath = Path.Combine(_path, image.FullPath);
ImageData.Add(new ImageDB(image));
db.insertImage(new ImageDB(image));
worker.ReportProgress((int)(index/size));
}
}
private void WorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine("Hello");
progressbar.Value = e.ProgressPercentage;
}
private void WorkerBurnNotice(object sender, RunWorkerCompletedEventArgs e)
{
db.close();
}
}
I suspect I am wrong about a fundamental aspect of BackgroundWorker. Where is my logical fault here?
EDIT Here is the code that calls and creates the DataProgressBar object.
private void createDb(string filePath, LFPReader lfp)
{
//Set up LFP related objects.
ImageData = new ImageDB.ImageInfo();
//create database file.
filePath = filePath.Replace(".lfp", ".mdb").Replace(".LFP", ".mdb");
_lfpName = filePath; // update to use the database file
Access db = new Access(filePath.Replace(".lfp", ".mdb"));
db.createDatabase();
//used for calculating progress of creating this new database.
var progressbar = new DataProgressBar(_path, lfp, db);
progressbar.ShowDialog();
progressbar.startAsyncWorker();
CurImage = ImageData.First().FullPath;
//Make sure that data context is set for these images.
DataContext = ImageData;
}

I think your progress calculation logic is faulty here.
worker.ReportProgress((int)(index/size));
This line will always report progress as 0. So, your progress bar will be always stuck at 0 position.
Instead, use following to report progress in percentage.
worker.ReportProgress((int)(index*100/size));
Update:
The code you have shared seems to be correct.
I think the problem lies in the way you have implemented progress bar.
I suppose you are calling startAsyncWorker method from main thread as follows;
var dpb = new DataProgressBar(p, reader, database);
dpb.startAsyncWorker();
After above two lines are called, your main thread should be free.
That means, following code will cause your progressbar to freeze for 50 seconds because, even if your DoWork is running perfectly, UI will not be updated since main thread is not free.
var dpb = new DataProgressBar(p, reader, database);
dpb.startAsyncWorker();
Thread.Sleep(50000); //Main thread is busy for 50 seconds
Update 2:
The real problem lies in following lines;
var progressbar = new DataProgressBar(_path, lfp, db);
progressbar.ShowDialog();
progressbar.startAsyncWorker();
Actually ShowDialog() method shows DataProgressBar as Modal dialog. That means, the control will not go to next line unless you close that dialog.
Your problem should be solved using following code;
var progressbar = new DataProgressBar(_path, lfp, db);
progressbar.startAsyncWorker();
progressbar.ShowDialog();
It will first start your background worker and then DataProgressBar dialog will be shown.

Related

WPF - Load Visio File with Backgroundworker

/Disclaimer, this is my first time working with WPF and with multi-threading, so bear with me if I am making some big mistakes/
So I have an application with a tabcontrol. In one of the Tabs I intend to load in a visio file via the usual windows form host + Visio viewer activeX control etc... And it works perfectly. The only issue is that when I load in the document the UI freezes for 20 seconds (as I am loading in rather huge files). As I read this is because my application is running on a simple thread. So I was trying to implement a background worker to keep the UI reactive while the background thread is running.
When I instantiate my UserControl then I add to the Tab (Initialpath is the filepath of the visio file):
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
worker.RunWorkerAsync(initialpath);
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
DiagramView UCworker;
UCworker = new DiagramView((string)e.Argument);
e.Result = UCworker;
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
UC = (DiagramView)e.Result;
this.Host.Child = UC;
}
And When it creates the new DiagramView:
public DiagramView(string path)
{
InitializeComponent();
this.Resize += new EventHandler(this.UpdateSize);
this.viewer = new AxVisioViewer.AxViewer();
this.Controls.Add(this.viewer);
this.viewer.CreateControl();
this.viewer.Load(path);
this.viewer.HighQualityRender = false;
this.viewer.BackColor = Color.White;
this.viewer.PageTabsVisible = true;
this.viewer.ContextMenuEnabled = false;
this.viewer.PropertyDialogEnabled = false;
this.viewer.ToolbarVisible = true;
this.viewer.OnSelectionChanged += Viewer_OnSelectionChanged;
}
And for this line:
this.viewer = new AxVisioViewer.AxViewer();
I get: : 'ActiveX control 'f8cf7a98-2c45-4c8d-9151-2d716989ddab' cannot be instantiated because the current thread is not in a single-threaded apartment.'
I read that the backgroundworker is not capable to modify the UI elements (correct me if I am wrong)
I also saw this thread: Single-threaded apartment - cannot instantiate ActiveX control
But I am not sure how to implement it (this STA apartment state business) and when I tried, the visio viewer simple crashed when trying to open the document.
I need some guideline how to approach this issue, cause my goal would be to having a loading page to display with an animation until the Document is finished loading/rendering so I can display it.
Thank you in advance for the answers.
UPDATE: I also tried the following approach:
public partial class TabDiagramView : UserControl, INotifyPropertyChanged
{
public delegate void DisplayVisio(DiagramView view);
public DisplayVisio DelegateM;
DiagramView UC;
public TabDiagramView()
{
InitializeComponent();
DelegateM = new DisplayVisio(DisplayV);
Thread t = new Thread(RT);
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = true;
t.Start();
}
#region Thread
private void RT()
{
DiagramView UCworker;
UCworker = new DiagramView(initialpath);
Dispatcher.Invoke(DelegateM, UCworker);
}
private void DisplayV (DiagramView DiagV)
{
UC = DiagV;
this.Host.Child = DiagV;
}
But in this case I get the following message on UC and the this.Host.chilld=DiagV when I am in the DisplayV method: System.InvalidOperationException: 'Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.'
It's simply impossible to load a UI control from another thread. However you could solve the freezing problem if the library had provided a thread-safe approach (e.g. AxViewer.LoadAsync) so the only way to make viewer.Load bearable for the user is to delay viewer.Load operation until the window is loaded:
string _path;
public DiagramView(string path)
{
InitializeComponent();
_path = path;
this.Resize += new EventHandler(this.UpdateSize);
this.viewer = new AxVisioViewer.AxViewer();
this.Controls.Add(this.viewer);
this.viewer.CreateControl();
this.viewer.HighQualityRender = false;
this.viewer.BackColor = Color.White;
this.viewer.PageTabsVisible = true;
this.viewer.ContextMenuEnabled = false;
this.viewer.PropertyDialogEnabled = false;
this.viewer.ToolbarVisible = true;
this.viewer.OnSelectionChanged += Viewer_OnSelectionChanged;
this.Loaded += Viewer_Loaded;
}
private void Viewer_Loaded(object sender, RoutedEventArgs e)
{
viewer.Load(_path);
}
And more importantly, delete the background worker code. It doesn't do any good here.

Controls synchronization between several threads in Windows Forms

I have some controls on the form of the Windows Forms application and I need to update its' texts at run-time from several threads.
Is it safe to just call BeginInvoke method like this:
BeginInvoke((MethodInvoker)delegate()
{
this.label.Text = "Some text";
});
from several threads at the same time? Should I do any additional synchronization in this case? Will it be processed by the same thread one by one and is this order guaranteed?
Thanks in advance.
Calling BeginInvoke puts the delegate on to the message queue to be processed by the UI thread, it will process the queue handling the messages one by one. So no, you do not need to do any additional synchronization (as long as the delegate is not accessing any resources that can't be accessed from the UI thread).
As for order, it is not guaranteed they will be processed in order but in practice most of the time the delegates will be processed in the order they where put in to the queue.
To address the question in the comments, instead of using multiple BeginInvoke calls you should be able to get away with just one.
You never really explained what your animation was so I am going to assume it is going to be that this.label will swap between ., .. and ... then you store the result text in this.label when you are done.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
animationTimer = new System.Windows.Forms.Timer();
animationTimer.Interval = 500;
animationTimer.Tick += animationTimer_Tick;
}
private System.Windows.Forms.Timer animationTimer;
private int dots = 0;
void animationTimer_Tick(object sender, EventArgs e)
{
//Make 1, 2, or 3 dots show up. This runs on the UI thread so we don't need to invoke.
this.label.Text = new String('.', dots + 1);
//Add one then reset to 0 if we reach 3.
dots += 1;
dots = dots % 3;
}
private void button1_Click(object sender, EventArgs e)
{
animationTimer.Start();
Task.Run(() => DoSomeSlowCalcuation());
}
private void DoSomeSlowCalcuation()
{
Thread.Sleep(5000);
this.BeginInvoke((MethodInvoker)delegate()
{
//We stop the timer before we set the text so the timer will not overwrite it.
animationTimer.Stop();
this.label.Text = "Some text";
});
}
}
This code is just a example to get my point across, if I where doing this I would use async/await for the button click and not use BeginInvoke at all.
private async void button1_Click(object sender, EventArgs e)
{
animationTimer.Start();
var result = await Task.Run(() => DoSomeSlowCalcuation());
animationTimer.Stop();
this.label.Text = result;
}
private string DoSomeSlowCalcuation()
{
Thread.Sleep(5000);
return "Some text";
}

Maintain a responsive user interface with backgroundWorker and heavy animation

I have a C# application (winforms and wpf, but for this question I'm focusing on the winforms one) where a backgroundWorker is used to work on a data set, and the call to ProgressChanged which then calls the form Refresh method to force a repaint. This then paints a bunch of ellipses based on the current frame of the data set.
A given frame may involve drawing anywhere between zero and several hundred ellipses.
In addition, I have a slider control that allows the user to adjust the playback rate (basically the thread.sleep value within the loop.)
When the user sets the sleep value too low, sometimes the repainting methods get queued up, and the UI becomes unresponsive. (This depends on the number of ellipses in the frame, and the speed of the computer. And the delay is 100% with the repainting on the UI, not with any other processing, which is basically just incrementing a counter and setting a label text.)
I would like to be able to detect the queuing up and automatically adjust the speed slider to accommodate a larger data set and/or slower computer. How can I tell if the UI thread is backed up with multiple calls to Map_Paint?
Current code (paraphrased):
public Map()
{
InitializeComponent();
_worker = new BackgroundWorker();
_worker.DoWork += _worker_DoWork;
_worker.ProgressChanged += _worker_ProgressChanged;
_worker.WorkerReportsProgress = true;
}
private void _worker_DoWork(object sender, DoWorkEventArgs e)
{
_frameCount = _frames.FrameCount();
// For this specific example, _frameCount may be around 30000-40000
for (var i = 0; i < _frameCount; i++)
{
var f = _frames.Frame(i + 1);
_worker.ReportProgress(i, f);
Thread.Sleep(_tickCount);
_suspend.WaitOne(); // Used to Pause the playback
}
}
void _worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// set some variables according to state and progresspercentage snipped here
// ...
// I would like to detect at this point whether the screen repainting is backed up
// and if so, adjust the value of _tickCount to slow down the program.
this.Refresh();
}
private void Map_Paint(object sender, PaintEventArgs e)
{
// Lots of ellipsis drawing stuff here
// Maybe 0-1000 ellipses drawn per cycle.
}
private void tbSpeed_Scroll(object sender, EventArgs e)
{
// This is the Scroll event for the slider.
// Value range is 10-300
// The slider becomes unresponsive when the UI thread backs up.
// I'd like to detect the back up and override the value of _tickCount
_tickCount = tbSpeed.Value;
}
private static object _lock = new object();
private static int _queuedCount = 0;
public Map()
{
InitializeComponent();
_worker = new BackgroundWorker();
_worker.DoWork += _worker_DoWork;
_worker.ProgressChanged += _worker_ProgressChanged;
_worker.WorkerReportsProgress = true;
}
private void _worker_DoWork(object sender, DoWorkEventArgs e)
{
_frameCount = _frames.FrameCount();
// For this specific example, _frameCount may be around 30000-40000
for (var i = 0; i < _frameCount; i++)
{
var f = _frames.Frame(i + 1);
lock(_lock)
{
_queuedCount++;
}
_worker.ReportProgress(i, f);
Thread.Sleep(_tickCount);
_suspend.WaitOne(); // Used to Pause the playback
}
}
void _worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (_queuedCount > 1)
//now queue is building up
this.Refresh();
lock(_lock)
{
_queuedCount--;
}
}

Instantiating a progress bar that runs asynchronously and modifying it with thread safe calls

In my application I have a queue download list which consists of progress bars and the file names. When the user clicks a button the file name and progress bar is instantiated and added to the queue. Files download one at a time and asynchronously. What I want to do is keep all the progress bars of the files that are waiting to be downloaded yellow in color and then turn green when it is being downloaded and then turn blue when they are completed. It currently works if I have CheckForIllegalCrossThreadCalls = false; in the constructor of the custom progress bar. I want to see if there is a way to make thread safe changes to the progress bars.
I have each queue item set up as an object. The queue item objects are created from the main form code (Form1.cs) when a button is pressed and the progress bars are created in the queue item constructor, which is probably where my problem begins. The downloads are started through a function in the queue item object.
Queue Item Snippet
public class QueueItem
{
public bool inProgress;
public QueueBar bar;
public QueueItem(args)
{
bar = new QueueBar();
inProgress = false;
// handle arguments
}
public void Download()
{
// process info
WebClient client = new WebClient();
client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
client.DownloadFileAsync(url, #savePath);
}
private long lastByte = 0;
private long newByte = 0;
private void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
percentValue = e.ProgressPercentage;
bar.Value = e.ProgressPercentage;
newByte = e.BytesReceived;
}
private void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
// change bar color
bar.Value = 100;
}
}
Queue Bar Snippet
public class QueueBar : ProgressBar
{
// variables
public QueueBar()
{
this.SetStyle(ControlStyles.UserPaint, true);
// initialize variables
}
// function to change text properties
// function to change color
protected override void OnPaint(PaintEventArgs e)
{
// painting
}
}
Main Function Snippet
public partial class Form1 : Form
{
private List<QueueItem> qItems;
private BackgroundWorker queue;
private void button_Click(object sender, EventArgs e)
{
// basic gist of it
qItems.Add(new QueueItem(args));
Label tmpLabel = new Label();
tmpLabel.Text = filename;
tmpLabel.Dock = DockStyle.Bottm;
splitContainerQueue.Panel2.Controls.Add(tmpLabel);
splitContainerQueue.Panel2.Controls.Add(qItems[qItems.Count - 1].bar);
if (!queue.IsBusy) { queue.RunWorkerAsync(); }
}
private void queue_DoWork(object sender, DoWorkEventArgs e)
{
while (qItems.Count > 0)
{
if (!qItems[0].inProgress && qItems[0].percentValue == 0)
{
qItems[0].inProgress = true;
qItems[0].Download();
}
// else if statements
}
}
I also just tried creating a background worker to create the Queue Items and add the controls asynchronously but that doesn't work since the split container was created on a different thread.
You cannot call a UI control (created on your UI thread) from another thread safely - you need to use InvokeRequired / BeginInvoke() for such calls. When calling BeginInvoke() you'll pass a delegate; something like this (just some sample code, yours will look slightly different):
private void SomeEventHandler ( object oSender, EventArgs oE )
{
if ( InvokeRequired )
{
MethodInvoker oDelegate = (MethodInvoker) delegate
{
SomeEventHandler ( oSender, oE );
};
BeginInvoke ( oDelegate );
return;
}
else
{
// already on the correct thread; access UI controls here
}
}
You also cannot create your progress bars away from the UI thread - you need to create all your controls as part of your UI and then if you need to access these progress bars from your queue items, you'll have to pass in a reference to the progress bar. When you try to access the progress bar, you'll do
if ( bar.InvokeRequired ) { ... }
to determine if you're trying to call it from the right thread.
The reason for this mess is because controls handle many of their property updates through messages and those messages must be delivered synchronously, in the correct order. The only way to ensure this (without some very complex coding) is to create all controls on the same thread where the thread runs a message pump.

c# - Pass information to BackgroundWorker From UI during execution

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.
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);
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.
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.

Categories