NotifyPropertyChanged

Fabrício Zimmerer Murta edited this page Nov 14, 2017 · 13 revisions

Index

  1. NotifyPropertyChanged (Deck)
  2. Automatically Implement INotifyPropertyChanged (Deck)
  3. Properties That Depend On Other Members (Deck)
  4. Properties That Depend On Other Objects (Deck)
  5. Implementing INotifyPropertyChanged (Deck)
  6. Ignore Changes To Properties (Deck)
  7. Raise Event On Any Change (Deck)
  8. Handle Event Raising (Deck)
  9. Suspend And Resume Events (Deck)

NotifyPropertyChanged (Deck)

using System;
using System.ComponentModel;
using Bridge.Aspect;

namespace Demo
{
    public class App
    {
        public static void Main()
        {
            var person = new Person();

            person.PropertyChanged += Person_PropertyChanged;
            person.FirstName = "Frank";
            person.LastName = "Finch";
            
            person.FirstName = "Sally";
            person.LastName = "Summer";
        }

        private static void Person_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine(e.PropertyName);
            Console.WriteLine(" - Old Value: " + e.OldValue);
            Console.WriteLine(" - New Value: " + e.NewValue);
        }
    }

    public class Person : Base
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    
    [NotifyPropertyChanged(Inheritance = MulticastInheritance.All)]
    public class Base : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName, object newValue, object oldValue)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName, newValue, oldValue));
        }
    }
}

Output

FirstName
 - Old Value: 
 - New Value: Frank
LastName
 - Old Value: 
 - New Value: Finch
FirstName
 - Old Value: Frank
 - New Value: Sally
LastName
 - Old Value: Finch
 - New Value: Summer

Automatically Implement INotifyPropertyChanged (Deck)

This example demonstrates how to configure your class to implement the INotifyPropertyChanged interface.

using System;
using System.ComponentModel;
using Bridge;
using Bridge.Aspect;

namespace Demo
{
    [NotifyPropertyChanged]
    public class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public string FullName => $"{FirstName} {LastName}";
    }

    public class Program
    {
        public static void Main()
        {
            var customer = new Customer();
            customer.As<INotifyPropertyChanged>().PropertyChanged += Customer_PropertyChanged;
            customer.FirstName = "John";
            customer.LastName = "Doe";
        }

        private static void Customer_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine("Changed: " + e.PropertyName);
        }
    }
}

Output

Changed: FirstName
Changed: LastName

This example has added the [NotifyPropertyChanged] attribute to one class. If you need to implement [NotifyPropertyChanged] on many different classes in your codebase, please use aspect multicasting.

Since the INotifyPropertyChanged interface is implemented by Bridge.Aspect at run time, the interface will not be visible to Intellisense. The same is true for the PropertyChanged event.

There are two ways to access the INotifyPropertyChanged interface from your code:

  • You can cast your object to INotifyPropertyChanged.
((INotifyPropertyChanged) customer).PropertyChanged += Customer_OnPropertyChanged;
  • You can use the .As extension method. The benefit of using this method is that the cast operation will not be translated to JavaScript. You must ensure that aspect is applied to the instance.
customer.As<INotifyPropertyChanged>().PropertyChanged += Customer_PropertyChanged;

Properties That Depend On Other Members (Deck)

If a property depends on another property or field in the same class, then you can use the [NotificationDependency] attribute to instruct Bridge.Aspect to raise an event if one of the dependencies is changed.

using System;
using System.ComponentModel;
using Bridge;
using Bridge.Aspect;

namespace Demo
{
    [NotifyPropertyChanged]
    public class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        [NotificationDependency("FirstName")]
        [NotificationDependency("LastName")]
        public string FullName => $"{FirstName} {LastName}";
    }

    public class Program
    {
        public static void Main()
        {
            var customer = new Customer();
            customer.As<INotifyPropertyChanged>().PropertyChanged += Customer_PropertyChanged;
            customer.FirstName = "John";
            customer.LastName = "Doe";
        }

        private static void Customer_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine("Changed: " + e.PropertyName);
        }
    }
}

Output

Changed: FirstName
Changed: LastName
Changed: FullName

Since the FullName property has a getter only, the aspect cannot track changes. Therefore it is required to define dependencies which affect this property. The [NotificationDependency] attribute accepts the name of the field or property. You can apply multiple dependencies to one member.

Properties That Depend On Other Objects (Deck)

It is very common for the properties of one class to be dependent on the properties of another class. By default, the aspect doesn’t track changes in properties of another class to narrow events from particular class, therefore it is required to configure RaiseOnSubPropertiesChange property in the aspect.

using System;
using System.ComponentModel;
using Bridge;
using Bridge.Aspect;

namespace Demo
{
    [NotifyPropertyChanged]
    public class Contacts
    {
        public string Phone  { get; set; }
        public string Mobile { get; set; }
        public string Email  { get; set; }
    }

    [NotifyPropertyChanged(RaiseOnSubPropertiesChange = true)]
    public class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public Contacts Contacts
        {
            get; set;
        }
    }

    public class Program
    {
        public static void Main()
        {
            var customer = new Customer();
            customer.As<INotifyPropertyChanged>().PropertyChanged += Customer_PropertyChanged;
            customer.Contacts = new Contacts();
            customer.Contacts.Phone = "+1 800 123456";
            customer.Contacts.Email = "john@doe.com";
        }

        private static void Customer_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine("Changed: " + e.PropertyName);
        }
    }
}

