12

I'm just learning MVVM, and I'm trying to work how to display changes to a calculated property as a result of changes to the values from which it's calculated. All of the solutions I've seen so far seriously violate encapsulation, and I'm wondering whether there's anything better.

Suppose one of the things I need to display is the results of a complex tax calculation. The calculation (and possibly its dependencies) will change from time to time, so I would like to keep it strictly encapsulated.

The solution most often offered here seems to be to get all of the properties on which the tax value depends to invoke PropertyChanged in the ModelView for the property itself and for every property that depends on it. That means that every property needs to know everything that uses or might use it. When my tax rules change in a way that makes the calculation dependent on things it was not previously dependent on, I will need to touch all those new properties that go into my calculation (possibly in other classes, possibly not under my control), to get them to invoke PropertyChanged for the tax value. That completely trashes any hope of encapsulation.

The best solution I can think of is to make the class that does the calculation receive PropertyChanged events, and raise a new PropertyChanged event for the tax value when anything changes that goes into the calculation. That at least preserves encapsulation at class level, but it still breaches method encapsulation: the class shouldn't have to know about how a method does its work.

So, my question is, is there a better way (and if so, what is it)? Or does encapsulation of presentation (MVVM) prevent encapsulation of the business logic? Am I faced with an either/or choice?

digitig
  • 1,989
  • 3
  • 25
  • 45
  • I don't know that there's a "right/wrong" answer here. Also, is there a huge concern of encapsulation within the ViewModel? The VM is still encapsulated and is a "black box" from the View's perspective regardless of how you go about this. – hall.stephenk Oct 25 '13 at 13:56
  • My worry about encapsulation is a worry about scalability. I'm really thinking of the case with a complicated UI and so multiple ViewModels. – digitig Oct 26 '13 at 19:47

5 Answers5

4

The solution most often offered here seems to be to get all of the properties on which the tax value depends to invoke PropertyChanged in the ModelView for the property itself and for every property that depends on it.

No the supporting properties do not need their own change notification unless they are being displayed. But each property will need to call the tax value's OnPropertyChanged("TaxValue") in their setter(s) either directly; or indirectly as per the example below. That way the UI gets updated because a supporting property has changed.

With that said, let us consider an example. One way is to create a method which will do the value calculation. When the ultimate value is set (TaxValue below) it will call OnNotifyPropertyChange. That operation will inform the user of the TaxValue change to the whole world; regardless of what value triggers it (Deduction|Rate|Income):

public class MainpageVM : INotifyPropertyChanged 
{
       public decimal TaxValue 
        {
           get { return _taxValue; }
           set { _taxValue = value; OnPropertyChanged(); }  // Using .Net 4.5 caller member method.
        }

        public decimal Deduction
        {
           get { return _deduction; }
           set { _deduction = value; FigureTax(); }
        }

        public decimal Rate
        {
           get { return _rate; }
           set { _rate = value; FigureTax(); }
        }

        public decimal Income
        {
           get { return _income; }
           set { _income = value; FigureTax(); }
        }

        // Something has changed figure the tax and update the user.
        private void FigureTax()
        {
           TaxValue = (Income - Deduction) * Rate;
        }


    #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>Raises the PropertyChanged event.</summary>
        /// <param name="propertyName">The name of the property that has changed, only needed
        /// if called from a different source.</param>
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

    #endif
    }

Edit

To use CallerMemberName (and other items) in .Net 4 install the Nuget package:

Microsoft.BCL.

or if not use the standard OnPropetyChanged("TaxValue") instead.

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
  • 1
    +1: Maybe worth mentioning: with an update you can use the .net 4.5 OnPropertyChanged property name inference in .net 4.0/Vs 2010. It's not in 4.0 out of the box. –  Oct 25 '13 at 14:24
  • @jdv-JandeVaan My bad...I edited the post to reflect how to get it in .Net 4. Thanks! – ΩmegaMan Oct 25 '13 at 15:48
  • 1
    +1 fine answer. I would add the class declaration to show the inheritance of INPC, but otherwise classic MVVM. – Gayot Fow Oct 25 '13 at 17:13
  • 2
    That doesn't seem to help. If FigureTax becomes dependent on some new property, the code for that property's setter still needs to be changed so it now invokes FigureTax. I can do that if they're all under my control (although it's trouble waiting to happen if I miss one -- the last 20 years of my programming experience has been learning ways of making such things impossible!) In a complicated case, though, there could be multiple ViewModels handling different parts of the UI, so I might not be able to change the setters. – digitig Oct 25 '13 at 17:13
  • @digitig You describe an automatic system. To that end, the other way would be to create a timer and then reflect off of the class for properties and within the timer have a cache of values for each of the properties. When one changes call PropertyChanged on Tax value. So MVVM PropertyChanged is a push system and the other, as just described, would be a pull system. Its up to you to choose which is the lesser of two evils. :-) – ΩmegaMan Oct 25 '13 at 17:42
  • Explicitly firing notifications for dependent property from trigger property setter becomes a maintenance nightmare in long run. It is basically cyclic dependency and it violates DRY in a subtle way. Modern UI libraries shifted towards reactive style which is good, but Rx has a bit of a learning curve. However CalculatedProperties which I mentioned in answer above are very simple and good for many use cases – KolA Nov 08 '17 at 00:36
