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
Related
Using a C# Winform as GUI to run my Python script in the background. Currently am applying a backgroundworker to handle it in another thread and keep the GUI responsive; however, the progress bar only seems to update when the process is finished. Stays at Value=0 and then changes to Value=100 once the process terminates; would much prefer have it increase as python executes the background process so I know it's still alive and working..
My understanding is that blocking should not be occurring because the backgroundworker is moving the process to a separate thread, so I'm not too sure what could be stopping it from updating.
Here's the relevant sections of my C# code:
public Form1()
{
InitializeComponent();
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
}
----
private void button_test2_Click(object sender, EventArgs e) // this is the active button
{
if (backgroundWorker1.IsBusy != true)
{
backgroundWorker1.RunWorkerAsync();
}
}
----
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
if (backgroundWorker1.CancellationPending == true)
{
e.Cancel = true;
//break;
}
else
{
// Cutting out some stuff here for brevity. Just assigns arg1-4 based on inputs from the GUI, nothing crazy
// Python script to run
process.StartInfo.FileName = #"C:\blablabla\python.exe";
string script = #"C:\paths\pythonscriptgoeshere.py";
process.StartInfo.Arguments = $"\"{script}\" \"{arg1}\" \"{arg2}\" \"{arg3}\" \"{arg4}\"";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
while (!process.StandardOutput.EndOfStream)
{
bool ItsAnInt = Int32.TryParse(process.StandardOutput.ReadLine(),out int PyOutput);
if (ItsAnInt) // I know this part is ugly lol - just making sure only numbers/percentages are being passed
{
backgroundWorker1.ReportProgress(PyOutput);
// PyOut is just a percentage (0-100) of how many iterations it has completed out of the total
}
}
//string stderrx = process.StandardError.ReadToEnd();
//string stdoutx = process.StandardOutput.ReadToEnd();
//MessageBox.Show($"Output: {stdoutx}\nError: {stderrx}");
}
---
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
Not quite sure why the progressbar1.Value isn't being updated from backgroundWorker1_ProgressChanged since it seems to be called while the process is actively running. Anyway, sorry for the long post and thanks for any help.
Cheers!
EDIT: Thank you #Jimi for helping me figure this out in the comments. The issue was that the UI was flooded with request since the test data set I was using was computed faster than the progressbar could update so it essentially didn't get a chance to breathe and catch up. Solution was to add in a Thread.Sleep(1) every handful of iterations to let it update.
EDIT 2: The plot thickens! Turns out that the progress bar would give me updates like I wanted, but wouldn't start the processes of updating until the background process was already done. Turns out what I needed to do was make a second background worker as well as an external python script that would read outputs and then report them to the bar. Not the best solution I'm sure but it seemed to do the trick for me :-)
I've spent 4 hours on this and totally failed.
I know that i need to use BackgroundWorker but all the tutorials refer to running a progress script on the actual form you are running the worker on.
I have a large datagrid, which the user can use a check box to "select all" and then press "UPDATE ALL"
This updates every grid with a bunch of options they choose.
For some users this may be 5 records which is nothing, but some might update 200 records with 5 options which takes about... 10-15 secs to iterate through them.
I have tried so many variations of running BGworker which loads a FrmLoading.Showdialog
Or trying to have BGworker "do work" running the code and then the main thread having the FrmLoading.Show()
However nothing is working.
If i have the update code in the background worker, it fails because the datagrid and everything is in a different thread.
The other way round, and it just hangs on FrmLoading.Show()
Any advice would be great.
I just can't seem to get my head around how to get this working for what seems to be an easy idea!
Current Update Code:
foreach (DataGridViewRow rowx in dataGridpatients.Rows)
{
//MessageBox.Show(Convert.ToBoolean(rowx.Cells["clnselected"].Value).ToString());
if (Convert.ToBoolean(rowx.Cells["clnselected"].Value) == true)
{
//if cycle has a value.
if (cmbcycle.SelectedIndex != -1)
{
rowx.Cells["clncycletype"].Value = cycle;
rowx.Cells["clnpackscollect"].Value = packs;
}
//if location has a value
if (cmblocation.SelectedIndex != -1)
{
location = Convert.ToInt32(cmblocation.SelectedValue);
rowx.Cells["clnlocation1"].Value = location;
}
if (cmbsize.SelectedIndex != -1)
{
size = Convert.ToInt32(cmbsize.SelectedValue);
rowx.Cells["clnpacksize"].Value = size;
}
if (chkDelivery.Checked == true)
{
rowx.Cells["clnDelivery"].Value = true;
}
if (chkSignSheet.Checked == true)
{
rowx.Cells["clnSigningSheet"].Value = true;
}
}
countupdated++;
}
foreach (DataGridViewRow row in dataGridpatients.Rows)
{
row.Cells["clnselected"].Value = false;
row.DefaultCellStyle.BackColor = Color.White;
}
cmbsize.SelectedIndex = -1;
cmblocation.SelectedIndex = -1;
cmbcycle.SelectedIndex = -1;
chkDelivery.Checked = false;
chkSignSheet.Checked = false;
#countupdated++;
I also have #CountSelected.
What i want to do is run this code above but have a popup overlay (dialog) with my logo + "Updating X%"
Where X = countupdated/countselected * 100
I now know i need to use the background worker and invoke for the above, but literally have no idea regarding how to invoke the grid and go from there.
I understand i need to invoke the variables I'm using
(eg. cmbcycle.SelectedIndex)
I know iterating through 150 records and updating individual cells is probably wrong,
My other option is creating a datatable from "selected" cells on that datatable
then Running the update via SQL instead of iterating through a bound table.
Then after the SQL i can re-create the table which will now have the new cell values updated in it?
Would that be a more appropriate way to do it?
Max rows on this table would be 200. Average ~70 so we are never talking 500 or 1000
EDIT:
So the checked answer works to run the background worker and refer to the controls on the form.
The issue is that if i do this:
backgroundWorker1.RunWorkerAsync();
splashy.ShowDialog();
Then the splash screen pops up after the background worker ends
If i do this:
splashy.ShowDialog();
backgroundWorker1.RunWorkerAsync();
Then the popup semi-forms and hangs until the end of the background worker, at which time it closes
because of the RunWorkerCompleted event.
EDIT:
I have no updated the code in DoWork and used Invokes to refer to the controls.
This works and the code runs fine.
I now need a popup ot appear showing the progress through the updates.
splashy.InvokeBy(() =>
{
splashy.Show();
});
backgroundWorker1.RunWorkerAsync();
Does not work. It causes the popup but freeze
splashy.ShowDialog();
backgroundWorker1.RunWorkerAsync();
Allows the Dialog to show (not 'frozen' and distorted) However the Lab (lblprogress) does not update.
This is because the form never get to the RunWorker method, it is stuck at ShowDialog.
It would be a good idea to make modifications on your DataSource itself and then bind it with the DataGridView.
But as from your existing code if you want to access your controls/UI to update or change values from BackgroundWorker.RunWorkerAsync method or any other Thread call for that matter, you can create an extension method to .Invoke() the controls like:
public static class MyExtensions
{
public static void InvokeBy(this Control ctl, MethodInvoker method)
{
if (ctl.InvokeRequired)
ctl.Invoke(method);
else method();
}
}
Keep this static class under the same Namespace as your main class for convenience.
Thus this code:
foreach (DataGridViewRow rowx in dataGridpatients.Rows)
{
//your codes
}
Will become:
dataGridpatients.InvokeBy(() =>
{
foreach (DataGridViewRow rowx in dataGridpatients.Rows)
{
//your codes
}
});
Similarly,
if (cmbcycle.SelectedIndex != -1)
{
//your codes
}
Will become:
cmbcycle.InvokeBy(() =>
{
if (cmbcycle.SelectedIndex != -1)
{
//your codes
}
});
This way you van safely access your controls, while keeping your UI responsive at the same time. Update your Popup Status UI the same way!
This answer is based around o_O's answer.
The main issue is that i wanted the UI to actually update and the background worker to supply the splash.
Instead of running all the 'hard code' in the BGW, i left it in the original thread, but called a BGW to display a popup Dialog form.
so at the start of the "hard code" I used:
backgroundWorker1.RunWorkerAsync();
This called:
FrmSplash splashy;
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
splashy = new FrmSplash();
splashy.ShowDialog();
}
In order to remove the dialog box, at the end of the code in the GUI thread, i used:
splashy.InvokeBy(() =>
{
splashy.Close();
}
);
backgroundWorker1.CancelAsync();
Which uses the extension supplied by O_o
public static class MyExtensions
{
public static void InvokeBy(this Control ctl, MethodInvoker method)
{
if (ctl.InvokeRequired)
ctl.Invoke(method);
else method();
}
}
I have also built a label update into splashy
So i could call
splashy.InvokeBy(() =>
{
splashy.SetStatus(countupdated.ToString());
}
);
As i iterated through the datagridview rows. This updated the label on the splash screen :)
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.
I have here a long method that takes a little while to execute. I would like to keep the user entertained so I created a progress bar and a label. What I would like is for that label to change while the system executes the progress. Ive been looking at Application.DoEvents(), but it seems like thats the wrong way to go. This application is pretty simple and its just a project and nothing professional. All this app does is send a file to a client and insert the data into a database.
I have one label (besides a success and error label), that I would like to constantly update along side the progress bar. Any ideas or tips on how to do this? Would Application.DoEvents() be acceptable in this situation? Or is there a simple way to update the text. I am using C#, asp.net, and a System.Web.UI.Page. Any help or pointing me to the right direction would be greatly appreciated.
protected void Button_Click(object sender, EventArgs e)
{
PutFTPButton.Enabled = false;
Thread.Sleep(3000);
Button btn = (Button)sender;
KaplanFTP.BatchFiles bf = new KaplanFTP.BatchFiles();
KaplanFTP.Transmit transmit = new KaplanFTP.Transmit();
//label text change here
if (btn.ID == PutFTPButton.ID)
{
//bf.ReadyFilesForTransmission();
DirectoryInfo dir = new DirectoryInfo(#"C:\Kaplan");
FileInfo[] BatchFiles = bf.GetBatchFiles(dir);
bool result = transmit.UploadBatchFilesToFTP(BatchFiles);
//label text change here
if (!result)
{
ErrorLabel.Text += KaplanFTP.errorMsg;
return;
}
bf.InsertBatchDataIntoDatabase("CTL");
bf.InsertBatchDataIntoDatabase("HDR");
bf.InsertBatchDataIntoDatabase("DET");
bf.InsertBatchDataIntoDatabase("NTS");
List<FileInfo> allfiles = BatchFiles.ToList<FileInfo>();
allfiles.AddRange(dir.GetFiles("*.txt"));
bf.MoveFiles(allfiles);
//label text change here
foreach (string order in bf.OrdersSent)
{
OrdersSentDiv.Controls.Add(new LiteralControl(order + "<br />"));
}
//lblWait.Visible = false;
OrdersSentDiv.Visible = true;
OrdersInfoDiv.Visible = false;
SuccessLabel.Visible = true;
NoBatchesToProcessLbl.Visible = true;
BatchesToProcessLbl.Visible = false;
PutFTPButton.Enabled = false;
BatchesCreatedLbl.Text = int.Parse(NextBatchNum).ToString();
Thread.Sleep(20000);
if (KaplanFTP.errorMsg.Length != 0)
{
ErrorLabel.Visible = true;
SuccessLabel.Visible = false;
ErrorLabel.Text = KaplanFTP.errorMsg;
}
}
}
I think you can use an Ajax UpdateProgress control, check Progress Bar on File Upload ASP.NET.
EDIT: Another one Displaying Progress Bar For Long Running Processes using ASP.NET AJAX.
Application.DoEvents() is not available in an ASP.NET application, nor is it's use acceptable in a standard WinForms application with the advent of multicore processors and the .NET threading library.
A web application requires communication to/from a server. Therefore simply updating the text of a label does nothing unless you are sending that back to the client. In your case you would need an event which was signaled by this line (because it is a batch upload):
transmit.UploadBatchFilesToFTP(BatchFiles);
The event would update the value you want to display. You would then need some AJAX code (or an ASP.NET update panel around a ASP.NET label) on the web page in question to get and display the new value.
HTH
delegate void SetTextCallback(string text);
private void SetText(string text)
{
if (this.label1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else this.label1.Text = text;
}
void SomeMethod()
{
SetText(yourVariable.ToString());
}
if i understand you correctly this should work.
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.