Wednesday, November 30, 2011

P2PMapper

If you're working with MVVM, you're probably familiar with the case in which one property depends on another property. 
For instance, consider the following 'User Registration' form. 




In this registration form, the 'SMS' checkbox is enabled only if the user entered a phone number and checked the 'Receive Messages' checkbox. The same goes for the 'Mail' checkbox - it is enabled only if the user entered an email address and checked the 'Receive Messages' checkbox.

To implement the above behavior you'll probably create the following properties in the ViewModel and bind them to the 'IsEnabled' of the 'SMS' and 'Mail' checkboxes:

        public bool EnableSMS
        {
            get
            {
                return ReceiveMessages && !string.IsNullOrEmpty(Phone);
            }
        }

        public bool EnableMail
        {
            get
            {
                return ReceiveMessages && !string.IsNullOrEmpty(Mail);
            }
        }

But now, 'EnableSMS' depends on 2 other properties - 'ReceiveMessage' and 'Phone'. Whenever these 2 properties are changing - the UI needs to be notified that the 'EnableSMS' has changed too. The same goes for 'EnableMail'.
So, to support that, you probably have the following code:

        public string Phone
        {
            get
            {
                return _phone;
            }

            set
            {
                _phone = value;
                NotifyPropertyChanged("Phone");
                NotifyPropertyChanged("EnableSMS");
            }
        }

        public string Mail
        {
            get
            {
                return _mail;
            }

            set
            {
                _mail = value;
                NotifyPropertyChanged("Mail");
                NotifyPropertyChanged("EnableMail");
            }
        }

        public bool ReceiveMessages
        {
            get
            {
                return _receiveMessages;
            }

            set
            {
                _receiveMessages = value;
                NotifyPropertyChanged("ReceiveMessages");
                NotifyPropertyChanged("EnableSMS");
                NotifyPropertyChanged("EnableMail");
            }
        }

I guess you will agree with me that the code is starting to get messy...

Now, there are several tools that can handle this situation, like Update ControlNotifyPropertyWeaver or this one and some of them are doing it quite well.

But as far as i know, none of the above tools supports the case in which one property depends on a graph of objects.
For example: the company's total salaries would be the sum of all Company.Employees.Salary.Amount.
For instance, consider the following 'Edit Salaries' screen:


The ViewModel of this screen has a 'Company' property. The grid is bound to Company.Employees and the Salary column is bound to 'Employee.Salary.Amount'.
The Total Salaries field in the bottom of the screen would be bound to a property called 'TotalSalaries' on the ViewModel and it will look like this:

        public double TotalSalaires 
        {
            get
            {
                return Company.Employees.Sum(x => x.Salary.Amount);
            }
        }


Now, if for some reason the 'Company' property would change - 'TotalSalaries' would change as well. But the UI wouldn't know about it because it has no way to know that 'TotalSalaries' depends on 'Company'.
And what if 'Company.Employess' would change? What if the 'Salary' of an employee would change, or the 'Amount' of a salary would change? The UI wouldn't know that 'TotalSalaries' needs to be re-invoked.

For this I've came with a solution called: P2PMapper which stands for: Property to Property Mapper.
Let's see how it works.
In the first example above i would use it like this:

            P2PMapper<UserRegViewModel>.Init(this)
                .Couple(x => x.EnableSMS)
                .With(x => x.ReceiveMessages)
                .With(x => x.Phone)
                .Couple(x => x.EnableMail)
                .With(x => x.ReceiveMessages)
                .With(x => x.Mail);

This code can be set in the ViewModel's constructor.
I pass the class of the ViewModel as a generics parameter.
Then i pass the instance of the ViewModel to the Init method.
Then i Couple the 'EnablePhone' property (of the ViewModel) With the 'ReceiveMessage' and 'Phone' properties and so forth.

Of course that,
NotifyPropertyChanged("EnableSMS");
and
NotifyPropertyChanged("EnableMail");
can now be removed from anywhere in the code.

In the second example above i would use it like this:

            P2PMapper<CompanyDetailsViewModel>.Init(this)
                .Couple(x => x.TotalSalaires)
                .With(x => x.Company.Employees[0].Salary.Amount);
Now, whenever there is a change down the path of Company.Employees.Salary.Amount - the P2PMapper will track it down and notify the UI about it.

NOTE: x.Company.Employees[0] is only a syntactic way to get down the path, it doesn't really mean 'the first employee'.


NOTE: each object down the path, including the ViewModel itself, needs to implement the interface P2PMapper.INotifier. Also, each property down the path must call NotifiyPropertyChanged when ever it is changing.


The sample code, including the P2PMapper.dll can be found here

No comments:

Post a Comment