views:

82

answers:

2

I need to calculate a trade value based on the selected price and quantity. How can

The following is my ViewModel:

class ViewModel : ViewModelBase
{
    public Trade Trade
    {
        get { return _trade; }
        set { SetField(ref _trade, value, () => Trade); }
    } private Trade _trade;

    public decimal TradeValue
    {
        get { return Trade.Amount * Trade.Price; }
    } 

}

ViewModelBase inherits INotifyPropertyChanged and contains SetField()

The Following is the Trade class:

public class Trade : INotifyPropertyChaged
{
    public virtual Decimal Amount
    {
        get { return _amount; }
        set { SetField(ref _amount, value, () => Amount); }
    } private Decimal _amount;
    public virtual Decimal Price
    {
        get { return _price; }
        set { SetField(ref _price, value, () => Price); }
    } private Decimal _price;
    ......
}

I know due to the design my TradeValue only gets calculated once (when its first requested) and UI doesn't get updated when amount/price changes. What is the best way of achieving this?

Any help greatly appreciated.

Update: INotifyPropertyChanged implementation:

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        var body = selectorExpression.Body as MemberExpression;
        if (body == null)
            throw new ArgumentException("The body must be a member expression");
        OnPropertyChanged(body.Member.Name);
    }
    protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(selectorExpression);
        return true;
    }

Both answers work, now I don't know which one to select as best answer. Luke's answer seems better as there seems to be less code repetition, although simply listening for the OnPropertyChanged event seems more lightweight.

+1  A: 

Subscribe to PropertyChanged event on the Trade class in the set method for the Trade property in your VM class. In that eventhandler, post a propertychanged for property TradeValue.

Wallstreet Programmer
This seems like a nicer solution. Subscribe to PropertyChanged on Trade class, and if its Trade or Amount, call the OnPropertyChanged event for the TradeValue field.
LnDCobra
Yes that is what I meant at least, notify clients that they need to get TradeValue since either Price or Amount has changed.
Wallstreet Programmer
I generally take this approach, only I give the 'computed' property (TradeValue in this case) a private setter which encapsulates the notification. This makes it clear that the notification is triggered by an update to the computed value and has the added side benefit of caching the computed value until the next underlying property change.
Dan Bryant
+2  A: 

this is how i usually do it:

private bool has_inited_tradevalue_linkage = false;
public decimal TradeValue
{
    get 
    { 
        if(!has_inited_tradevalue_linkage)
        {
            has_inited_tradevalue_linkage = true;
            this.PropertyChanged += (_, e) => 
            {
                if(e.PropertyName.Equals("Amount") || e.PropertyName.Equals("Price"))
                    OnPropertyChanged("TradeValue");
            };
        }
        return Trade.Amount * Trade.Price; }
}

this way the linkage is constructed lazily (so you don't post events no one is interested in), and all the logic is still contained within this property. Conversely you could just add calls to OnPropertyChanged("TradeValue") in the setters of Price and Amount, but that 'dirtys' up the rest of the class a little bit (at least in my eyes). I have used this pattern a lot very successfully, to create similar 'dependent' properties.

Another option would be just to specifically track the dependencies:

in ViewModelBase you could add this:

private Dictionary> DependencyMap = new Dictionary>(); protected void AddPropertyDependency(String prop, String dependantProp) { if(DependencyMap.ContainsKey(prop)) { DependencyMap[prop].Add(dependantProp); } else { DependencyMap[prop] = new List{dependantProp}; } }

then in your OnPropertyChanged method:

protected void OnPropertyChanged(String prop)
{
    var eh = PropertyChanged;
    if(eh != null)
    {
        eh(this, new PropertyChangedEventArgs(prop);
        if(DependencyMap.ContainsKey(prop))
        {
            foreach(var p in DependencyMap[prop])
                OnPropertyChanged(p);//recursive call would allow for arbitrary dependencies
        }
    }
}

then in your class constructor you would just define your property dependencies:

public ViewModel()
{
    AddPropertyDependency("Amount", "TradeValue");
    AddPropertyDependency("Price", "TradeValue");
}

this is definitely a more general solution and most of the changes are in your base class.

luke
But this variant dirtys up the class as well. Having some boolean values inside the class that determines if a value should be refreshed new is not good in my eyes. Additionally this variant is not that good for readability.
KroaX
Where in the code is this listening for property changes in the trade object? Just can't get my head around the updating bit (I understand what the list is for, etc)
LnDCobra