Click here to Skip to main content
Click here to Skip to main content

WPF: The calculated property dependency problem

By , 30 Apr 2012
Rate this:
Please Sign up or sign in to vote.

Introduction

Model-View-ViewModel (MVVM) is one of the new patterns (well maybe old now!) used to separate UI layers from model layers in applications. It's ubiquitously used in WPF and takes advantage of the extensive data binding support offered. While it's a wonderful separation pattern it certainly does have it drawbacks. For example, capturing non routed events on the view model. Developers have found numerous workarounds for issues like this including using attached properties and event-to-command wrappers just to name two of them.  In this article I'm going to speak about another nuance of the MVVM pattern; namely binding to calculated properties and updating the view when calculations change. I'm also crazy enough to propose a solution.

Background 

Step 1: Let's look into the abyss shall we!  

Below you will find a very simple ViewModel. Let's assume that a View is bound to it and the RaisePropertyChanged method is implemented correctly. Let's also assume that a WPF TextBlock on the View is bounded to the Name property.  

public class ViewModel: INotifyPropertyChanged
{
    private string name = String.Empty;
    private DateTime birthDate;
        
    public string Name
    {
        get
        {
           return name;
        }

        set
        {
            name = value;
            RaisePropertyChanged("Name");
        }
    }
}

If I now change the Name property in code, the TextBlock's Text will change since I called the RaisePropertyChanged method which raises the PropertyChanged event. Indeed this is the ideal case and is how view model property binding and notification works. 

Step 2: Let's climb down into the abyss

Let's modify our view model as follows:

public class ViewModel: INotifyPropertyChanged
{
    private string name = String.Empty;
    
    public string Name
    {
        get
        {
           return name;
        }

        set
        {
            name = value;
            RaisePropertyChanged("Name");
        }
    }

    public DateTime BirthDate
    {
        get
        {
           return birthDate;
        }

        set
        {
            birthDate= value;
            RaisePropertyChanged("BirthDate");
        }
    }


    public int Age
    {
        get
        {
           return DateTime.Today.Year-BirthDate.Year;
        }    
    }
}

Let's assume that in the second ViewModel a TextBlock on the View is bounded to Age, a calculated property. Careful readers will notice that when BirthDate is updated, the TextBlock on the view bound to Age will not update. This is because the view does not know that Age has changed (we never told it). Age also does not have a backing property! A solution to this issue is deceptively simple:

public class ViewModel: INotifyPropertyChanged
{
    private string name = String.Empty;
    
    public string Name
    {
        get
        {
           return name;
        }

        set
        {
            name = value;
            RaisePropertyChanged("Name");
        }
    }

    public DateTime BirthDate
    {
        get
        {
           return birthDate;
        }

        set
        {
            birthDate= value;
            RaisePropertyChanged("BirthDate");
            RaisePropertyChanged("Age");
        }
    }

    public int Age
    {
        get
        {
           return DateTime.Today.Year-BirthDate.Year;
        }    
    }

}

By calling RaisePropertyChanged("Age") in the setter of BirthDate the view will query Age and hence retrieve the correct value.  

While this solution works in this extremely simple case it will soon become a nightmare when we have numerous calculated properties with no setters. Calculated properties may even depend on each other! This will result in calling the RaisePropertyChanged method all over your code just to get the view and the view model to sync. Suddenly we are in the abyss and it's dark.

Step 3: Staring into the abyss (and not being afraid)

At this point the abyss is telling us to accept our fate (the solution above) and move on. But that's not who we are. We are warriors with sharp swords (c sharp, that is).

We need a solution to this issue which we can reuse across different view models. The last thing we need is to litter our code with property changed notification calls. What we need is a way to tell our view model that if a certain property changes then we should also raise a property changed event of any property that depends on it. We need a kind of parent-child property thing-y.

Step 4: Fighting the abyss (does that even make sense?)

Let's modify our view model as follows: 

public class ViewModel: INotifyPropertyChanged
{
    private string name = String.Empty;
    
    public string Name
    {
        get
        {
           return name;
        }

        set
        {
            name = value;
            RaisePropertyChanged("Name");
        }
    }

    [DependentProperties("Age")]
    public DateTime BirthDate
    {
        get
        {
           return birthDate;
        }

        set
        {
            birthDate= value;
            RaisePropertyChanged("BirthDate");
            RaisePropertyChanged("Age");
        }
    }

    public int Age
    {
        get
        {
           return DateTime.Today.Year-BirthDate.Year;
        }    
    }
}

Notice that I have annotated the Birthdate property with an attribute called DependentProperties. I have also removed the RaisePropertyChanged method call from the setter of BirthDate.

