0

I am having a class named Employee. I need to update only the property Status from thread other than current dispatcher thread:

class Employee
{
    public string Name { get; set; }
    public string Status { get; set; }
}

Viewmodel (ViewModelBase implements INotifyPropertyChanged interface):

    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }


public class DisplayViewModel : ViewModelBase
{

    public static event EventHandler OnStatusChanged;

    ObservableCollection<Employee> _dailyEmployees;

    public ObservableCollection<Employee> DailyEmployees
    {
        get { return _dailyEmployees; }
        set
        {
            _dailyEmployees = value;
            OnPropertyChanged();
        }
    }

    public DisplayViewModel()
    {
        OnStatusChanged += DisplayViewModel_OnStatusChanged;
    }

    //invoked in other thread
    private void DisplayViewModel_OnStatusChanged(object sender, EventArgs e)
    {
        var d = sender as Employee;
        if (d == null)
            return;
        var em = DailyEmployees.FirstOrDefault(a => a.Name == d.Name);

        if(em == null)
        {
            DailyEmployees.Add(em);
        }
        else
        {
            em.Status = d.Status;
        }
    }
}

When i tried adding values to observable collection it throws exception as

"This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread"

How shall I proceed? Thanks.

G K
  • 2,481
  • 4
  • 29
  • 45
S.Frank Richarrd
  • 488
  • 1
  • 3
  • 15
  • 2
    Possible duplicate of [How do I update an ObservableCollection via a worker thread?](https://stackoverflow.com/questions/2091988/how-do-i-update-an-observablecollection-via-a-worker-thread) –  Jul 10 '18 at 06:42

3 Answers3

3

Either use the dispatcher to call the Add method on the UI thread:

Application.Current.Dispatcher.BeginInvoke(() => DailyEmployees.Add(em)); 

Or call the BindingOperations.EnableCollectionSynchronization method on the UI thread to enable the collection to be used on multiple threads:

public class DisplayViewModel
{
    private readonly ObservableCollection<Employee> _dailyEmployees = new ObservableCollection<Employee>();
    private readonly object _lock = new object();

    public ObservableCollection<Employee> DailyEmployees
    {
        get { return _dailyEmployees; }
    }

    public DisplayViewModel()
    {
        System.Windows.Data.BindingOperations.EnableCollectionSynchronization(_dailyEmployees, _lock);
        OnStatusChanged += DisplayViewModel_OnStatusChanged;
    }

    //invoked in other thread
    private void DisplayViewModel_OnStatusChanged(object sender, EventArgs e)
    {
        var d = sender as Employee;
        if (d == null)
            return;
        var em = DailyEmployees.FirstOrDefault(a => a.Name == d.Name);

        if (em == null)
        {
            DailyEmployees.Add(em);
        }
        else
        {
            em.Status = d.Status;
        }
    }
}

Also note that the Employee class should implement the INotifyPropertyChanged interface and raise the PropertyChanged event for the Status property if you want the value of this one to be reflected in the view when you set it.

mm8
  • 163,881
  • 10
  • 57
  • 88
1

There is a big problem in your code. First, the ObservableCollection is already a collection that notifies on change, so you not need reinitialize it, just call Clear and Add/Insert.

Second, the Employee class should be ViewModelBase:

class Employee: ViewModelBase
{
   private string _status;

   public string Name { get; set; }
   public string Status
   {
     get
     {
        return _status;
     }
     private set
     {
        _status=value;
       RaisePropertyChanged("Status");
     }
   }
}

This should allow you to change DailyEmployees[0].Status="NewStatus";

LE: In case you have problems changing data from another thread, check this link: Using BindingOperations.EnableCollectionSynchronization

LLE: I used the ViewModelBase class for the Employee, because it was already used in the code sample. A raw answer would have mean implementing the class Employee: INotifyPropertyChanged and implementing the required method.

Lupu Silviu
  • 1,145
  • 11
  • 23
  • 1
    _"Employee class should be ViewModelBase"_ - no it should not. An `Employee` is the _model_ in _MVVM_ not the _VM_, All it needs is a `INotifyPropertyChanged`. The fact that this interface is not implemented is why notifications don't occur. `ViewModelBase` is incidental –  Jul 10 '18 at 06:43
  • 2
    Just to point out to anyone reading the answer, with less MVVM knowledge. The "ViewModelBase" in the above answer requires the interface INotifyPropertyChanged which allows the employee properties live one-way/two-way updates from the UI. This is facilitated by the "RaisePropertyChanged" method, which should fire the event. As for the issue with updates from a different thread. It should be as simple as "**Dispatcher.Invoke(() => { Method(); })**" – B.Spangenberg Jul 10 '18 at 06:44
  • @MickyD I answered using the resources the user has give in the code. He is already using ViewModelBase, that means he has knowledge about it and how to use it. A raw answered would have been as you pointed out. – Lupu Silviu Jul 10 '18 at 06:59
0

Try Application.Current.Dispatcher.BeginInvoke(() => /* update value */);

https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.begininvoke(v=vs.110).aspx

Edit: see this answer

am9417
  • 994
  • 8
  • 18