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
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
I am trying to make a simple application in WPF which will open a new window in a thread it's behaving oddly.
ArrayList formArray = new ArrayList();
Thread th;
Window1 vd;
public void Start()
{
vd = new Window1();
formArray.Add(vd);
vd.ShowDialog();
}
public void StartCall()
{
th = new Thread(new ThreadStart(Start));
th.SetApartmentState(ApartmentState.STA);
th.Start();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
StartCall();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
((Window1)(formArray[0])).Show();
}
Window1 code is
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
this.Hide();
}
When trying to open it again, it just throws an error The calling thread cannot access this object because a different thread owns it.
When trying to use dispatcher.. invoke... all these things didn't help.
To make it even weirder, this same code worked in a Windows Forms application.
Maybe it's related to this line? th.SetApartmentState(ApartmentState.STA);?
It might be this guys, but if I won't add it, it will also fail with an error that
Additional information: The calling thread must be STA, because many UI components require this.
Edit:
Added the force run on dispatcher on your thread.
I also added a Display method to show the dialog depending on the dispatcher who is calling. Hope that help !
Also, as explained here: Dispatcher.Run
You should shutdown the dispatcher of the corresponding thread when you are done.
MainWindow:
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
StartCall();
}
ArrayList formArray = new ArrayList();
Window1 vd;
Thread th;
public void Start()
{
vd = new Window1();
formArray.Add(vd);
vd.ShowDialog();
System.Windows.Threading.Dispatcher.Run(); //ok this is magic
}
public void StartCall()
{
th = new Thread(new ThreadStart(Start));
th.SetApartmentState(ApartmentState.STA);
th.Start();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
StartCall();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
((Window1)(formArray[0])).Display();
}
Window1:
void Window1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
this.Hide();
}
public void Display()
{
if (!Dispatcher.CheckAccess())
{
Dispatcher.BeginInvoke((Action)Display);
return;
}
this.Show();
}
You can't call .Show on your window from a thread other than the one it was created on (that's basically what the error message is telling you!). Fortunately, as you suggested, this is what the dispatcher is for: to marshal calls onto the correct thread. But you have to use the correct dispatcher for the job!
Each control in WPF (including a Window) has a .Dispatcher property that gets the Dispatcher for that control's thread. My guess is that you were using the one from your main window when trying to re-open the dialog - which is the wrong one. Instead, if you use this in your Button_Click you will have more luck:
var window = (Window1)formArray[0];
window.Dispatcher.Invoke(window.Show); // note: use the dispatcher that belongs to the window you're calling
(NOTE: this isn't to say that this is a typically useful/recommended design pattern. In fact, it's often going to cause more problems than it solves. But, it's certainly something you can choose to do.)
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.
First of all, I post my code:
public partial class MainWindow : Window
{
private readonly BackgroundWorker worker = new BackgroundWorker();
private List<string> list = new List<string>();
private List<string> arrOfAdresses = new List<string>();
public MainWindow()
{
InitializeComponent();
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
list.Add("http://www.yahoo.com");
list.Add("http://www.google.com");
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("All is done");
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
foreach (string s in list)
{
WebBrowser bro = new WebBrowser();
bro.Width = bro.Height = 1;
grid.Children.Add(bro);
bro.Navigate(s);
bro.LoadCompleted += OnLoadCompleted;
}
}
private void button_Click(object sender, RoutedEventArgs e)
{
worker.RunWorkerAsync();
}
private void OnLoadCompleted(object sender, NavigationEventArgs e)
{
WebBrowser bro = sender as WebBrowser;
this.arrOfAdresses.Add(bro.Source.ToString()+"Added text");
MessageBox.Show("xxx"); //MessageBox is ignored
}
private void shower_Click(object sender, RoutedEventArgs e)
{
arrOfAdresses.Reverse();
foreach (string s in arrOfAdresses)
MessageBox.Show(s);
}
}
button_click event should store adresses into arrOfAdresse, which will be little bit modified.
Next, shower_Click should show all modified values which are in arrOfAdresses. When I want to show values, which I shoud have in arrOfAdresses, it returns me nothing. I think, problem is in LoadCompleted event, because when I put the MessageBox the program ignore it. Is there some way I can show values, when shower_Click is raised and is there some way I can fix it? Thank you for replies.
DoWork runs on another thread so you're not allowed to touch the UI (directly). Most obvious offender:
grid.Children.Add(bro);
But creating and loading the WebBrowser is probably not OK either.
Most important lesson to learn here:
always check the e.Error property first in a Completed event.
There's no way the message box is just ignored. Most likely the line above it threw an exception. WPF doesn't crash the application when exceptions are thrown, instead he logs them to the Debug output. Look there for the exception and you'll know what's wrong.
Most likely the exception is thrown because the line doesn't occur in the UI thread. If that's the case, all you need to do is run the command using a dispatcher, like this:
Dispatcher.Invoke(new Action(() => arrOfAdresses.Add(bro.Source.ToString() + "Added text")));
Note the dispatcher in my sample is a property of window: http://msdn.microsoft.com/en-us/library/system.windows.dependencyobject.dispatcher(v=vs.95).aspx
I have done things like this in Webbrowser. Loading progress in webbrowser in actually done in a seperate thread even if u call it from the main thread. So in my case, in many situations, if there is any error in between any of the lines in onLoadComplete or onPregress events, the error is not thrown. I donno how or why. But what i'll do is just debug. You have to put breakpoint right into starting line of the onLoadComplete event, and analyse line by line.. Even Try Catch wont give result, but this does.. And at the line where the program skips the next lines will be havin error..
There will be an error in the line
this.arrOfAdresses.Add(bro.Source.ToString()+"Added text");
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...