Using the mechanism we can say that whenever BirthDate is updated the property changed event of Age should also be raised. The view will be notified that Age has 'changed' and query it. The view and the view model will be in sync.

Benefits  

  • A higher level of abstraction when dealing with calculated properties
  • Less code to write
  • Less code to maintain 
  • No 'tracking down' of which properties depend on each other.
  • A solution to the calculated property dependency problem in WPF.

Step 5: Punching the abyss in the gut (aka the implementation)!

Let's describe the DependentProperties attribute class.

public class DependentPropertiesAttribute: Attribute
{
    private readonly string[] properties;

    public DependentPropertiesAttribute(params string[] dp)
    {
        properties = dp;
    }

    public string[] Properties
    {
        get 
        {
            return properties;
        }
    }
}

It derives from Sytem.Attribute and has a simple Properties field. This field holds all the dependent properties (properties that need to be re-queried when the annotated property value changes).

Now let's look at who consumes the DependentProperties attribute.

The magic is in the RaiseProperty procedure:

public class ViewModelBase: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaiseProperty(string propertyName, List<string> calledProperties = null)
    {
        RaisePropertyChanged(propertyName);

        if (calledProperties == null)
        {
            calledProperties = new List<string>();
        }
     
        calledProperties.Add(propertyName);
      
        PropertyInfo pInfo =  GetType().GetProperty(propertyName);

       if (pInfo != null)
       {
           foreach (DependentPropertiesAttribute ca in 
             pInfo.GetCustomAttributes(false).OfType<dependentpropertiesattribute>())
           {
               if (ca.Properties != null)
               {
                   foreach (string prop in ca.Properties)
                   {
                       if (prop != propertyName && !calledProperties.Contains(prop))
                       {
                           RaiseProperty(prop, calledProperties);
                       }
                   }
               }
           }
       }
    }

    private void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
           PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Step 5: Making the abyss beg for mercy

The algorithm, although it looks complex, it quite simple:

  1. Call RaiseProperty method for a property.
  2. Get all the dependent properties for that property.
  3. Call RaiseProperty on them as well.
  4. Avoid stack overflow exceptions from properties who depend on each other either directly or indirectly by keeping a list of called properties.

Step 6: Climbing out from the abyss

At the this you survived the abyss. You are much stronger than it!

Points of Interest 

Although I feel that this solution is elegant it still relies on reflection to get the dependent properties. Also a list of called properties must be maintained per top level RaiseProperty call. This solution can be used when performance is not an issue. I believe the small performance hit due to reflection far outweighs the complexity of dealing with dependant and calculated properties. You have the option of also using a hybrid approach. For example, for calculated and dependent properties call the RaiseProperty method. For properties that do not need this just call the general RaisePropertyChanged event.

The solution can also be optimized to cache the dependent properties whereby avoiding the reflection call.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

FatCatProgrammer
Software Developer (Senior) Finance Industry
United States United States
Currently pursuing 'Programming Nirvana' (The ineffable ultimate in which one has attained disinterested wisdom and compassion as it relates to programming)
 
Acknowledgements:
 
Microsoft Certified Technologist for WPF and .Net 3.5 (MCTS)
Microsoft Certified Technologist for WCF and .Net 3.5 (MCTS)
Microsoft Certified Application Developer for .Net (MCAD)
Microsoft Certified Systems Engineer (MCSE)
Microsoft Certified Professional (MCP)
 
Sun Certified Developer for Java 2 Platform (SCD)
Sun Certified Programmer for Java 2 Platform (SCP)
Sun Certified Web Component Developer (SCWCD)
 
CompTIA A+ Certified Professional
 
Registered Business School Teacher for Computer Programming and Computer Applications (2004)
(University of the State of New York Education Department)
 
Graduated from University At Stony Brook

Comments and Discussions

 
Questiongetting rid of the attributes as well using a runtime dependency graph and code injection - an idea? Pinmemberzaphoed8-Jan-14 7:58 
QuestionClarification Request PinmemberJoseph S. Keller27-Sep-13 6:31 
QuestionI like were u going PinmemberSk8tz30-Apr-12 7:22 
AnswerRe: I like were u going PinmemberFatCatProgrammer1-May-12 7:32 
QuestionAlmost perfect... PinmvpPaulo Zemek28-Apr-12 7:25 
GeneralRe: Almost perfect... - Agreed Pinmembercrashedapp28-Apr-12 15:02 
AnswerRe: Almost perfect... PinmemberFatCat_Programmer28-Apr-12 17:26 
GeneralRe: Almost perfect... PinmvpPaulo Zemek29-Apr-12 3:13 
GeneralRe: Almost perfect... PinmemberFatCatProgrammer29-Apr-12 8:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 30 Apr 2012
Article Copyright 2012 by FatCatProgrammer
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid