Changing label text in background worker Error - c#

I am trying to change a label on the form during a background worker process However it says unhandled exception. I've looked at the error and it said to invoke it like the accepted answer says here: Update label text in background worker winforms
I have done this successfully with changing values inside a checkbox list, but I've used the same method and for the label it won't invoke, I get the red error line in the code when I type it.
My Background Worker:
private void bw2_DoWork(object sender, DoWorkEventArgs args)
{
BackgroundWorker worker = sender as BackgroundWorker;
func.sshConnect();
for (int num = 0; num < checklist.Items.Count; num++)
{
if (checklist.GetItemChecked(num))
{
string project = checklist.Items[num].ToString();
lblStatus.Text = "Opening " + project + "..."; //error here
if (func.svnCheckoutProject(project))
{
lblStatus.Text = project + " Opened"; //same error here
func.sshRunCommand("echo " + project + " >> " + Properties.Settings.Default.serverUserFilesPath + Properties.Settings.Default.Username);
}
else
{
//error message
}
}
worker.ReportProgress(num * (100 / checklist.Items.Count));
}
}
I've tried replacing the lines with errors with this, but in visual studio it gives it a red line under invoke and won't let me build it.
lblStatus.Invoke((MethodInvoker)delegate { lblStatus.Text = "Opening " + project + "..."; });
Visual studio points me to here when the error appears: MSDN
I used this method for the checkbox list and it worked but try it for the label and it doesn't work.
Any ideas of why it isn't working or another way of doing this?

Don't update your UI controls from the DoWork event - you're on a separate thread from the UI thread. You can call Invoke, but it's really not the right place for it.
The BackgroundWorker already provides a construct for periodically updating the UI thread while the worker is running, and you're already using it - it's the ReportProgress method. That's the place you should be updating your Label from. Anything in there runs back on the main UI thread.
You can pass any object you want to the ReportProgess method:
worker.ReportProgress(num * (100 / checklist.Items.Count),
string.Format("Opening {0} ...", project));
Then cast the value back and use it in the ProgressChanged event:
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var message = e.UserState.ToString();
lblStatus.Text = message;
}

In most cases, you would want to use BeginIvoke rather than Invoke
public void changeLabelText(System.Windows.Forms.Label lib, String whateva)
{
if (lib.InvokeRequired)
{
lib.BeginInvoke(new MethodInvoker(() => changeLabelText(lib, whateva)));
}
else
{
lib.Text = whateva;
}
}
and the call the method in your DoWork
private void bw_DoWork(object sender, DoWorkEventArgs args)
{
//do something
//change the text of a label lb1
changeLabelText(lb1, "I'm reporting some progress....");
}

Related

Updating the GUI from background worker

