Progress bar in uploading Xml file - c#

i want to use a background thread for the process of loading the XML data, possibly with a progress bar to let the user know that the application is actively doing something.
i have written this code through searching the net.
i want to load a XML tree in treeview on winform when a user cliks a Browse button.
In case of a large XML file the winform freezes.So to let the user know that in background the work is going on i want to add a progress bar.i have used a background worker here.
But it is raising an exception of System.ArgumentException showing this message "The URL cannot be empty.\r\nParameter name: url" on xmlDocument.Load(txtFileName.Text); this line.
My xml file is in correct format and is at the proper location where i selected.
But i am unable to find the cause of this exception.
Can you please help out or tell me the correction in my code?
Thanks....
private void btnBrowse_Click(object sender,EventArgs e)
{
bgWorker1.RunWorkerAsync();
StripProgressBar.Value = 0;
toolStripStatusLabel1.Text = "Browsing for a Xml file";
if (open.ShowDialog(this) == DialogResult.OK)
{
txtFileName.Text = open.FileName;
initiatingTree(open.FileName); //this variable gives the name of selected file
}
while (this.bgWorker1.IsBusy)
{
StripProgressBar.Increment(1);
// Keep UI messages moving, so the form remains
// responsive during the asynchronous operation.
Application.DoEvents();
}
}//Browse button
private void bgWorker1_DoWork(object sender, DoWorkEventArgs e)
{
xmlDocument = new XmlDocument();
Thread.Sleep(5000);
xmlDocument.Load(txtFileName.Text);
btnBrowse.Enabled = false;
}
private void bgworker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Set progress bar to 100% in case it's not already there.
StripProgressBar.Value = 100;
if (e.Error == null)
{
MessageBox.Show(xmlDocument.InnerXml, "Download Complete");
}
else
{
MessageBox.Show("Failed to download file");
}
// Enable the Browse button and reset the progress bar.
this.btnBrowse.Enabled = true;
StripProgressBar.Value = 0;
toolStripStatusLabel1.Text = "work finished processing request.";
}//workerCompleted

You're starting the asynchronous process immediately when the user clicks "Browse", by calling
bgWorker1.RunWorkerAsync();
This calls the DoWork method of your background worker, which sleeps for 5 seconds, and pulls the value from txtFileName.Text whether or not the user has completed their entry in the FileOpenDialog.
You'd be better off moving the byWorker1.RunWorkerAsync() (and the busy waiting) into the if (open.ShowDialog(this) == DialogResult.OK) block.
private void btnBrowse_Click(object sender,EventArgs e)
{
StripProgressBar.Value = 0;
toolStripStatusLabel1.Text = "Browsing for a Xml file";
if (open.ShowDialog(this) == DialogResult.OK)
{
txtFileName.Text = open.FileName;
initiatingTree(open.FileName);
bgWorker1.RunWorkerAsync();
while (this.bgWorker1.IsBusy)
{
StripProgressBar.Increment(1);
// Keep UI messages moving, so the form remains
// responsive during the asynchronous operation.
Application.DoEvents();
}
}
}
For these kinds of problems, it can be helpful to put a breakpoint right where the file is going to get loaded, and see what the value is when that happens... you might notice that it's getting called with an empty string.
You might also consider the version of RunWorkerAsync that takes a parameter; you could pass the file in that way, instead of trying to read it asynchronously from the textbox.
And personally, I wouldn't use a loop that calls Application.DoEvents(); instead I'd return control back to the UI thread and then Invoke() onto it from the asynchronous thread to effect the progressbar updates.

When the method bgWorker1.RunWorkerAsync(); is called the event DoWork is fired.
Because the method is called in the beginning of the application, the file name text box is empty.
I hope you've understood.

Related

Check multiple checkbox.checked state inside a do while cycle

