since two days I am trying to solve the following problem:
I have a WPF control where a WrapPanel is bound to an ObservableCollection. An action changes the content of the ObservableCollection. The content is loaded in a BackgroundWorker. Immediately after the action that caused the content change, the new content is needed in a foreach-loop. The problem is that the loading of the content is slow, so it needs a bit to get ready.
My first attempt was to wait for the backgroundworker until the IsBusy property is set to false. But the IsBusy property never changed while waiting!
Second attempt was to try to manipulate the ObservableCollection directly from the BackgroundWorker. Of course no success because the ObservableCollection is in another thread than the BackgroundWorker.
I read really really much about how to manipulate content in cross-thread-wide. But none of them worked. Tried solutions with Dispatcher, "ThreadSafeObservableCollection", .....
Might anyone tell me how I can solve that problem?
Is there a simple way to edit content of the UI thread within another thread?
Or how do I wait correctly for the BackgroundWorker to get finished?
EDIT:
But how can I wait for the BackgroundWorker to get finished???
The BackgroundWorker can help you in two ways.
To update the collection while the BGWorker is running, use the ProgressChanged
event. The name of this event is misleading - while you can update the progress of a task, you can use actually use it for anything that needs to be done in the UI (calling) thread by passing an object using the UserState property of the ProgressChangedEventArgs.
The BGWorker also has an event when it finishes. Again, you can pass any information back to it that you'd like in the Result property of the RunWorkerCompletedEventArgs in the RunWorkerCompleted
event.
The following code is from another thread that I answered about BackgroundWorker:
BackgroundWorker bgWorker = new BackgroundWorker();
ObservableCollection<int> mNumbers = new ObservableCollection<int>();
public Window1()
{
InitializeComponent();
bgWorker.DoWork +=
new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.ProgressChanged +=
new ProgressChangedEventHandler(bgWorker_ProgressChanged);
bgWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
bgWorker.WorkerReportsProgress = true;
btnGenerateNumbers.Click += (s, e) => UpdateNumbers();
this.DataContext = this;
}
void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progress.Visibility = Visibility.Collapsed;
lstItems.Opacity = 1d;
btnGenerateNumbers.IsEnabled = true;
}
void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
List<int> numbers = (List<int>)e.UserState;
foreach (int number in numbers)
{
mNumbers.Add(number);
}
progress.Value = e.ProgressPercentage;
}
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
Random rnd = new Random();
List<int> numbers = new List<int>(10);
for (int i = 1; i <= 100; i++)
{
// Add a random number
numbers.Add(rnd.Next());
// Sleep from 1/8 of a second to 1 second
Thread.Sleep(rnd.Next(125, 1000));
// Every 10 iterations, report progress
if ((i % 10) == 0)
{
bgWorker.ReportProgress(i, numbers.ToList<int>());
numbers.Clear();
}
}
}
public ObservableCollection<int> NumberItems
{
get { return mNumbers; }
}
private void UpdateNumbers()
{
btnGenerateNumbers.IsEnabled = false;
mNumbers.Clear();
progress.Value = 0;
progress.Visibility = Visibility.Visible;
lstItems.Opacity = 0.5;
bgWorker.RunWorkerAsync();
}
Pushing the ObservableCollection.Add
to the dispatcher of the UI thread should work.
App.Current.Dispatcher.Invoke(new Action(() => collection.Add(item)));
You can update your collection in the BackgroundWorker.RunWorkerCompleted event handler. It runs in the same synchronization context you started it which is a UI thread usually so you can safely use any UI related stuff from there.
The BackGroundWorker fires an event when it is finished.
What I have been doing in a similar situation is :
I have a list that is not an observablecollecion
- Set enabled= false in my window and display a spinner
- Start background worker
- in DoWork i fill the list
- in RunWorkerCompleted event i copy the list content to my observablecollection and enable stuff and hide spinner
so all interaction with the collection is on same thread - the copying is usually not the expensive part.