简体   繁体   中英

MVVM object fire property changed event when a field is changed

So I have an MVVM binding setup for an object. The object is of a class like the following:

public class SomeClass
{
  public int Field1 {get; set:}
  public List<MyClass> CollectionOfObjects {get; set;}
  public string Description {get; set;}
}

My MVVM class implements INotifyPropertyChanged ...

private SomeClass _viewableObject;
public SomeClass ViewableObject
{
    get
     {
       return _viewableObject;
     }
    set
     {
        _viewableObject = value;
        OnPropertyChanged("ViewableObject");
     }


} 

This works great in the case where I'm defining a new object. For instance:

 ViewableObject = new SomeClass();

But I'm binding UI components to fields of SomeClass . Field1 and Description get pushed to a textboxes and say CollectionOfObjects gets pushed to a DataGrid .

<Datagrid .... ItemsSource = "{Binding ViewableObject.CollectionOfObjects}" ></DataGrid>

So in the event that I update just ViewableObject.CollectionOfObjects and not the whole ViewableObject how can I get the UI to update to reflect the modification? Is there a cleaner way to do that rather than splitting up each field of SomeClass in the MVVM?

您可以创建自己的实现INotifyCollectionChanged的集合,但是您会发现使用已经完成的ObservableCollection更容易。

You have a couple of options here. The simplest of which is to convert CollectionOfObjects from a List<MyClass> to an ObservableCollection<MyClass> - as has already been mentioned.

Be aware, however, that this will only give the UI notification when new items are added to the ObservableCollection , or existing items are removed.

If you change the entire reference that CollectionOfObjects points to, then using an ObservableCollection is of no use. Instead, you will need to implement INotifyPropertyChanged on SomeClass and raise PropertyChanged events when each property value is set.

The resulting SomeClass would be look like this:

public class SomeClass : INotifyPropertyChanged
{

    public SomeClass()
    {
        CollectionOfObjects = new ObservableCollection<MyClass>();
    }

    public int Field1 
    {
        get 
        {
            return _field1;
        }
        set
        {
            if(_field1 != value)
            {

                _field1 = value;
                PropertyChanged(new PropertyChangedEventArgs("Field1"));
            }
        }
    }

    public ObservableCollection<MyClass> CollectionOfObjects 
    {
        get 
        {
            return _collectionOfObjects;
        }
        set
        {
            if(_collectionOfObjects != value)
            {

                _collectionOfObjects = value;
                PropertyChanged(new PropertyChangedEventArgs("CollectionOfObjects"));
            }
        }
    }

    public string Description
    {
        get 
        {
            return _description;
        }
        set
        {
            if(_description != value)
            {

                _description = value;
                PropertyChanged(new PropertyChangedEventArgs("Description"));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged = delegate {}

    private int _field1;
    private ObservableCollection<MyClass> _collectionOfObjects;
    private string _description;
}

Note that this is not strictly speaking a Model object any more. It's dependence on INotifyPropertyChanged and ObservableCollection make it a ViewModel. But, you already have one of those...

The choice is whether you can tolerate 'polluting' your model class to the point that it becomes a ViewModel. This is absolutely fine for some scenarios. In other cases, it is preferential to strictly separate the concerns of Model and ViewModel. At this point, you need introduce a mapper which is able to convert from Model to ViewModel - probably vice-versa, too. Perhaps the Model is a database record, or something else entirely. It might need to depend on other libraries, inherit from some layer-specific base class, or contain lots of domain logic.

A further benefit of strictly separating Model and ViewModel is that the two can vary independently. The Model might contain some properties that the UI - and therefore the ViewModel - need not contain. Or, the ViewModel might aggregate some properties into an average, or a total, etc. ViewModels should typically have properties that map to the user interface, whereas Models might not.

One final note. By introducing a mapper and properly separating Model and ViewModel, what would you call the existing ViewModel class that you have? I tend to refer to this as a Controller. The pattern, then, becomes MVC-VM: Model-View-Controller-ViewModel. The Controller remains the DataContext of a UI element but exposes a ViewModel property. The Controller should first load Model data (possibly via a repository interface) then ask a mapper to convert the Model to an MVVM-specific ViewModel.

If you're using Dependency Injection, then Controllers would accept service interfaces as constructor parameters but ViewModels would not have services as constructor parameters: they would be fairly dumb bags of data.

I'll reiterate that this can be overkill, but it is also sometimes the best option available.

Thank you everyone for the suggestions. The solution that I used is as follows:

public class SomeClass : ObservableCollection<Elements>
{
  private int _field1;

  public int Field1 
   {
    get
     {
      return _field1;
     }
    set
     {
      _field1 = value;
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
     }
   }

  private string _description;
  public string Description 
   {
    get
     {
      return _description;
     }
    set
     {
      _description= value;
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
     }
   }

  // remove this since the class is now the collection.
  // public List<MyClass> CollectionOfObjects {get; set;}
}

Now SomeClass extends ObservableCollection therefore has a CollectionChangedEvent

So in my MVVM class I added the following:

    private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        this.OnPropertyChanged("ViewableObject");

    }

private SomeClass _viewableObject;
public SomeClass ViewableObject
{
     get
     {
      return _viewableObject;
     }
     set
     {
      _viewableObject = value;

      if(value != null)
         _viewableObject.CollectionChanged += new NotifyCollectionChangedEventHandler(CollectionChanged)

      OnPropertyChanged("ViewableObject");
     }


} 

Considering the whole problem to start out with was that the only time the code from the MVVM would fire is when a new assignment happened the event handler is only called when needed. I think this is a simple way to approach it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM