Dbloom
Dbloom

Reputation: 1402

Why does this code throw an exception? I thought RunWorkerCompleted could access UI elements

I have a datagrid in my WPF app, and when the app launches, I get deployment records from the database and load them into an ObservableCollection, which is bound to the datagrid.

Via a timer, I use a BackgroundWorker to go out and get any new records from the database and put them into a new ObservableCollection.

The, via RunWorkerCompleted I try to update the datagrid with the items in the new ObservableCollection.

However, I am getting a 'System.NotSupportedException'.

Additional information: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

I thought that I could access the UI controls from the RunWorkerCompleted method, but appearently there is something going on I don't understand.

My code is below:

private void MetroWindow_Loaded(object sender, RoutedEventArgs e)
    {
        //Init the deployment collection
        deployments = DataAccess.GetDeployments();
        dgDeployments.ItemsSource = deployments;

        //Setup the background worker
        bw = new BackgroundWorker();
        bw.DoWork += bw_DoWork;
        bw.RunWorkerCompleted += bw_RunWorkerCompleted;
        bw.WorkerReportsProgress = true;
        bw.WorkerSupportsCancellation = true;

        //Setup timer
        System.Threading.Timer t = new System.Threading.Timer(new System.Threading.TimerCallback(TimerProc));
        t.Change(10000, 0);           
    }

    private void TimerProc(object state)
    {
        //MessageBox.Show("Timer fired!");
        //Get the latest currentTime from the items in the grid
        DateTime? latestTime = DataAccess.GetDeployments().ToList()[0].CurrentTime;
        bw.RunWorkerAsync(latestTime);
    }

    void bw_DoWork(object sender, DoWorkEventArgs e)
    {         
        //Get new records that have changed since that time
        e.Result = (ObservableCollection<Deployment>)DataAccess.GetDeployments((DateTime)e.Argument);
    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if(e.Error != null)
        {
            MessageBox.Show(e.Error.Message);
        }
        else if (e.Cancelled)
        {
            MessageBox.Show("The operation was cancelled");
        }
        else
        {
            ObservableCollection<Deployment> newDeployments = (ObservableCollection<Deployment>)e.Result;

            foreach (Deployment d in newDeployments)
            {
                //Remove this new/changed deployment from the collection bound to the datagrid
                int index = deployments.IndexOf(deployments.Where(x => x.UniqueID == d.UniqueID).FirstOrDefault());
                if (index > -1)
                {
                    deployments.RemoveAt(index);
                }

                //Now add the new deployments
                deployments.Add(d);
                deployments.Move(deployments.IndexOf(d), 0);
            }
        }
    }

Upvotes: 2

Views: 101

Answers (2)

Dbloom
Dbloom

Reputation: 1402

Thank you everyone for the good information. It has helped me understand this issue better. Here is the code I used that ended up working for me.

Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()
                    {
                        //Remove the old deployment
                        deployments.RemoveAt(index);

                        //Now add the new deployments
                        deployments.Add(d);
                        deployments.Move(deployments.IndexOf(d), 0);
                    });

Upvotes: 1

ΩmegaMan
ΩmegaMan

Reputation: 31616

The timer process starts the background worker in a different thread. Try starting the background worker in the GUI thread so the completed operation will run on the GUI thread and not the thread started from the timer.

Either Invoke the operation to the GUI thread or just make the timer a DispatchTimer.

Upvotes: 2

Related Questions