I asked in a previous question how to "Threading 2 forms to use simultaneously C#".
I realize now that I was not explicit enough and was asking the wrong question.
Here is my scenario:
I have some data, that I receive from a local server, that I need to write to a file.
This data is being sent at a constant time rate that I cant control.
What I would like to do is to have one winform for the initial setup of the tcp stream and then click on a button to start reading the tcp stream and write it to a file, and at the same time launch another winform with multiple check-boxes that I need to check the checked state and add that info simultaneously to the same file.
This processing is to be stopped when a different button is pressed, closing the stream, the file and the second winform. (this button location is not specifically mandatory to any of the winforms).
Because of this cancel button (and before I tried to implement the 2nd form) I used a background worker to be able to asynchronously cancel the do while loop used to read the stream and write the file.
private void bRecord_Click(object sender, EventArgs e)
{
System.IO.StreamWriter file = new System.IO.StreamWriter(AppDomain.CurrentDomain.BaseDirectory + DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss") + ".xml", true);
data_feed = client.GetStream();
data_write = new StreamWriter(data_feed);
data_write.Write("<SEND_DATA/>\r\n");
data_write.Flush();
exit_state = false;
string behavior = null;
//code to launch form2 with the checkboxes
//...
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler((state, args) =>
{
do
{
int var = data_feed.ReadByte();
if (var != -1)
{
data_in += (char)var;
if (data_in.IndexOf("\r\n") != -1)
{
//code to check the checkboxes state in form2
//if (form2.checkBox1.Checked) behavior = form2.checkBox1.Text;
//if (form2.checkBoxn.Checked) behavior = form2.checkBoxn.Text;
file.WriteLine(data_in + behavior);
data_in = "";
}
}
}
while (exit_state == false);
});
worker.RunWorkerAsync();
}
private void bStop_Click(object sender, EventArgs e)
{
exit_state = true;
worker.CancelAsync();
}
I hope I've been clearer now.
I not experienced in event programming and just started in C# so please try to provide some simple examples in the answers if possible.
At first would it be enough to use one Winform? Disable all checkboxes, click a button which enables the checkboxes and start reading the tcpstream? If you need two Forms for other reasons let me know, but i think this isn't needed from what i can see in your question.
Then i would suggest you to use the Task Library from .Net. This is the "modern" way to handle multithreading. BackgroundWorker is kind of old school. If you just able to run on .Net 2.0 you have to use BackgroundWorker, but don't seem to be the case (example follows).
Further if you want to cancel a BackgroundWorker operation this isn't only call CancelAsync();. You also need to handle the e.Cancelled flag.
backgroundWorker.WorkerSupportsCancellation = true;
private void CancelBW()
{
backgroundWorker.CancelAsync();
}
private void backgroundWorker_DoWork += ((sender, args)
{
//Handle the cancellation (in your case do this in your loop for sure)
if (e.Cancelled) //Flag is true if someone call backgroundWorker.CancelAsync();
return;
//Do your stuff.
});
There is no common way to directly cancel the backgroundWorker
operation. You always need to handle this.
Now let's change your code to the modern TAP-Pattern and make some stuff you want to have.
private void MyForm : Form
{
private CancellationTokenSource ct;
public MyForm()
{
InitializeComponent();
checkbox1.Enable = false;
//Disable all checkboxes here.
ct = new CancellationTokenSource();
}
//Event if someone click your start button
private void buttonStart_Click(object sender, EventArgs e)
{
//Enable all checkboxes here
//This will be called if we get some progress from tcp
var progress = new Progress<string>(value =>
{
//check the behaviour of the checkboxes and write to file
file.WriteLine(value + behavior);
});
Task.Factory.StartNew(() => ListenToTcp(ct, progress as IProgress<string)); //starts the tcp listening async
}
//Event if someone click your stop button
private void buttonStop_Click(object sender, EventArgs e)
{
ct.Cancel();
//Disable all checkboxes (better make a method for this :D)
}
private void ListenToTcp(CancellationToken ct, IProgess<string> progress)
{
do
{
if (ct.IsCancellationRequested)
return;
int temp = data_feed.ReadByte(); //replaced var => temp because var is keyword
if (temp != -1)
{
data_in += (char)temp;
if (data_in.IndexOf("\r\n") != -1)
{
if (progress != null)
progress.Report(data_in); //Report the tcp-data to form thread
data_in = string.empty;
}
}
while (exit_state == false);
}
}
This snippet should do the trick. I don't test it so some syntax error maybe occur :P, but the principle will work.
The most important part is that you are not allowed to access gui
components in another thread then gui thread. You tried to access the
checkboxes within your BackgroundWorker DoWork which is no possible
and throw an exception.
So I use a Progress-Object to reuse the data we get in the Tcp-Stream, back to the Main-Thread. There we can access the checkboxes, build our string and write it to the file. More about BackgroundWorker vs. Task and the Progress behaviour you can find here.
Let me know if you have any further questions.

thread call fires inconsistently

I am a beginner in high level programming languages. I am trying to make an WForms app for a serial port , im using VS 2010 C#
I get the following error:
Cross-thread operation not valid: Control 'rtxtDataArea' accessed from a thread other than the thread it was created on.
This happens here:
private void ComPort_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
recievedData = ComPort.ReadExisting(); //read all available data in the receiving buffer.
// Show in the terminal window
rtxtDataArea.ForeColor = Color.Green; // error ,
rtxtDataArea.AppendText(recievedData + "\n");
}
I have tried to change the color of a textbox when I receive some data.
It fires that cross thread error.
The question is why it does not fire the same error here, when I try to change the color of a label?
private void btnConnect_Click(object sender, EventArgs e)
{
if (ComPort.IsOpen)
{
disconnect();
}
else
{
connect();
rdText.ForeColor = Color.Blue;//ok, it works
}
}
; this works ; the first does not.
Why? Is not the ComPort_DataReceived_1 the same nature as btnConnect_Click ?
Or what is the reason?
I have read a lot about threads, but I understood nothing I can use, Can someone give an intuitive explanation ?
In winforms there is only one thread that may change anything on the UI like enable buttons, change text boxes, etc. Usually this is the UI thread. Quite often this is the only thread you have.
However, if you start a new thread, this thread might want to change the UI. This happens especially if this new thread fires an event that is received by your form.
Whenever you see the message accessed from a thread other than the thread it was created on, you can be almost certain this is the case.
The most easy solution to solve this is using the functions Control.IsInvokeRequired and Control.Invoke. The pattern to do this is as follows. The following function updates myButton on myForm
private void UpdateMyButton (MyType myParameter)
{
if (myButton.InvokeRequired)
{ // this is not the thread that created the button to update
this.Invoke(new MethodInvoker( () => this.UpdateMyButton(myParameter)));
// this will let the UI thread call this function with the same parameter.
}
else
{ // Now called by the UI thread: this thread may change myButton
myButton.Enabled = myParameter.ShouldButtonEnable;
myButton.Text = myParameter.ButtonText;
}
}
By the way, if you have to update several controls on your form you ought to check InvokeRequired for each of these controls. However, since they are usually created by the same UI thread it is sufficient to check for this.InvokeRequired.
Control.Invoke returns after the invoke is completed, so after all items are updated. Upon return of Invoke you can use the result of UpdateMyButton.
If you don't want your non-ui thread to wait for completion of UpdateMyButton, consider the use of Control.BeginInvoke: "hey UI thread, whenever you've got time, can you UpdateMyButton for me. Of course in that case you can't use the results of UpdateMyButton
Because "DataReceived" runs on another thread and not UI thread. You must use Invoke for that :
private void ComPort_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
recievedData = ComPort.ReadExisting(); //read all available data in the receiving buffer.
if (InvokeRequired)
{
// If not on UI thread, then invoke
Invoke(new MethodInvoker(() =>
{
// Show in the terminal window
rtxtDataArea.ForeColor = Color.Green; // error ,
rtxtDataArea.AppendText(recievedData + "\n");
}));
}
else
{
// On UI thread, invoke not needed
// Show in the terminal window
rtxtDataArea.ForeColor = Color.Green; // error ,
rtxtDataArea.AppendText(recievedData + "\n");
}
}

