I've created an application that patches my game servers files.
However, I've got 3 problems which I can't solve:
When downloading a new patch, it doesn't update the progressbar instantly, but refreshes it after around 30-40 seconds
I want a label to show how much mega bytes they are downloading, and how much they have so far (for example: 122Mb/750Mb
When downloading, I want a label to show ~% of how much it has downloaded so far
I am not sure how to add number 2 and 3, and the number 1 problem just seems ridiculous, because there's nothing that indicates it should refresh after 30-40 seconds in my coding (at least as far as I know)
My backgroundWorker1_DoWork:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//Defines the server's update directory
string Server = "http://localhost/dl/game-updates/";
//Defines application root
string Root = AppDomain.CurrentDomain.BaseDirectory;
//Make sure version file exists
FileStream fs = null;
if (!File.Exists("version"))
{
using (fs = File.Create("version"))
{
}
using (StreamWriter sw = new StreamWriter("version"))
{
sw.Write("1.0");
}
}
//checks client version
string lclVersion;
using (StreamReader reader = new StreamReader("version"))
{
lclVersion = reader.ReadLine();
}
decimal localVersion = decimal.Parse(lclVersion);
//server's list of updates
XDocument serverXml = XDocument.Load(#Server + "Updates.xml");
//The Update Process
foreach (XElement update in serverXml.Descendants("update"))
{
string version = update.Element("version").Value;
string file = update.Element("file").Value;
decimal serverVersion = decimal.Parse(version);
string sUrlToReadFileFrom = Server + file;
string sFilePathToWriteFileTo = Root + file;
if (serverVersion > localVersion)
{
Uri url = new Uri(sUrlToReadFileFrom);
System.Net.HttpWebRequest request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
System.Net.HttpWebResponse response = (System.Net.HttpWebResponse)request.GetResponse();
response.Close();
Int64 iSize = response.ContentLength;
Int64 iRunningByteTotal = 0;
using (System.Net.WebClient client = new System.Net.WebClient())
{
using (System.IO.Stream streamRemote = client.OpenRead(new Uri(sUrlToReadFileFrom)))
{
using (Stream streamLocal = new FileStream(sFilePathToWriteFileTo, FileMode.Create, FileAccess.Write, FileShare.None))
{
int iByteSize = 0;
byte[] byteBuffer = new byte[iSize];
while ((iByteSize = streamRemote.Read(byteBuffer, 0, byteBuffer.Length)) > 0)
{
streamLocal.Write(byteBuffer, 0, iByteSize);
iRunningByteTotal += iByteSize;
double dIndex = (double)(iRunningByteTotal);
double dTotal = (double)byteBuffer.Length;
double dProgressPercentage = (dIndex / dTotal);
int iProgressPercentage = (int)(dProgressPercentage * 100);
backgroundWorker1.ReportProgress(iProgressPercentage);
}
streamLocal.Close();
}
streamRemote.Close();
}
}
//unzip
using (ZipFile zip = ZipFile.Read(file))
{
foreach (ZipEntry zipFiles in zip)
{
zipFiles.Extract(Root + "\\", true);
}
}
//download new version file
WebClient webClient = new WebClient();
webClient.DownloadFile(Server + "version.txt", #Root + "version");
//Delete Zip File
deleteFile(file);
}
}
}
My backgroundWorker1_ProgressChanged:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
label1.Text = "Downloading updates...";
}
And my backgroundWorker1_RunWorkerCompleted:
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
settings_btn.Enabled = true;
start_btn_disabled.Enabled = false;
start_btn_disabled.Visible = false;
start_btn.Visible = true;
start_btn.Enabled = true;
progressBar1.Value = 100;
label1.Text = "Client is up to date!";
}
Also, a side note: I'm also having a bit problems of updating labels in backgroundWorker2_DoWork?
Any ideas?
Here's some working code which updates a label on Form1 using the BackgroundWorker.
Create a new Windows Form project and drop it in your code and it'll work.
It's super ugly, but it works.
After that, just plug your code into the DoWork method and calculate your value and send to ReportProgress.
Keep in mind that the work done in DoWork method is the actual Background Thread.
That means that in that method (DoWork) you cannot access UI (form) elements because they are on the UI thread.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
}
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.Enabled = true;
}
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
backgroundWorker1.RunWorkerAsync();
}
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = e.ProgressPercentage.ToString();
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
FakeCountingWork();
}
private void FakeCountingWork()
{
int totalNumber = 100;
int progressCounter = 0;
while (progressCounter < totalNumber)
{
int fakecounter = 0;
for (int x = 0; x < 100000000; x++)
{
fakecounter++;
}
progressCounter++;
backgroundWorker1.ReportProgress(progressCounter);
}
}
}
################################## EDITED TO ADD OTHER FUNCTIONALITY
Okay, here's how you can implement a label which displays the number of bytes downloaded so far.
Add a second label named label2 to your form.
Next alter the following methods from my previous example.
Here we are going to use the UserState to pass an extra value to the ProgressChanged Event. It's very simple. You can see that I'm generating a random number and it will now appear in Label2. This is where you could show your number of bytes.
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = e.ProgressPercentage.ToString();
label2.Text = e.UserState.ToString();
}
private void FakeCountingWork()
{
int totalNumber = 100;
int progressCounter = 0;
Random rnd = new Random();
while (progressCounter < totalNumber)
{
int fakecounter = 0;
for (int x = 0; x < 100000000; x++)
{
fakecounter++;
}
progressCounter++;
updateValue = rnd.Next();
backgroundWorker1.ReportProgress(progressCounter,updateValue);
}
}
I would imagine this is because you are trying to update UI objects on a different thread. HAve you tried using the Dispatcher if using wpf? https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.invoke(v=vs.110).aspx
or Invoke if using Winforms? https://msdn.microsoft.com/fr-ca/library/zyzhdc6b(v=vs.85).aspx
Edit:
As #daylight pointed out to be, the UI was being updated in the progresschanged event, which executes on the thread which created the background worker, therefore there shouldn't be an issue regarding threading. See https://msdn.microsoft.com/en-us/library/ka89zff4(v=vs.110).aspx for more info
Related
I'm currently working on an interface between C# and an arduino project. I'm using the serial port to send and receive messages from the arduino and so far everything looks fine.
However I came to a point, where I want to send a string to the arduino and wait for a response until I'm going to send over the next string.
I figured that I cannot simply timeout here since my RichTextField is also going to freeze and the "OK*" message cannot be read from there.
I've read about some other solutions online but since I'm very new to C# I hope that somebody can point me in the right direction.
Best regards
//Edited the post with a full code example:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.IO.Ports;
namespace interface_test
{
public partial class Form1 : Form
{
string serialDataIn;
public Form1()
{
InitializeComponent();
Enable_Console();
comboBox_baudRate.Text = "115200";
comboBox_comPort.Text = "COM3";
string[] portLists = SerialPort.GetPortNames();
comboBox_comPort.Items.AddRange(portLists);
}
private void Form1_Load(object sender, EventArgs e)
{
}
/* erase textbox contents */
private void button_clearLog_Click(object sender, EventArgs e)
{
richTextBox_receiveMsg.Text = "";
}
/* listening to the serial port */
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
serialDataIn = serialPort1.ReadExisting();
this.Invoke(new EventHandler(ShowData));
}
/* process the serial data */
public void ShowData(object sender, EventArgs e)
{
string trimmedMsg = serialDataIn;
richTextBox_receiveMsg.Text += trimmedMsg;
if (trimmedMsg.Contains("Initialization done."))
{
button_detectCEM.Enabled = false;
}
}
/* open COM-port to arduino */
private void button_openCom_Click(object sender, EventArgs e)
{
try
{
serialPort1.PortName = comboBox_comPort.Text;
serialPort1.BaudRate = Convert.ToInt32(comboBox_baudRate.Text);
serialPort1.Open();
Enable_Console();
button_detectCEM.Enabled = true;
}
catch (Exception error)
{
MessageBox.Show(error.Message);
}
}
/* close COM-port to arduino */
private void button_closeCom_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
try
{
serialPort1.Close();
Enable_Console();
}
catch (Exception error)
{
MessageBox.Show(error.Message);
}
}
}
private void button_fileBrowser_Click(object sender, EventArgs e)
{
openAndWrite();
}
public void openAndWrite()
{
/* load a .txt file */
OpenFileDialog openFileDialog1 = new OpenFileDialog();
openFileDialog1.InitialDirectory = "c:\\";
openFileDialog1.Filter = "TXT files (*.txt)|*.txt";
openFileDialog1.FilterIndex = 0;
openFileDialog1.RestoreDirectory = true;
string selectedFile = "";
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
selectedFile = openFileDialog1.FileName;
}
/* parse file and store it in data[] */
byte[] data;
using (FileStream fsSource = new FileStream(selectedFile, FileMode.Open, FileAccess.Read))
{
data = new byte[fsSource.Length];
int numBytesToRead = (int)fsSource.Length;
int numBytesRead = 0;
while (numBytesToRead > 0)
{
// Read may return anything from 0 to numBytesToRead.
int n = fsSource.Read(data, numBytesRead, numBytesToRead);
// Break when the end of the file is reached.
if (n == 0)
break;
numBytesRead += n;
numBytesToRead -= n;
}
numBytesToRead = data.Length;
}
for (int i = 0; i < BitConverter.ToInt32(blockLength, 0); i++)
{
data[i] = data[offset + i];
}
/* write data block */
offset = 0;
while (offset < data.Length)
{
byte[] dataString = new byte[6];
string blockMsg = "FFFFFF";
int start = offset;
offset += 6;
if (offset > data.Length) offset = data.Length;
for (int i = 0; i < 6; i++)
{
if ((start + i) < data.Length) dataString[i] = data[start + i];
}
blockMsg += BitConverter.ToString(dataString).Replace("-", string.Empty);
blockMsg += "*";
serialPort1.Write(blockMsg);
//Wait for "OK*" before next Message...
}
}
}
}
}
There's a few ways that you could solve this. My approach would be to make use of the TaskCompletionSource class and a lambda function. Here's a rough example, you'll need to make the method containing this code async, and potentially have it return a Task to await it all the way up the chain.
var tcs = new TaskCompletionSource<string>();
serialPort1.DataReceived += (s, e) =>
{
SerialPort sp = (SerialPort)s;
string newData = sp.ReadExisting();
tcs.SetResult(newData);
}
serialPort1.Write(Msg);
var dataFromPort = await tcs.Task;
I'm away from an IDE, nor do I have access to all your code, so without the wider context it's hard to know exactly what else you might need. But I've done some Arduino work in the past, and this is how I solved the problem.
EDIT :: Thinking about it, you might (test it first) run into a problem because that event isn't being unsubscribed from and the code is running in a loop. IF that turns out to be a problem, you could make use of C#'s Local Functions to handle the event subscription, like this:
var tcs = new TaskCompletionSource<string>();
void localHandler(object s, SerialDataReceivedEventArgs e)
{
serialPort1.DataReceived -= localHandler;
SerialPort sp = (SerialPort)s;
string newData = sp.ReadExisting();
tcs.SetResult(newData);
};
serialPort1.DataReceived += localHandler
serialPort1.Write(Msg);
var dataFromPort = await tcs.Task;
It might look a little strange, but it's certainly cleaner (IMO) then needing to keep track of instance variables and manually manage threads.
EDIT2 :: Tested when I was sat at a computer, if I understand your requirement correctly, this should do everything you need.
This may take you in the right direction.
It might be useful in this case to separate the data received from the display update thread. Since the data received is an event, it will be coming in on its own thread, likely from some component - in this case the serial port.
The form, and all the display objects on it, run on a single thread, owned by the form.
You can use a timer as a background thread to make the leap between what was received and what you want to do/display.
For example, one way is to use a concurrent queue and a timer to poll the status of the queue.
using System.Collections.Concurrent;
private ConcurrentQueue<string> dataIn = new ConcurrentQueue<string>();
private System.Threading.Timer timer = new System.Threading.Timer(CheckQue,null,TimeSpan.FromSeconds(1),TimeSpan.FromSeconds(1));
private void CheckQue(object state)
{
while(dataIn.TryDequeue(out var data))
{
ShowData(data);
if (data.Contains("OK*"))
{
//Do Something
//Better yet raise an event that does something
Msg += BitConverter.ToString(dataString).Replace("-", string.Empty);
Msg += "*";
serialPort1.Write(Msg);
}
}
}
Then modify your above code to add to the queue when data is received
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
dataIn.Enqueue(SerialPort1.ReadExisting());
}
And since your using a windows form, you need to make sure you don't get a cross-threaded operation when updating it, by marshaling the update onto the form's thread like so.
public static void ShowData(string data)
{
if (this.InvokeRequired)
{
var del = new MethodInvoker(() => ShowData(data));
try
{
this.Invoke(del);
return;
}
catch
{ }
}
richTextBox_receiveMsg.Text += data;
}
EDIT based on your revised question:
To modify for sending blocks of a file you could added another Que
private ConcurrentQueue<string> dataOut = new ConcurrentQueue<string>();
Then when you read the file, do whatever processing you need and put the final output in the queue and finish by sending the first block.
public void openAndWrite()
{
/* load a .txt file */
.
.
.
while (offset < data.Length)
{
.
.
blockMsg += BitConverter.ToString(dataString).Replace("-", string.Empty);
blockMsg += "*";
dataOut.Enqueue(blockMsg);
}
SendBlock();
}
Where SendBlock looks like this
private void SendBlock()
{
if(dataOut.TryDequeue(out var data))
{
serialPort1.Write(data);
}
}
Then just modify the timer handler to send blocks as needed
private void CheckQue(object state)
{
while (dataIn.TryDequeue(out var data))
{
ShowData(data);
if (data.Contains("OK*"))
{
SendBlock();
}
}
}
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.Windows.Forms;
using System.IO;
using System.Net;
using System.Xml.Linq;
using System.Diagnostics;
using System.Management;
using System.Runtime.InteropServices;
namespace DownloadFiles
{
public partial class Form1 : Form
{
Stopwatch sw = new Stopwatch();
Stopwatch stopwatch = new Stopwatch();
string filesdirectory = "Downloaded_Files";
string mainurl = "http://www.usgodae.org/ftp/outgoing/fnmoc/models/navgem_0.5/latest_data/";
List<string> parsedlinks = new List<string>();
string path_exe = Path.GetDirectoryName(Application.LocalUserAppDataPath);
List<string> results = new List<string>();
//StreamWriter w = new StreamWriter(#"e:\monitordetector.txt");
public Form1()
{
InitializeComponent();
//DetectScreenName();
label3.Text = "";
label4.Text = "";
label5.Text = "";
label7.Text = "";
button2.Enabled = false;
button3.Enabled = false;
filesdirectory = Path.Combine(path_exe, filesdirectory);
if (!Directory.Exists(filesdirectory))
{
Directory.CreateDirectory(filesdirectory);
}
else
{
if (IsDirectoryEmpty(filesdirectory) == false)
{
button3.Enabled = true;
}
}
}
public bool IsDirectoryEmpty(string path)
{
return !Directory.EnumerateFileSystemEntries(path).Any();
}
private string downloadhtml(string url)
{
backgroundWorker1.ReportProgress(0, "Downloading Main Url");
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
request.Proxy = null;
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader sr = new StreamReader(response.GetResponseStream());
string html = sr.ReadToEnd();
sr.Close();
response.Close();
StreamWriter w = new StreamWriter(path_exe + "\\page.html");
w.Write(html);
w.Close();
return html;
}
int Counter = 0;
int percentage = 0;
int total = 0;
int countfiletodownload = 0;
bool processStatus = false;
private void Parseanddownloadfiles()
{
downloadhtml(mainurl);
if (bgw.CancellationPending == false)
{
backgroundWorker1.ReportProgress(0, "Parsing Links");
HtmlAgilityPack.HtmlWeb hw = new HtmlAgilityPack.HtmlWeb();
HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
doc = hw.Load(path_exe + "\\page.html");
foreach (HtmlAgilityPack.HtmlNode link in doc.DocumentNode.SelectNodes("//a[#href]"))
{
string hrefValue = link.GetAttributeValue("href", string.Empty);
if (hrefValue.Contains("US"))
{
string url = "http://www.usgodae.org/ftp/outgoing/fnmoc/models/navgem_0.5/latest_data/" + hrefValue;
parsedlinks.Add(url);
if (bgw.CancellationPending == true)
return;
}
}
countfiletodownload = parsedlinks.Count;
total = parsedlinks.Count;
backgroundWorker1.ReportProgress(0, "Downloading Files");
processStatus = true;
for (int i = 0; i < parsedlinks.Count && bgw.CancellationPending == false; i++)
{
try
{
using (WebClient client = new WebClient())
{
sw.Start();
Uri uri = new Uri(parsedlinks[i]);
string filename = parsedlinks[i].Substring(71);
//client.DownloadFile(parsedlinks[i], filesdirectory + "\\" + filename);
client.DownloadFileAsync(uri, filesdirectory + "\\" + filename);
Counter += 1;
percentage = Counter * 100 / total;
string filenametoreport = filename.Substring(1);
countfiletodownload--;
backgroundWorker1.ReportProgress(percentage, filenametoreport);//countfiletodownload, filenametoreport);
}
}
catch (Exception err)
{
string error = err.ToString();
}
}
}
}
/*void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
// Calculate download speed and output it to labelSpeed.
if (label12.InvokeRequired)
{
label12.Invoke(new MethodInvoker(delegate
{
label12.Text = string.Format("{0} kb/s", (e.BytesReceived / 1024d / sw.Elapsed.TotalSeconds).ToString("0.00"));
}));
}
// Update the progressbar percentage only when the value is not the same.
if (progressBar1.InvokeRequired)
{
progressBar1.Invoke(new MethodInvoker(delegate
{
progressBar1.Value = e.ProgressPercentage;
}));
}
// Show the percentage on our label.
if (label13.InvokeRequired)
{
label13.Invoke(new MethodInvoker(delegate
{
label13.Text = e.ProgressPercentage.ToString() + "%";
}));
}
// Update the label with how much data have been downloaded so far and the total size of the file we are currently downloading
if (label14.InvokeRequired)
{
label14.Invoke(new MethodInvoker(delegate
{
label14.Text = string.Format("{0} MB's / {1} MB's",
(e.BytesReceived / 1024d / 1024d).ToString("0.00"),
(e.TotalBytesToReceive / 1024d / 1024d).ToString("0.00"));
}));
}
}*/
/*void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
// Reset the stopwatch.
sw.Reset();
if (e.Cancelled == true)
{
MessageBox.Show("Download has been canceled.");
}
else
{
//MessageBox.Show("Download completed!");
}
}*/
private void Form1_Load(object sender, EventArgs e)
{
}
BackgroundWorker bgw;
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
bgw = (BackgroundWorker)sender;
if (bgw.CancellationPending == true)
{
return;
}
else
{
Parseanddownloadfiles();
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.UserState.ToString() == "Downloading Main Url")
{
label3.Text = e.UserState.ToString();
}
if (e.UserState.ToString() == "Parsing Links")
{
label3.Text = e.UserState.ToString();
}
if (e.UserState.ToString() == "Downloading Files")
{
label7.Text = countfiletodownload.ToString();//parsedlinks.Count.ToString();
label3.Text = e.UserState.ToString();
}
if (processStatus == true)
{
if (e.UserState.ToString() != "Downloading Files")
{
label4.Text = e.UserState.ToString();
label7.Text = countfiletodownload.ToString();
progressBar1.Value = e.ProgressPercentage;
/*using (var bitmap = new Bitmap(this.Width, this.Height))
{
this.DrawToBitmap(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height));
bitmap.Save(#"e:\screens\ss.gif" + countscreenshots, System.Drawing.Imaging.ImageFormat.Gif);
countscreenshots += 1;
}*/
}
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
string fff = null;
}
label3.Text = "Operation Cancelled";
button1.Enabled = true;
}
private void button2_Click(object sender, EventArgs e)
{
label3.Text = "Cancelling Operation";
backgroundWorker1.CancelAsync();
button2.Enabled = false;
timer1.Stop();
stopwatch.Stop();
stopwatch.Reset();
}
private void button1_Click(object sender, EventArgs e)
{
label3.Text = "";
label4.Text = "";
label7.Text = "";
backgroundWorker1.RunWorkerAsync();
timer1.Start();
stopwatch.Start();
button1.Enabled = false;
button2.Enabled = true;
}
private void button3_Click(object sender, EventArgs e)
{
Process.Start(filesdirectory);
}
private void timer1_Tick(object sender, EventArgs e)
{
label5.Text = string.Format("{0:hh\\:mm\\:ss}", stopwatch.Elapsed);
}
}
}
for some reason the code inside the event make the whole application to not to be async. If I comment the code not to use it then it's async working fine.
I also want to use the client_DownloadProgressChanged that now is not in use to show and display the downloading infos.
Like speed time using progressBar info per current file download and info for overall progress download.
No*, you do not need to Invoke in a BackgroundWorker's ProgressChanged event. DoWork is run on a background thread but the thread that executes the code inside ProgressChanged (and the other event handlers on a backgroundworker) is done by the thread that created the backgroundworker, which should be the same as the thread that created the other ui controls and hence no Invokation required
*Having said this, pay close attention to the part where I said that BGW will run the ProgressChanged event using the thread that created the BGW. In most cases this will be the UI thread.. if you've used a thread other than the UI thread to create the BGW then YES, invokation would be required. Create the BGW on the UI thread along with all your other controls, if you want a simple life. For the rest of my advice I'll assume this is what you've done.
I wasn't able to understand exactly your problem, but remember that it's the UI thread that runs the event handler. If you send that thread off doing some long task or blocking operation as part of your efforts to update the labels in the UI then it will make the application seem hung. You must let the UI thread complete the code in the event handler as soon as possible. If it will need to access data that is from a long or blocking operation, either have the DoWork calculate the data before it raises its progress, or use another method to avoid blocking the UI thread, like async task pattern
Note that Invoke and blocking the ui are completely different things. Windows controls may only be accessed by the thread they were created with. If another thread wants to access the control, it should use Invoke to cause the ui thread to do the work instead. This is a very different thing to the notion of not jamming up your ui by using the UI thread to read 50 gigabytes from a slow server and not using something that lets it quickly get back to its job of processing window messages and keeping the app responsive
I'm trying to fill my ProgressBar inside a while loop.
But somehow the Progressbar value wont grow.
I want the code to open a textfile and to post a request for each row in it.
My code for this:
public void registerAccounts()
{
const Int32 BufferSize = 128;
using (var fileStream = File.OpenRead(nsTextBox3.Text))
using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize))
{
String line;
while ((line = streamReader.ReadLine()) != null)
{
var request = (HttpWebRequest)WebRequest.Create("http://registration.zwinky.com/registration/register.jhtml");
var postData = "form=reg&success=http://profile.zwinky.com/zwinkyprofile/main.jhtml?username=&tp=&user.partnerId=ZZzer000&source=zwinky&design=classic&ad=/&user.userName=" + line + "&user.password=" + nsTextBox1.Text + "&user.passwordConfirm=" + nsTextBox1.Text + "&user.secretQId=1&user.secretQAnswer=" + line + "&user.birthdayMonth=1&user.birthdayDay=1&user.birthdayYear=1996&user.gender=M&user.emailAddr=" + nsTextBox2.Text + "&emailAddrConfirm=" + nsTextBox2.Text + "&x=42&y=51&partnerCode=/";
var data = Encoding.ASCII.GetBytes(postData);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;
using (var stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
var response = (HttpWebResponse)request.GetResponse();
var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
}
}
}
How would I let the ProgressBar in my GUI grow now?
I thought about counting the lines inside the text document like this:
var lineCount = File.ReadLines(nsTextBox3.Text).Count();
and then run a for loop in it. But it wont grow sadly.
Would appreciate any kind of help.
You appear to be running this method on the UI thread of your application. This thread is responsible for updating the UI, so while it's busy with the loop it can't do that. What likely happens is that once your loop completes the progress bar is updated straight to 100% and you don't see it.
Look at using a Background Worker.
It takes care of the above concerns by doing all of the threading and callbacks for you in a very simple manner.
Example taken straight from the MSDN article:
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace SL_BackgroundWorker_CS
{
public partial class Page : UserControl
{
private BackgroundWorker bw = new BackgroundWorker();
public Page()
{
InitializeComponent();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
private void buttonStart_Click(object sender, RoutedEventArgs e)
{
if (bw.IsBusy != true)
{
bw.RunWorkerAsync();
}
}
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
if (bw.WorkerSupportsCancellation == true)
{
bw.CancelAsync();
}
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500);
worker.ReportProgress((i * 10));
}
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
this.tbProgress.Text = "Canceled!";
}
else if (!(e.Error == null))
{
this.tbProgress.Text = ("Error: " + e.Error.Message);
}
else
{
this.tbProgress.Text = "Done!";
}
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.tbProgress.Text = (e.ProgressPercentage.ToString() + "%");
}
}
}
Put your loop in DoWork and your progress bar in ProgressChanged. Simples!
you can try this,this is not perfect but worked
int linecount=0;
while ((line = streamReader.ReadLine()) != null)
{
linecount=linecount+1;
}
int number=0;
while ((line = streamReader.ReadLine()) != null)
{
number=number+1;
// what is yor work
progressBar1.Value = i * progressBar1.Maximum / linecount; //show process bar counts
LabelTotal.Text = i.ToString() + " of " + linecount; //show number of count in lable
int presentage = (i * 100) / linecount;
LabelPresentage.Text = presentage.ToString() + " %"; //show precentage in lable
Application.DoEvents(); //keep form active in every loop
}
In my application, when a user clicks on a menu item, they are prompted to select a file which I then use a background worker to load and parse with progress being reported to a progress bar during this operation. when the work is complete, I start a new window which i pass the parsed data to. for some reason, the new window is getting opened before the data is parsed and null arguments are passed instead of the data.
my Click Event:
private void CIRCUIT_INSTALATION_SCHEDULED_Click(object sender, RoutedEventArgs e)
{
//prevents users from selecting other menu items
disableAll();
System.Windows.Forms.OpenFileDialog mainExcelFileDialog = new System.Windows.Forms.OpenFileDialog();
mainExcelFileDialog.InitialDirectory = System.Configuration.ConfigurationManager.AppSettings["MainWorkbookDirectory"];
mainExcelFileDialog.Title = "Main Excel File";
mainExcelFileDialog.ShowDialog();
string mainPath = mainExcelFileDialog.FileName;
this.Cursor = System.Windows.Input.Cursors.Wait;
BackgroundWorker cisWorker = new BackgroundWorker();
cisWorker.DoWork += cisWorker_DoWork;
cisWorker.ProgressChanged+=cisWorker_ProgressChanged;
cisWorker.RunWorkerCompleted += cisWorker_RunWorkerCompleted;
cisWorker.RunWorkerAsync(mainPath);
}
my Do Work:
void cisWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
using (FileStream fs = new FileStream(e.Argument.ToString() , FileMode.Open))
{
//copy filestream to memorystream chunk by chunk, reporting progress.
using (MemoryStream ms = new MemoryStream())
{
byte[] buffer = new byte[32768];
int read;
int steps = Convert.ToInt32(fs.Length / buffer.Length);
int i = 0;
while ((read = fs.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
i++;
var progress = new Decimal(i) / new Decimal(steps);
var outOf = Math.Round(progress * 50);
worker.ReportProgress(Convert.ToInt32(outOf));
}
worker.ReportProgress(50);
ms.Position = 0;
//load excel workbook dataset from memorystream
using (var xlr = Excel.ExcelReaderFactory.CreateOpenXmlReader(ms))
{
xlr.IsFirstRowAsColumnNames = true;
mainWorkbook = xlr.AsDataSet();
worker.ReportProgress(100);
}
}
}
}
my on complete:
void cisWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.Cursor = System.Windows.Input.Cursors.Arrow;
CircuitInstallationScheduledWindow CIS_Window = new CircuitInstallationScheduledWindow();
CIS_Window.Show();
CIS_Window.Closed += CIS_Window_Closed;
}
EDIT:
I added the mainPath argument to the cisWorker.runWorkerAsync() method. the problem persists unfortunately.
Basically you should check for your data not being null, i.e that no error occurred during DoWork
void cisWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error == null)
{
/* Your mainWorkbook shouldn't be null */
this.Cursor = System.Windows.Input.Cursors.Arrow;
CircuitInstallationScheduledWindow CIS_Window = new CircuitInstallationScheduledWindow();
CIS_Window.Show();
CIS_Window.Closed += CIS_Window_Closed;
}
else
{
// Handle error
}
}
Henk Holterman was right. I was getting an error because I had not set cisWorker.WorkerReportsProgress = true; I still don't know why the error wasn't breaking the debugger since it wasn't being handled, but I did a print statement in my Completed method which helped me track down the culprit.
void cisWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine(e.Error);
this.Cursor = System.Windows.Input.Cursors.Arrow;
CircuitInstallationScheduledWindow CIS_Window = new CircuitInstallationScheduledWindow(mainWorkbook, abfsDatabase);
CIS_Window.Show();
CIS_Window.Closed += CIS_Window_Closed;
}
It has been surprisingly hard to find a code example of downloading multiple files using the webclient class asynchronous method, but downloading one at a time.
How can I initiate a async download, but wait until the first is finished until the second, etc. Basically a que.
(note I do not want to use the sync method, because of the increased functionality of the async method.)
The below code starts all my downloads at once. (the progress bar is all over the place)
private void downloadFile(string url)
{
WebClient client = new WebClient();
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
// Starts the download
btnGetDownload.Text = "Downloading...";
btnGetDownload.Enabled = false;
progressBar1.Visible = true;
lblFileName.Text = url;
lblFileName.Visible = true;
string FileName = url.Substring(url.LastIndexOf("/") + 1,
(url.Length - url.LastIndexOf("/") - 1));
client.DownloadFileAsync(new Uri(url), "C:\\Test4\\" + FileName);
}
void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
}
void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
double bytesIn = double.Parse(e.BytesReceived.ToString());
double totalBytes = double.Parse(e.TotalBytesToReceive.ToString());
double percentage = bytesIn / totalBytes * 100;
progressBar1.Value = int.Parse(Math.Truncate(percentage).ToString());
}
What I have done is populate a Queue containing all my urls, then I download each item in the queue. When there are no items left, I can then process all the items. I've mocked some code up below. Keep in mind, the code below is for downloading strings and not files. It shouldn't be too difficult to modify the below code.
private Queue<string> _items = new Queue<string>();
private List<string> _results = new List<string>();
private void PopulateItemsQueue()
{
_items.Enqueue("some_url_here");
_items.Enqueue("perhaps_another_here");
_items.Enqueue("and_a_third_item_as_well");
DownloadItem();
}
private void DownloadItem()
{
if (_items.Any())
{
var nextItem = _items.Dequeue();
var webClient = new WebClient();
webClient.DownloadStringCompleted += OnGetDownloadedStringCompleted;
webClient.DownloadStringAsync(new Uri(nextItem));
return;
}
ProcessResults(_results);
}
private void OnGetDownloadedStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null && !string.IsNullOrEmpty(e.Result))
{
// do something with e.Result string.
_results.Add(e.Result);
}
DownloadItem();
}
Edit:
I've modified your code to use a Queue. Not entirely sure how you wanted progress to work. I'm sure if you wanted the progress to cater for all downloads, then you could store the item count in the 'PopulateItemsQueue()' method and use that field in the progress changed method.
private Queue<string> _downloadUrls = new Queue<string>();
private void downloadFile(IEnumerable<string> urls)
{
foreach (var url in urls)
{
_downloadUrls.Enqueue(url);
}
// Starts the download
btnGetDownload.Text = "Downloading...";
btnGetDownload.Enabled = false;
progressBar1.Visible = true;
lblFileName.Visible = true;
DownloadFile();
}
private void DownloadFile()
{
if (_downloadUrls.Any())
{
WebClient client = new WebClient();
client.DownloadProgressChanged += client_DownloadProgressChanged;
client.DownloadFileCompleted += client_DownloadFileCompleted;
var url = _downloadUrls.Dequeue();
string FileName = url.Substring(url.LastIndexOf("/") + 1,
(url.Length - url.LastIndexOf("/") - 1));
client.DownloadFileAsync(new Uri(url), "C:\\Test4\\" + FileName);
lblFileName.Text = url;
return;
}
// End of the download
btnGetDownload.Text = "Download Complete";
}
private void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
if (e.Error != null)
{
// handle error scenario
throw e.Error;
}
if (e.Cancelled)
{
// handle cancelled scenario
}
DownloadFile();
}
void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
double bytesIn = double.Parse(e.BytesReceived.ToString());
double totalBytes = double.Parse(e.TotalBytesToReceive.ToString());
double percentage = bytesIn / totalBytes * 100;
progressBar1.Value = int.Parse(Math.Truncate(percentage).ToString());
}
I am struggling to understand where the issue is. If you only call the async method for the first file, won't it only download that file? Why not use the client_downlaodFileCompleted event to initiate the next file download based on some value passed by AsyncCompletedEvents, or maintain a list of downloaded files as a static variable and have client_DownloadFileCompleted iterate tht list to find the next file to download.
Hope that helps but please post more information if I have miunderstood your question.
I would make a new method, for example named getUrlFromQueue which returns me a new url from the queue (collection or array) and deletes it.. then it calls downloadFile(url) - and in client_DownloadFileCompleted I call getUrlFromQueue again.