I am working on a program which uses a backgroundWorker to append text to a Textbox control. My problem is that simply, the backgroundWorker will not insert text into the Textbox control, it just remains blank.
My code:
private void button1_Click(object sender, EventArgs e) {
backgroundWorker1.RunWorkerAsync(); //Start the worker
}
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
this.writeText("Hello World!");
}
public void writeText(string text) {
textbox1.Text = textbox1.Text + text + "\r\n";
textbox1.SelectionStart = textbox1.Text.Length;
textbox1.ScrollToCaret(); //Scroll to the end of the textbox
}
Looking at the code, it seems fine (to me anyway) and it compiles fine too, so it's probably something very obvious that I am missing.
If someone would care to enlighten me on what I am doing wrong, it would be much appreciated ;)
In the method subscribed to the DoWork event, do your long-running operation. Then, within the worker method, you can call ReportProgress to send updates. This causes the OnProgressChanged event to fire on your UI thread, at which time you can make changes to your UI.
Alternately, if you're on .NET 4.5, you could use the async/await pattern to keep your UI responsive while performing long-running, I/O-bound operations. For CPU-bound operations, a BackgroundWorker is still appropriate.
As always, MSDN is a fantastic resource. From the BackgroundWorker page:
To set up for a background operation, add an event handler for the
DoWork event. Call your time-consuming operation in this event
handler. To start the operation, call RunWorkerAsync. To receive
notifications of progress updates, handle the ProgressChanged event.
To receive a notification when the operation is completed, handle the
RunWorkerCompleted event.
You must be careful not to manipulate any user-interface objects in
your DoWork event handler. Instead, communicate to the user interface
through the ProgressChanged and RunWorkerCompleted events.
You can declare a delegate at the class level for your Form.
public delegate void WriteLogEntryDelegate(string log_entry);
You can then wrap up most of the logic in another method:
void WriteLogEntryCB(string log_entry)
{
if (textbox1.InvokeRequired == true)
{
var d = new WriteLogEntryDelegate(WriteLogEntryCB);
this.Invoke(d, log_entry);
}
else
{
textbox1.Text(log_entry + "\r\n");
this.textbox1.SelectionStart = this.textbox1.Text.Length;
this.textbox1.ScrollToCaret();
}
}
You can then call that Function from your DoWork Method:
public void DoWork(object sender, DoWorkEventArgs e)
{
WriteLogEntryCB("Hello World!");
}
Edit to Include Daniel Mann's Suggestion:
Another way would be to cast the sender in the DoWork method as the BackgroundWorker, and then call the ReportProgress Method (or use the RunWorkerCompleted Event Handler).
void bw1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
worker.ReportProgress((1));
}
You would then require an event handler for the ProgressChanged Event:
void bw1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//update textbox here
}
Finally, you could also use the RunWorkerCompleted Event Handler:
void bw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//update textbox here
}
In the writeText method, the UI related code should be invoked by the UI
thread that create the UI controls.
What you do is called attempt to get access to the UI element from other the thread rather than the thread where element was created.
Read more info here.
Related
I have the following form where I am trying to implement an incremental search on, using a backgroundworker.
So the idea is the user types in the textbox at the top, and for each keystroke, the listview below is filtered to contain only the items that contain the characters the user has typed.
I have recently learnt about the backgroundworker component and was therefore trying to use it to do the filtering and updating the listbox.
This is the event code for the textbox:
private void txtSearch_TextChanged(object sender, EventArgs e)
{
if (!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
}
}
and the backgroundworker event is:
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
if (txtSearch.Text != String.Empty)
{
GetTheListOfFiles();
listView.Items.Clear(); << Exception occurs here !
...... //some more code to populate the listview control
}
}
PROBLEM
When I type into the textbox, I was expecting the listbox to respond immediately to my keystrokes and display the filtered data accordingly. Instead, there is a pause of about 8 seconds and then I get this error:
I presume the issue is the bit that I have highlighted, but I have no idea how to solve it. Is it that a backgroundworker cannot be used for this purpose or am I missing something in my implementation?
PS: I welcome any different way to accomplish this. Perhaps there's a better solution out there among more experienced programmers?
UPDATE
Here is the progresschanged event I am using:
private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
toolStripProgressBar1.Value = e.ProgressPercentage;
tsLabelTwo.Text = e.ProgressPercentage.ToString() + #"%";
}
Thanks
If you create a control using the UI thread, you can't access it thought another thread (eg some background thread)
Just invoke the block that is throwing cross-thread exception on the main thread:
listView.BeginInvoke(new Action(() => { listView.Items.Clear(); }));
If you want to update UI, you need to invoke the control:
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
if (txtSearch.Text != String.Empty)
{
GetTheListOfFiles();
listView.Dispatcher.BeginInvoke(new Action(() => listView.Items.Clear()), DispatcherPriority.Background);
}
}
This is because you are trying to a control that runs on UI thread from another thread you've created, which is considered illegal. The correct workaround for this is to invoke your control, in this case your ListView.
listView.BeginInvoke(new Action(() =>
{
listView.Items.Clear();
//or perform your UI update or whatever.
}));
But if you wanna be such a rebel and do illegal stuff (sarcasm), add this piece of code right after your InitializeComponents(); method in the form's constructor.
Control.CheckForIllegalCrossThreadCalls = false;
But don't, there is a reason it is called "Illegal Thread Calls" :)
For more information Control.CheckForIllegalCrossThreadCalls Property
In the example below, I use the ReportProgress functionality of the BackgroundWorker:
void MyMethod()
{
if (!File.Exists)
{
bw.ReportProgress(0, "Error");
}
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.MyMethod1();
this.MyMethod2();
this.MyMethod3();
}
private void bw_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
string error = e.UserState as String;
if (!string.IsNullOrEmpty(error)
{
// stop BackgrowndWorker
}
}
In every MyMethod() i check something and do ReportProgress.
If there are possibilities to correctly stop BackgroundWorker in my case?
Need i to change the way of my code architecture?
The ReportProgress method is to... report progress. The only thing you can pass in is an percentage and the user state. That's all. You shouldn't use it to pass in an error.
Instead, the best thing you can do is to throw an exception. This will bubble up till the RunWorkerCompleted event handler, which has an event argument property named Error. Check that property in the event handler you write and you know what went wrong.
I would like to add progress bar to my "app".
ProgressForm progressForm = new ProgressForm();
progressForm.paths.path1= pathSource1;
progressForm.paths.path2 = pathSource2;
progressForm.paths.path3= pathSource3;
progressForm.paths.path4=path4;
progressForm.paths.path5 = path5;
progressForm.ShowDialog();
During load event of progress form backgroundworker is fired.
private void ProgressForm_Load(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//some code
backgroundWorker1.ReportProgress(1, "Loading Data From File");
//some code
backgroundWorker1.ReportProgress(0, "Loading Data From ...File");
//some code
backgroundWorker1.ReportProgress(10, "Loading Data From... File 2");
//some code
backgroundWorker1.ReportProgress(0, "hjhgjhgjfhgh");
for (int i = 0; i < dataCollection.Count(); i++)
{
//some code
backgroundWorker1.ReportProgress(((i+1) / data1.Count())*100, "");
//some code
}
}
WorkerReportsProgress is set to true, unfortunatelly ReportProgress method is not firing event ProgressChange (I set breakpoint there)
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage!=0)
{
progressBar.Value = e.ProgressPercentage;
}
if (e.UserState.ToString()!="")
{
lblProgressDesc.Text = e.UserState.ToString();
}
}
What could be a cause of that ?
You should set the WorkerReportsProgress property to true
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker_properties(v=vs.110).aspx
The following code example demonstrates the use of the ProgressChanged event to report the progress of an asynchronous operation to the user. This code example is part of a larger example provided for the BackgroundWorker class.
// This event handler updates the progress bar.
private void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
}
I had a similar issue, where the ProgressChanged and RunWorkerCompleted events weren't firing. I was starting the RunWorkerAsync process from the UI thread and then sitting in a sleep loop waiting for the IsBusy flag to change. Turned out I needed to add an Application.DoEvents to the loop in order for the events to fire.
Ok, I am sorry, I found the reason. I do not why but VS did not treat mentioned method/event handler as an event of backgroundworker. I went to properties -> events -> clicked twice on ProgressChanged and it added new event handler: backgroundWorker1_ProgressChanged_1
I have a MainWindow with eventhandler which is not working properly. I have made simple model of this problem. Please see comment in code where the problem is:
public partial class MainWindow : Window
{
public event EventHandler Event1;
public MainWindow()
{
Event1 += MainWindow_Event1;
InitializeComponent();
}
void MainWindow_Event1(object sender, EventArgs e)
{
textBox1.Text = "wth!?"; //Not changing text box. Not showing message. If delete this line, it will work fine
MessageBox.Show("raised");
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
EventHandler evt = Event1;
while (true)
{
Thread.Sleep(500);
evt(null, null);
}
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerAsync();
}
}
Please explain this behavior and how can I fix it?
The problem is that you're invoking the event from a background thread. This will not work and the program is simply hanging when trying to access the TextBox. However, if you change this code:
textBox1.Text = "wth!?"; //Not changing text box. Not showing message. If delete this line, it will work fine
MessageBox.Show("raised");
to this:
this.Dispatcher.BeginInvoke((Action)delegate()
{
textBox1.Text = "wth!?"; //Not changing text box. Not showing message. If delete this line, it will work fine
MessageBox.Show("raised");
});
it'll work for you.
You can't update the UI elements from the background thread.
The worker thread fails by exception trying to access the UI element (Text property). So messageBox isn't showing as well. Use notification mechanisms, or Dispatcher calls (there is a wast amount of information like this on the web)
Here are possible duplicates/help:
Update GUI using BackgroundWorker
Update GUI from background worker or event
This problem is because you need to use the Synchronization Context of the current Thread for comunicating between threads, some thing like this
private void Button_Click(object sender, RoutedEventArgs e)
{
var sync = SynchronizationContext.Current;
BackgroundWorker w = new BackgroundWorker();
w.DoWork+=(_, __)=>
{
//Do some delayed thing, that doesn't update the view
sync.Post(p => { /*Do things that update the view*/}, null);
};
w.RunWorkerAsync();
}
Please check this question, hope can helps...
ObservableCollection<String> listBoxItems = new ObservableCollection<String>();
scheduledRecordingListBox.ItemsSource = listBoxItems;
public void timerElapsed(object sender, ElapsedEventArgs e)
{
listBoxItems.Remove(itemToBeRemoved);
}
Just a snippet of what I'm actually trying to do. I believe the error is caused because the timer is running on a different thread than the GUI main thread that the ObservableCollection I'm trying to remove from is.
If you are using WinForms, then just use the System.Windows.Timer class. It's Tick event is automatically executed on the UI thread.
This should do the Trick:
public void timerElapsed(object sender, ElapsedEventArgs e)
{
this.Invoke(new Action(() => listBoxItems.Remove(itemToBeRemoved)));
}
Try using Invoke it executes a delegate on the thread that owns the control's underlying window handle.
You can also have a look of the section timers in this page