4

Check out Stephen Cleary's Calculated Properties: https://github.com/StephenCleary/CalculatedProperties

It's very simple and does just this: propagates notifications of dependant properties without polluting the trigger property setter.

Primitive example:

public string Name 
{
  get { return Property.Get(string.Empty); }
  set { Property.Set(value); }
} 

public string Greeting => Property.Calculated(() => "Hello, " + Name + "!");

It is incredibly powerful for its size: think Excel-like formula engine for View Model properties.

I used it in several projects both in domain and view model classes, it helped me to eliminate most of imperative control flow (a major source of errors) and make code much more declarative and clear.

The best thing about it is that dependent properties can belong to different view models and dependency graph can change dramatically during runtime and it still just works.

KolA
  • 707
  • 7
  • 15
3

There is an addin called Fody/PropertyChanged that works at compile-time to automatically implement PropertyChanged. It will automatically see what properties in the same class make use of your property and raise all appropriate PropertyChanged events when that one complex tax calculation changes.

You can decompile the compiled code with ILSpy to see what it did and verify that it's raising all appropriate events.

Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • Interesting -- it looks as if I'm not the only one with this concern, then, and a solution is possible. – digitig Oct 25 '13 at 17:49
  • Fody, PostSharp don't support dependency graphs that is known or change at runtime (e.g. Total is a sum of list of child view model's subtotals). https://github.com/StephenCleary/CalculatedProperties however can do it with no additional boilerplate – KolA Nov 08 '17 at 00:53
3

The solution most often offered here seems to be to get all of the properties on which the tax value depends to invoke PropertyChanged in the ModelView for the property itself and for every property that depends on it. ....

Yes, but only for that object: Each property should fire its own property change event in the setter. Additionally the setter should somehow trigger properties that depend on that value on itself. You should not try to trigger updates on other objects proactively: they should listen to this objects PropertyChanged.

The best solution I can think of is to make the class that does the calculation receive PropertyChanged events, and raise a new PropertyChanged event for the tax value when anything changes that goes into the calculation. That at least preserves encapsulation at class level, but it still breaches method encapsulation: the class shouldn't have to know about how a method does its work.

This is indeed the standard way. Each class has the responsibility to monitor the properties that it depends on, and fire property change events for the properties it.

There are probably frameworks that will help to do this for you, but it is worthwhile to know what should happen.

  • Ok, I can see that in a complicated case the handler could end up with a big switch statement on the possible changed properties, invoking PropertyChanged on each property they can affect. That sounds like a maintenance bad dream even if it's not quite a nightmare. I suppose the answer to that is to refactor the property with a complex calculation into its own class, so the PropertyChanged handler only has to deal with that one property, not the (potentially) many calculated properties the containing class might have. – digitig Oct 25 '13 at 17:57
0

The best solution I can think of is to make the class that does the calculation receive PropertyChanged events, and raise a new PropertyChanged event for the tax value when anything changes that goes into the calculation. That at least preserves encapsulation at class level, but it still breaches method encapsulation: the class shouldn't have to know about how a method does its work.

I think you're extending the term "encapsulation" to the point of quibbling about syntax. There is no issue here, for instance:

private int _methodXCalls;
public void MethodX() {
    Console.WriteLine("MethodX called {0} times", ++_methodXCalls);
}

The field is relevant only within MethodX, but just because the declaration is not syntactically inside MethodX does not mean it breaks method encapsulation.

Likewise, there is no issue with setting up event handlers for each of your properties in the class initialization. As long as it only appears once at initialization, and nothing else is required to "know" that those particular handlers were added, your properties are still logically self-contained. You could probably somehow use attributes on the properties, e.g. [DependsOn(property1, property2)], but this is really just a code readability concern.

nmclean
  • 7,564
  • 2
  • 28
  • 37
  • I don't see that as a particularly close analogy. I'm more interested in the pragmatic matter of avoiding bugs than about doctrinal issues of encapsulation purity. Other users of _methodXCalls -- maintainers of the class to which it belongs -- don't care how MethodX keeps it updated, only that it does. And if they have to change MethodX, they don't need to know what else uses it. Changes don't have to be propagated across the boundary. – digitig Oct 25 '13 at 17:48
  • @digitig It is very close; in fact, it is not even an "analogy", merely another example of the same concept: initialization. The only difference is the fact that fields can be initialized at a class level while event subscription must be placed in a constructor. This is primarily nothing but a syntax limitation. In both cases there is no "boundary crossing": Other users of the properties `Tax` is dependent on don't care about the event subscriptions, while `Tax` itself and its initialization does not care about what updates the properties, only that they update. Where is the problem? – nmclean Oct 25 '13 at 19:48
  • The problem is a feeling for where I'm likely to make mistakes when I come to maintain code a few years down the line. I'm quite likely to foul up the updating of complicated dependencies, but I'm only likely to foul up the use of _methodXCalls after a particularly heavy all-nighter. – digitig Oct 26 '13 at 20:05