0

I have a Car class.

class Car
{
  string ModelNum;
}

Then I have Car instance

class CarInstance
{
  string RegistrationNum;
}

There are several instances of Cars in my viewmodel.

 ViewModel(Car car)
 {
    CarInstance Ins = car.GetInstance();
 }

Question: Say that the Car itself is changing and it need to notify the view models of that change. What is the most efficient way to do this. I am aware I can use events(including eventaggregator in PRISM). I would like to know if there is any faster way to do this.

An Action parameter which can call multiple subscribers? Any such ideas?

All pseudocode.

Jimmy
  • 3,224
  • 5
  • 29
  • 47
  • 2
    Have you came across INotifyPropertyChanged? – Ali Baig Jan 23 '17 at 15:27
  • Yes. Two questions. Is that more performing than events? Is it acceptable to put the property notification in Car class(which is a business object and notify property changed is a UI specific idea)? – Jimmy Jan 23 '17 at 15:30
  • 2
    I believe yes. Also yeah its very normal putting the property notifications in your model classes like Car you have. These events are raised from the setter of your model properties. You can also receive notifications from your ViewModel but having them in your property setters is important if you are going to bind those on your UI – Ali Baig Jan 23 '17 at 15:36

3 Answers3

1

Is INotifyPropertyChanged more performing than events?

The INotifyPropertyChanged interface defines a PropertyChanged event, i.e. it actually uses an event to notify the view.

Say that the Car itself is changing and it need to notify the view models of that change. What is the most efficient way to do this. I am aware I can use events(including eventaggregator in PRISM). I would like to know if there is any faster way to do this.

Raising an event will be very minor in terms of performance in most cases so you shouldn't worry about this:

How much performance overhead is there in using events?

The benefit of using an event aggregator is that your classes become more loosly coupled to each other compared to keeping a direct reference to each subscriber from each publisher class: https://blog.magnusmontin.net/2014/02/28/using-the-event-aggregator-pattern-to-communicate-between-view-models/

But raising an event, either using strong references or an event aggregator, is a good, recommended and fast way of notifying the outside world of a change.

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

Its absolute normal to put the property changed notifications in your models if they are bound to your views. It is the preferred approach over anything in MVVM. This is how you can do it

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

public class Car: INotifyPropertyChanged
{
    #region Property Changed
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion

    private string _modelNum;

    public string ModelNum 
    {
        get{ return _modelNum; }
        set 
        { 
            _modelNum = value; 
            //this will raise the notification
            OnPropertyChanged("ModelNum"); 
        }
    }
}

If this car object is bound to your View, You can

Text="{Binding Car.ModelNum, UpdateSourceTrigger=PropertyChanged}"

UpdateSourceTrigger=PropertyChanged will update the UI whenever ModelName is updated or will update the ModelName whenever you update from UI.

Ali Baig
  • 3,819
  • 4
  • 34
  • 47
0

Create the following class for notiyfing:

public abstract class Notifier : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();

    protected T Get<T>([CallerMemberName] string name = null)
    {
        Debug.Assert(name != null, "name != null");
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T)value;
        return default(T);
    }
    protected void Set<T>(T value, [CallerMemberName] string name = null)
    {
        Debug.Assert(name != null, "name != null");
        if (Equals(value, Get<T>(name)))
            return;
        _properties[name] = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
    public void Notify<T>(Expression<Func<T>> memberExpression)
    {
        if (memberExpression == null)
        {
            throw new ArgumentNullException("memberExpression");
        }
        var body = memberExpression.Body as MemberExpression;
        if (body == null)
        {
            throw new ArgumentException("Lambda must return a property.");
        }

        var vmExpression = body.Expression as ConstantExpression;
        if (vmExpression != null)
        {
            LambdaExpression lambda = Expression.Lambda(vmExpression);
            Delegate vmFunc = lambda.Compile();
            object sender = vmFunc.DynamicInvoke();

            PropertyChanged?.Invoke(sender, new PropertyChangedEventArgs(body.Member.Name));
        }
    }
}

Your ViewModel should look something like this.

public class ViewModel : Notifier
{
    public ObservableCollection<String> Messages
    {
        get { return Get<ObservableCollection<String>>(); }
        set
        {
            Set(value); 
            Notify(() => AreThereMessages);
        }
    }

    public bool AreThereMessages => Messages?.Count > 0;
}

You don't need any private variables with the Notifier class above.

Getter: get { return Get<T>(); }

Setter: get { Set(value); }

Notify other property: Notify(() => OtherProperty);

Example view:

<DataGrid ItemsSource="{Binding Messages}"/>
<Button IsEnabled="{Binding AreThereMessages}"/>

The code above notifies a button if the messages collection is empty or not. This is the easiest way I know to handle your viewmodel without additional libraries.

remarkies
  • 113
  • 10