Backgroundworker, not running the progressbar

How can I fix this issue ?
I am expecting the progressbar to load during process untill process it is done
Here is my code:
private void btnProcess_Click(object sender, EventArgs e)
{
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//start transaction
DoTransaction();
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
My transaction function:
private void DoTransaction()
{
string pathIdentifier;
pathIdentifier = func.checkthePathFile();
if (pathIdentifier == null)
{
MessageBox.Show("Path has no yet been specified!");
}
else
{
//create xml base from user inputs
XElement transactXML = new XElement("Transaction",
new XElement("CardNumber", txtCardNum.Text.Trim()),
new XElement("ExpireDate", txtExpDate.Text.Trim()),
new XElement("Cardtype", txtCardType.Text.Trim())
);
//save xml to a file
transactXML.Save(pathIdentifier + "/sample.xml");
}
}
How is the runtime supposed to know how far along your process is?
You need to tell it by calling backgroundWorker.ReportProgress from the background operation. No magic here.
MSDN: http://msdn.microsoft.com/en-us/library/ka89zff4.aspx
Break down your process into meaningful chunks and ReportProgress whenever it makes sense to do so.
public void DoTransaction()
{
part1();
backgroundWorker.ReportProgress(25);
part2();
backgroundWorker.ReportProgress(50);
part3();
backgroundWorker.ReportProgress(75);
part4();
backgroundWorker.ReportProgress(100);
}
Edit Based on Posting of Transaction() function
If you are not confident in writing multithreaded programs, then do not attempt to write multithreaded programs, even with the help of a BackgroundWorker which tries to abstract some of those details away from you.
A few issues:
Your provided Transaction() method attempts to launch a MessageBox and read the Text property of various controls from the background thread. This is going to cause problems as the runtime typically throws an Exception when UI elements are accessed from a thread other than the one which created them.
If you really want to do the XML saving in the BackgroundWorker, you should validate the filename and directory, and save the Text properties to an intermediate object before setting up the BackgroundWorker and calling RunWorkerAsync.
Furthermore, in my opinion, your Transaction method is not going to be time intensive enough to truly warrant a background thread. Even a relatively old PC will be able to create and save a 15 element XML file faster than you can blink. The runtime will probably waste more time marshalling data between the threads than it would to simply write the file out to disk. Just do your work in the button click event handler.
needs some reference to the BackgroundWorker instance.pass the reference to the class when instantiating it.
instantiate like this
BackgroundWorker worker = sender as BackgroundWorker;
then call like this
`worker.ReportProgress(...)`

changing visibility not working

I have a Label and PictureBox element that in designer i set visibility as false.
now i try this :
private void openExcelButton_Click(object sender, EventArgs e)
{
openExcelDialog.Filter = "Excel files|*.xls;*.xlsx;*.csv";
DialogResult result = openExcelDialog.ShowDialog();
if (result == DialogResult.OK) // Test result.
{
LoadingGIF.Visible = true;
LoadingLabel.Text = "Loading...";
LoadingLabel.Visible = true;
string file = openExcelDialog.FileName;
//more code
LoadingGIF.Visible = false;
LoadingLabel.Text = "Uploading Finished!";
}
}
Now when pressing the button and choosing a file nothing happens untill i finish the code in the //more code section and then the label changes.
Why does this happen?
The reason this happens is because your main thread is becoming non-responsive and not allowing the changes to happen in a sequential order. I had a very similar issue on a project a year ago. The suggested solution by MS is to use a background worker to open the file and manipulate it so the primary thread does not become non-responsive. Microsoft has a fairly decent example of how to use a background worker here: http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx

How does the BackgroundWorker work exactly?

I have written this code for a Message Receiving class that uses a backgroundworker to check for new files in a directory (the files are SMS messages received from users that are updating continuously). If the directory is not empty, I send an acknowledgement message to every new SMS and start the worker again.
public MessageReceiving()
{
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Result == true)
{
SendAcknowledgement();
if(!bw.IsBusy)
bw.RunWorkerAsync();
}
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
bool flag = false;
while (flag.Equals(false))
{
string path = #"C:\SMS";
if (Directory.GetFiles(path).Length > 0)
{
e.Result = true;
flag = true;
}
else
{
e.Result = false;
}
}
}
I initialize the worker from the main thread once -
MessageReceiving mr = new MessageReceiving();
mr.bw.RunWorkerAsync();
I did all this to allow me to send messages synchronously to users - As soon as a user send an SMS, I send him an ACK. The problem is that the user is getting multiple ACKs even on sending one SMS - why is this happening? I have thought of every possibility, but to no avail!
Consider what happens when this starts:
You start running bw_DoWork
That then tight-loops (not a good idea to start with) until it finds a file
The background worker completes, and you send an acknowledgement
You then immediately run the background worker again... which will find the file again unless you've deleted the file during SendAcknowledgement. Have you?
I suspect what you really want instead of any of this is a FileSystemWatcher by the way. Also note that just because a file is present doesn't mean that it's finished being written to yet, or that you can read it.
Additionally, your tight loop can be made a lot simpler:
void bw_DoWork(object sender, DoWorkEventArgs e)
{
string path = #"C:\SMS";
while (!e.Result)
{
e.Result = Directory.GetFiles(path).Any();
}
}
and I would likewise change
if (e.Result == true)
to
if (e.Result)
(Assuming e.Result is typed as a bool; if it's not, your current code has other problems.)
It depends on code not shown.
You need at most 1 thread scanning the files. Then somewhere during the processing you have to remove or rename the files. In your case this should happen in SendAcknowledgement, and the Bgw should not be restarted before it all replies have been sent.
It would be better to use a rename the files early and push them in a queue. Or process them directly after finding a file inside DoWork. SendAcknowledgement(fileName) looks more logical.
Currently your SendAcknowledgement() runs in the main thread, that may not be what you want.

Categories