Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update ObservableCollection from inside a BackgroundWorker using MVVM?

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???

like image 679
Kai Avatar asked Jan 21 '11 13:01

Kai


2 Answers

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();
}
like image 167
Wonko the Sane Avatar answered Sep 18 '22 14:09

Wonko the Sane


Pushing the ObservableCollection.Add to the dispatcher of the UI thread should work.

App.Current.Dispatcher.Invoke(new Action(() => collection.Add(item)));
like image 35
H.B. Avatar answered Sep 21 '22 14:09

H.B.