The name of the question is: "Updating the GUI from background worker", but the correct name world be: "Updating the GUI from background worker OR reporting multiple-variables (other than an integer) from background worker"
Please let me explain my situation. In a program I have a background worker which analyses the information.As the result of this analysis - form GUI elements should be populated with necessary data. In GUI I would like to update
2 datagridviews
1 listbox
5 labels
As I understand - I can only natively report 1 int value via ReportProgress() method of background worker.
So the question is - how can I pass a List<> ( + some other variables: string, int) via ReportProgress()? Basically - i want to update the GUI with the information but "1 integer" just won't do.. So either it should be possible to pass multiple variables via an ReportProgress() OR i can use an Invoke from inside the BackgroundWorker itself to update the GUI.. Personally I don't like the Invoke approach... What's your opinion?
Here is my code (see the comments):
private void button9_Click(object sender, EventArgs e) // start BW
{
bw.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
bw.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.RunWorkerAsync(10);
}
private void button10_Click(object sender, EventArgs e) // cancel BW
{
bw.CancelAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int count = (int)e.Argument;
for (int i = 1; i <= count; i++)
{
if (bw.CancellationPending)
{
e.Cancel = true;
break;
}
List<List<string>> list_result = new List<List<string>>();
list_result = Proccess();
bw.ReportProgress(list_result.Count()); // right now I can only return a single INT
/////////// UPDATE GUI //////////////
// change datagridview 1 based on "list_result" values
// change datagridview 2
// change listbox
// change label 1
// change label ..
Thread.Sleep(20000);
}
MessageBox.Show("Complete!");
e.Result = sum;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
prog_count++;
listBox1.Items.Add("Count: (" + prog_count.ToString() + "/20). Found: " + e.ProgressPercentage.ToString() + ".");
}
There's a UserState parameter when calling ReportProgress.
var list_result = new List<List<string>>();
new backgroundWorker1.ReportProgress(0, list_result);
The parameter type is an object so you'll have to cast it back to the type you need:
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var userState = (List<List<string>>)e.UserState;
}
The tricky issue with this is, how do you determine whether you're passing back a List, or a list of lists, or a single string, number, etc. You'll have to test for each possibility in the ProgressChanged event.
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var myList = e.UserState as List<List<string>>;
if (myList != null)
{
// use list
return;
}
int myNumber;
if (Int32.TryParse(e.UserState.ToString(), out myNumber))
{
// use number
return;
}
var myString = e.UserState.ToString();
// use string
}
Alternatively, you could create a class that holds all the values you need (or use Tuple), run everything in the background to populate that class, then pass that to the RunWorkerCompleted event, and update your UI all at once from there.
I have written two very easy methods that enable you to invoke your code (only if required) and you only need to write your code once. I think this makes Invoke much friendlier to use:
1) BeginInvoke
public static void SafeBeginInvoke(System.Windows.Forms.Control control, System.Action action)
{
if (control.InvokeRequired)
control.BeginInvoke(new System.Windows.Forms.MethodInvoker(() => { action(); }));
else
action();
}
2) Invoke
public static void SafeInvoke(System.Windows.Forms.Control control, System.Action action)
{
if (control.InvokeRequired)
control.Invoke(new System.Windows.Forms.MethodInvoker(() => { action(); }));
else
action();
}
It can be called like this:
SafeInvoke(textbox, () => { textbox.Text = "text got changed"; });
Alternatively you could just
System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false;
(which only changes behaviour in debug mode btw) and look if you run into problems.More often than not you actually don't. It took me quite some time to find cases very Invoke is really required for things not to get messed up.
The basic pattern for updating the UI from another thread is:
If controlItem.InvokeRequired Then
controlItem.Invoke(Sub() controlItem.Text = textUpdateValue)
Else
controlItem.Text = textUpdateValue
End If
This could update your list of controls without requiring you to pass anything through ReportProgress. If you would like to update your control from within the thread, I don't believe you need to check InvokeRequired, because it will always be required. However, best practices might be to expose the setting of a control via a property and then to do the full check so you can call it from anywhere.

C# executing code for GUI before executing blocking processes