Output

Changed: Contacts
Changed: Contacts.Phone
Changed: Contacts.Email

In this example, we set RaiseOnSubPropertiesChange = true for the NotifyPropertyChanged aspect of the Customer class. It instructs the aspect to track changes inside the Contacts instance. Please note that Contacts class must be also trackable by applying the NotifyPropertyChanged aspect to the class. The PropertyName of PropertyChangedEventArgs parameter will contain a dot to separate main and sub properties (such as, Contact.Phone)

If Contacts is updated by object initializer then such code is considered an atomic operation, only one message (Changed: Contacts) will be triggered. For example:

customer.Contacts = new Contacts
{
    Phone = "+1 800 123456",
    Email = "john@doe.com"
};

Implementing INotifyPropertyChanged (Deck)

The INotifyPropertyChanged interface can be implemented if you want to manually handle an event inside your class.

using System;
using System.ComponentModel;
using Bridge;
using Bridge.Aspect;

namespace Demo
{
    [NotifyPropertyChanged]
    public class Customer: INotifyPropertyChanged
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public Customer()
        {
            this.PropertyChanged += Customer_PropertyChanged;
        }

        private void Customer_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine("Changed: " + e.PropertyName);
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

    public class Program
    {
        public static void Main()
        {
            var customer = new Customer
            {
                FirstName = "John",
                LastName = "Doe"
            };
        }
    }
}

Output

Changed: FirstName
Changed: LastName

Ignore Changes To Properties (Deck)

Use the [IgnoreAutoChangeNotification] to prevent a PropertyChanged event from being invoked when setting a property.

[NotifyPropertyChanged]
public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    [IgnoreAutoChangeNotification]
    public string Country { get; set; }
}

Output

Changed: FirstName

Raise Event On Any Changes (Deck)

By default, aspect compares values before and after setter invocation and raises an event only if values are different. You can change this behaviour using the property RaiseOnChange = false.

using System;
using System.ComponentModel;
using Bridge;
using Bridge.Aspect;

namespace Demo
{
    [NotifyPropertyChanged]
    public class Customer: INotifyPropertyChanged
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public Customer()
        {
            this.PropertyChanged += Customer_PropertyChanged;
        }

        private void Customer_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine("Changed: " + e.PropertyName);
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

    public class Program
    {
        public static void Main()
        {
            var customer = new Customer();
            customer.FirstName = "John";
            customer.FirstName = "John";
        }
    }
}

Output

Changed: FirstName

As you see, we change FirstName twice with the same value, although only a single event will be raised. The aspect sees that the second value is the same. If set RaiseOnChange = false, then both changes will trigger the event. (Deck)

[NotifyPropertyChanged(RaiseOnChange = false)]

Output

Changed: FirstName
Changed: FirstName

Handle Event Raising (Deck)

You can extend the NotifyPropertyChangedAttribute class to implement your own custom logic. For example, in the following sample an event is swallowed for properties which accept a value with Object type.

using System;
using System.ComponentModel;
using Bridge;
using Bridge.Aspect;

namespace Demo
{
    public class MyNotifyPropertyChangedAttribute : NotifyPropertyChangedAttribute
    {
        protected override void BeforeEvent(NotifyPropertyChangedAspectEventArgs eventArgs)
        {
            if (eventArgs.Value == null || eventArgs.Value.GetType() == typeof(object))
            {
                Console.WriteLine($"The event for {eventArgs.PropertyName} is swallowed");
                eventArgs.Flow = AspectFlow.Return;
            }
        }
    }

    [MyNotifyPropertyChanged]
    public class Customer: INotifyPropertyChanged
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public object CustomProperty { get; set; }

        public Customer()
        {
            this.PropertyChanged += Customer_PropertyChanged;
        }

        private void Customer_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine("Changed: " + e.PropertyName);
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

    public class Program
    {
        public static void Main()
        {
            var customer = new Customer
            {
                FirstName = "John",
                CustomProperty = new object()
            };
        }
    }
}

Output

Changed: FirstName
The event for CustomProperty is swallowed

Suspend And Resume Events (Deck)

You can manually suspend events, make batch changes, then resume events and raise only one event manually.

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Bridge;
using Bridge.Aspect;

namespace Demo
{
    [NotifyPropertyChanged]
    public class Customer: INotifyPropertyChanged
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public Customer()
        {
            this.PropertyChanged += Customer_PropertyChanged;
        }

        private void Customer_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine("Changed: " + e.PropertyName);
        }

        public event PropertyChangedEventHandler PropertyChanged;

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

    public class Program
    {
        public static void Main()
        {
            var customer = new Customer();

            NotifyPropertyChangedAttribute.SuspendEvents();
            customer.FirstName = "John";
            customer.LastName = "Doe";
            NotifyPropertyChangedAttribute.ResumeEvents();

            customer.OnPropertyChanged(null);
        }
    }
}

Output

Changed: [none]