0
votes

I'm using a DataGrid in my View, which is bound to a DataTable of a ViewModel instance.

Each change of the DataTable within the ViewModel Thread will be notified to the view and the view is up to date. So this works fine. But if I use an backgroundworker for editing DataTable's data and notify within RunWorkerCompleted Event then the view will not updated.

I tried already to raise the PropertyChange Event within the correponding Dispatcher, but no change.

So I checked the ThreadId's and all code will be executed in the right threads. My ViewModel:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;

namespace BackgroundWorker_vs_INotifyPropertyChange
{
    class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;



    BackgroundWorker worker;

    #region Properties
    private DataTable data = new DataTable();
    public DataTable Data
    {
        get
        {
            return data;
        }
    }
    #endregion

    #region Commands
    ButtonCommand btnCommand;
    public ICommand btnExecuteClick
    {
        get
        {
            return btnCommand;
        }
    }
    #endregion

    public ViewModel()
    {
        Debug.WriteLine("ViewModel_" + Thread.CurrentThread.ManagedThreadId);
        // Default data for datatable
        data.Columns.Add("Firstname");
        data.Columns.Add("Lastname");
        // -- Sample data
        data.Rows.Add("Andreas", "Anderson");

        // Commands
        btnCommand = new ButtonCommand(worker_Start);

        // BackgroundWorker
        worker = new BackgroundWorker();
        worker.WorkerReportsProgress = true;
        worker.WorkerSupportsCancellation = true;

        worker.DoWork += new DoWorkEventHandler(worker_DoWork);
        worker.ProgressChanged +=
                    new ProgressChangedEventHandler(worker_ProgressChanged);
        worker.RunWorkerCompleted +=
                   new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
    }

    private void worker_Start()
    {
        worker.RunWorkerAsync();
    }


    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        Debug.WriteLine("worker_" + Thread.CurrentThread.ManagedThreadId);
        // Process some work like filling the aDataTable with new data
        // ...
        int percentFinished = 0;
        while (!worker.CancellationPending && percentFinished < 100)
        {
            percentFinished++;
            worker.ReportProgress(percentFinished);
            System.Threading.Thread.Sleep(50);
            data.Rows.Add(percentFinished.ToString(), percentFinished.ToString());
        }
        e.Result = percentFinished;
    }

    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
    }

    private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {// Worker finished

        // Notify the PropertyChanged Listener the change of ProgressStateOfWork Property
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                    new Action(() =>
                    {
                        this.RaisePropertyChanged("Data");
                        Debug.WriteLine("Dispatcher_" + Thread.CurrentThread.ManagedThreadId);
                        //PropertyChanged(this, new PropertyChangedEventArgs("Data"));
                    }
                    ));
        MessageBox.Show("Completed");
    }

    protected void RaisePropertyChanged(string s)
    {
        Debug.WriteLine("RaisePropertyChanged_" + Thread.CurrentThread.ManagedThreadId);
        var temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new PropertyChangedEventArgs(s));
        }
    }
}

}

My View's Resource

<Window.Resources>
    <local:ViewModel x:Key="aViewModel"></local:ViewModel>
</Window.Resources>

The binding to DataTable:

<DataGrid x:Name="dataGrid" 
          ItemsSource="{Binding Data,Source={StaticResource aViewModel}}" />

The Button simply executes thread-worker.

2
Maybe the problem is that you just Update "Data". But if DataTable isn't observable you obviously dont update anything in View mate. Except the binding of Data... - Peter
btnExecuteClick oh please no. Don't. No Hungarian. Also, you don't have to BeginInvoke updates to your view model when it implements INPC--Bindings automatically marshal these update events onto the UI thread for you. That will simplify your code greatly. - user1228

2 Answers

0
votes

Use ObservableColletion for Data, as suggested by @Peter and modify the code

     Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                new Action(() =>
                {
                    Data.Rows.Add(percentFinished.ToString(), percentFinished.ToString());
                }
                ));

Hope this works!!

0
votes

Set the DataContext property of your window to an instance of your view model:

<Window.DataContext>
    <local:ViewModel />
</Window.DataContext>
...
<DataGrid x:Name="dataGrid" ItemsSource="{Binding Data}" />

Then the DataGrid should be updated whenever you raise the PropertyChanged event for the Data property of the view model.