I have (hopefully) a straight forward question. I have a function that runs a command prompt command in a hidden window and returns the response in a string. This process takes about 3 seconds. I wanted to add a simple label in my GUI that would appear before the function executes. The label just states that something is being checked so the user does not think the interface is just slow or unresponsive.
Here is an example snippet to illustrate.
svnPathCheck_lbl.Visible = true; //Show the label
// Check validity of SVN Path
string svnValidity = getCMDOutput("svn info " + SVNPath_txtbox.Text);
// Here we call Regex.Match. If there is a 'Revision:' string, it was successful
Match match = Regex.Match(svnValidity, #"Revision:\s+([0-9]+)", RegexOptions.IgnoreCase);
svnPathCheck_lbl.Visible = false; //Hide the label
The getCMDOutput() function runs the hidden command and blocks the GUI.
What I expected this to do was display my label "Checking ...", then run the blocking function getCMDOutput(). Once the function returned and the GUI was responsive again, it would hide the label.
Instead, I never see the label show up at all. Its almost like it never executed. Could it be that the blocking function executes before the GUI has a chance to update?
Thanks for the help!
try this code, it should work...
private void button1_Click(object sender, EventArgs e)
{
svnPathCheck_lbl.Text = "Checking...";
svnPathCheck_lbl.Visible = true;
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_WorkCompleted;
bw.RunWorkerAsync();
}
private void bw_WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
svnPathCheck_lbl.Text = "Work completed";
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
string svnValidity = getCMDOutput("svn info " + SVNPath_txtbox.Text);
Match match = Regex.Match(svnValidity, #"Revision:\s+([0-9]+)", RegexOptions.IgnoreCase);
}
I recommend that you run your getCMDOutput method asynchronously.
If this is a windows forms application you can do this using a BackgroundWorker. Handle the event DoWork of the worker to call your method, and where you were calling it previously, put instead backgroundWorker1.RunWorkerAsync()
This will cause the method to run in a new thead, so the UI updates will be treated separately and will be instantaneous.
try this:
svnPathCheck_lbl.Visible = true; //Show the label
Task connectToSVN = new Task(() => { this.connectToSVN; }); connectToSVN.Start(); //Open new Task to complite the code without blocking the GUI.
private void connectToSVN
{
// Check validity of SVN Path
string svnValidity = getCMDOutput("svn info " + SVNPath_txtbox.Text);
// Here we call Regex.Match. If there is a 'Revision:' string, it was successful
Match match = Regex.Match(svnValidity, #"Revision:\s+([0-9]+)", RegexOptions.IgnoreCase);
this.Dispatcher.Invoke((Action)(() =>
{
svnPathCheck_lbl.Visible = false; //Hide the label
}
));
}

Form freezes when opened for second time

I am developing a Windows Forms application that access a WCF service. I ran into a great problem that I can't predict the reason of it. Even the Visual Studio debugger not showing any exception in the Output view. The scenario is like this, I have a custom user control that has a linkLabel on it. Whenever the link label is clicked, a form is opened and a class object is passed to it. The class definition of this object resides on WCF service on a remote server. Now the problem is that when I click the linkLabel, the form opens perfectly loading each of its component according to the class object passed to it. But when I close this form and click that linkLabel again, the form opens but immediately freezes after loading some elements. I tried many code variations. Edited many part of code that I think can affect. But none of them showed the difference. Since, I don't know where actually is the code has error, I am posting the linkLabel click code and functions that are called after it is clicked.
private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Enabled = false;
string temp = Title.Text;
Title.Text = "Opening...";
System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(openTopic));
t.Start();
Title.Text = temp;
Enabled = true;
}
void createTopicWindow()
{
TopicViewer t = new TopicViewer(t);
Invoke(new Action(() => t.Show()));
}
private void openTopic()
{
Invoke(new Action(() => createTopicWindow()));
}
The above is the edited code, since I was getting Cross thread exception before.
Following is the code of constructor of the form that is called when clicked the linkLabel:
try
{
InitializeComponent();
this.t = topic;
if (IsHandleCreated == false)
CreateHandle();
System.Threading.Thread th = new System.Threading.Thread(new System.Threading.ThreadStart(loadTopic));
th.Start();
Common.openedTopics.Add(this);
AddComment addComment1 = new AddComment();
addComment1.Topic = t;
addComment1.Dock = DockStyle.Fill;
panel5.Controls.Add(addComment1);
backgroundWorker1.RunWorkerAsync();
}
catch (Exception)
{ }
void loadTopic()
{
Invoke(new Action(()=>tHead = new TopicHeader()));
Global.SetControlPropertyThreadSafe(tHead,"Topic", t);
Global.SetControlPropertyThreadSafe(tHead,"Dock", DockStyle.Fill);
Invoke(new Action(()=>panel1.Controls.Add(tHead)));
Global.SetControlPropertyThreadSafe(this,"Text", t.Title + " - Topic Viewer");
if (t.Description.Trim().Length > 0)
{
Global.SetControlPropertyThreadSafe(webBrowser1, "DocumentText", t.Description);
}
else
{
Invoke(new Action(() => tabControl1.TabPages[0].Dispose()));
}
Global.SetControlPropertyThreadSafe(tabPage2, "Text", "Comments (" + client.getComCount(t.TopicID) + ") ");
}
TopicHeader is another small user control.
Please anyone tell me the solution to this?
If you are using .Net 4.5, then using async/await would be easiest solution. That way, you don't need any Invokes
async private void Form1_Load(object sender, EventArgs e)
{
string s = await Task<string>.Factory.StartNew(LongRunningTask,
TaskCreationOptions.LongRunning);
this.Text = s;
}
string LongRunningTask()
{
Thread.Sleep(10000);
return "------";
}
I can't give a direct answer to you question, but this may give a hold on.
public void Form_Load()
{
// do some stuff on the gui-thread
// i need to do something that takes a long time:
ThreadPool.QueueUserWorkItem((state) =>
{
// i'll execute it on the ThreadPool
// Long running code....
// update results in mainform on gui thread.
Invoke(new Action( delegate
{
// because the invoke will execute this on the gui-thread, you'll able to update controls.
// update my gui controls.
DataGrid.Source = myReceiveDataThing;
}));
}
}
You might expand the code, to check if the form is still valid.

Implementing a progress bar in WinForms app during parsing XML

I made an application that passes trough an XML file and "extracts" the entries that have a specific value of a certain attribute. The application works fine, but the XML file has over 52,000 entries and only 3 values to sort by (so naturally it takes some time to execute it). Because of that I want to implement a progress bar so I can follow the progress my application made. I googled it, but none of the solutions work for me (the application does it's work, but the progress bar doesn't move at all), so if you could help me that would be grate.
The code that extracts the entries is this:
XmlNodeList nodes;
string ExtractedXML = "";
private void extractingXMLToolStripMenuItem_Click(object sender, EventArgs e)
{
if (nodes.Count != 0)
{
foreach (XmlNode node in nodes)
{
if (String.Compare(node.ChildNodes[6].ChildNodes[0].Attributes.GetNamedItem("code").Value.ToString(), "CodeValue") == 0)
{
tempXML = node.InnerXml;
temp = "<" + node.Name + " code=\"" + node.Attributes.GetNamedItem("code").Value + "\">" + Environment.NewLine;
temp += tempXML + Environment.NewLine;
temp += "</" + node.Name + ">";
ExtractedXML += temp + Environment.NewLine;
temp = "";
}
}
textBox1.Text = ExtractedXML;
}
}
There is a ToolStripMenue item for each value with similar code, only the "CodeValue" changes. How can I implement the progress bar to show me how far did the application get?
Best thing to do, is to not process the XML on the UI thread.
The reason why you don't see the progressbar increasing, is because processing that XML probably uses all resources and the UI thread has no chance of updating it's controls, until processing the XML has finished.
You can use a BackgroundWorker to do the processing, and use the ProgressChanged event to let the UI know that it should update the progressbar.
By doing so, your application will remain responsive, and the UI will not freeze. The UI thread will be handed some CPU time as well, and it is able to update his controls and respond to events.
You should do your long running process inside of a separate thread, BackgroundWorker would be useful here. When kicking off your process call:
bgWorker.RunWorkerAsync(//pass in object to process)
Then have a ReportProgress event where you increment your progress bar e.g.
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
And finally, inside of the do work event you'll want to call report progress with your formula for getting the percentage completed, as well as passing in a processed result, here's an example i've used:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
bgWorker.ReportProgress((outputGrid.Rows.Count * 100) / importGrid.Rows.Count, result);
}

Background worker, and cross thread issue

I have a Winforms application which is working fine.. using a BackgroundWorkerThread to manage GUI usability during processing of serial data to a device.
It's working fine.
Now, I am adding a new method, and copying what I did in other forms. But I am getting a cross thread exception.
I declare my BWT like this:
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += DownloadGpsDataFromDevice;
bw.WorkerReportsProgress = true;
bw.RunWorkerAsync();
I then have a method delared like this, which does the background work:
private void DownloadGpsDataFromDevice(object sender, DoWorkEventArgs e)
{
_performScreenUpdate = true;
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
Common.WriteLog("Extracting raw GPS data. Sending LL.");
ReplyString raw = DeviceServices.ExecuteCommand("$LL");
The DeviceServices.ExecuteCommand("$LL"); is the bit that does the work, but I am getting the exception on the previous line, where I log to a text file. Now, that makes you worry - writing to a file. However, I have done this thousands of times in another BWT.
I made the writing thread safe. Here this my Common.WriteLog method:
public static void WriteLog(string input)
{
lock (_lockObject)
{
WriteLogThreadSafe(input);
}
}
private static void WriteLogThreadSafe(string input)
{
Directory.CreateDirectory(LogFilePath);
StreamWriter w = File.AppendText(LogFilePath + #"\" + LogFileName);
try
{
w.WriteLine(string.Format("{0}\t{1}", DateTime.Now, input));
}
catch (Exception e)
{
System.Console.WriteLine("Error writing to log file!");
System.Console.WriteLine("Tried to write: [" + input + "]");
System.Console.WriteLine("Failed with error: [" + e.Message + "]");
}
finally
{
w.Close();
}
}
This have been working for ages. I don't believe the error is there. I think I am just missing something on the call maybe?
You cannot change UI elements from BackgroundWorker thread. You'll have to marshall back to UI thread by calling Invoke().
Try this
private void DownloadGpsDataFromDevice(object sender, DoWorkEventArgs e)
{
_performScreenUpdate = true;
Invoke((MethodInvoker)(() => {
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
});
...
The issue is that you are updating UI elements from non-UI thread:
Those lines should not be inside of DownloadGpsDataFromDevice
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
To take advantage of BackgroundWorker run method bw.ReportProgress(0);. Update UI in ProgressChanged handler, which was specifically designed for this purpose.
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage = 0)
{
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
}
}
Some instances can't or should not be accessed by multiple threads. You have two options to protect your data from cross thread exceptions.
You can lock your object when you access it from multiple threads with a lock:
object locker = new object();
SomeObject MyObject = new SomeObject();
private void FromMultipleThread()
{
lock(locker)
{
MyObject = OtherObject;
}
}
Your second option is to lock your thread with a ManualResetEvent. It is very simple, you only have to call WaitOne() from you ManualResetEvent to lock your thread while an other thread access your "cross threaded" Object.
In your case, you would want to change your UI from the reportProgress of your backgroundWorker. The reportProgress will come back to the initial thread, then you can modify your UI.
Are you sure that is the right line? I don't think you should be able to update the ui in your worker. Try commenting out the gui update and clean and build your solution to see if the logging is really the problem. To update the ui, set WorkerReportsProgress and create an event handler for that to update the ui and report progress in the worker.

Categories