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 callingBeginInvoke()you’ll pass a delegate; something like this (just some sample code, yours will look slightly different):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
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.