C# MVVM Calculating Total
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 decim开发者_Python百科al 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.
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.
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.
private void UpdateValue()
{
this.Value = this.Acres * this.YieldPerAcre * this.UnitPrice;
}
public double Acres
{
get { return _cropProductionRecord.Acres; }
set
{
_cropProductionRecord.Acres = value;
OnPropertyChanged("Acres");
UpdateValue();
}
}
public double YieldPerAcre
{
get { return _cropProductionRecord.YieldPerAcre; }
set
{
_cropProductionRecord.YieldPerAcre = value;
OnPropertyChanged("YieldPerAcre");
UpdateValue();
}
}
public double UnitPrice
{
get { return _cropProductionRecord.UnitPrice; }
set
{
_cropProductionRecord.UnitPrice = value;
OnPropertyChanged("UnitPrice");
UpdateValue();
}
}
public double Value
{
get { return _cropProductionRecord.Value; }
set
{
_cropProductionRecord.Value = value;
OnPropertyChanged("Value");
}
}
精彩评论