// CalculatorState and CalculatorLogic is your model/BL and it can be made shareable. Any calculation can take your model and modify it with a result or with an error message (divide by zero etc)
// this is. It represents the entire I/O signature of any calculationyour business logic (calculation on a calculator), you cancould expand it to support new features, such as history etc, with minimum refactoring of other code.
CalculatorWindow/xaml is your UI, this is the visible part of your code
CalculatorVM is the glue between UI and model.
// model class
public class CalculatorState : INotifyPropertyChanged /*IMPLEMENT, ON ALL PROPERTIES */ {
public bool IsError{get;set;}
public string ErrorMessage{get;set;}
// Value is what's on the calculator screen under normal conditions
public double Value {get;set;}
// the calculator's memory
private double? mem;
public double Mem {
get { return mem.GetValueOrDefault(Value); }
set { mem = value; }
}
}
// business logic
public static class CalculatorLogic{
public static readonly Action<CalculatorState, double?> Add = (state,prm)=>state.Value = state.Mem + state.Value;
public static readonly Action<CalculatorState, double?> Sub = (state,prm)=>state.Value = state.Mem - state.Value;
}
// VM component
public class CalculatorCommand: ICommand<double?>{
public CalculatorState State {get;set;}
public readonly Action<CalculatorState, double?> Calculate;
public readonly bool IsTwoOpCommand;
public CalculatorCommand(Action<CalculatorState, double?> calculate, CalculatorState state = null, bool isTwoOpCommand = true){
Calculate = calculate;
State = state;
IsTwoOpCommand = isTwoOpCommand;
}
public void Execute(double? prm){
if (State!=null){
if (Calculate!=null){
// for two-op commands without a Mem put the Value in Mem
if (!IsTwoOpCommand || State.Mem.HasValue)
Calculate(State);
else
State.Mem = State.Value;
} else {
State.IsError = true;
State.ErrorMessage = "Null function";
}
} else // throw if you wish
Debug.WriteLine("Unexpected empty state");
}
}
// View-Model, links your UI to the model
public CalculatorVM : INotifyPropertyChanged {
public readonly CalculatorState State;
public readonly ICommand AddCommand;
public readonly ICommand SubCommand;
....
public readonly ICommand NumberCommand;
public CalculatorVM(CalculatorState state){
State = state;
NumberCommand = new CalculatorCommand(c,p=>c.Value = c.Value*10 + p, State, false);
SubCommand = new CalculatorCommand(c,p=>c.Value = c.Mem - cCalculatorLogic.Value,Sub State);
AddCommand = new CalculatorCommand(c,p=>c.Value = c.Mem + cCalculatorLogic.ValueAdd, State);
}
}
// View (UI). If you did the rest of the work your UI class should be mostly empty,
// most of the setup would be done in the declarative part (XAML) via bindings
// this allows you to reuse your entire business logic, unit test included
// when you decide to switch platforms (desktop, mobile, server)
public CalculatorWindow: Window{
public CalculatorWindow(){
BindingContext = new CalculatorVM(new CalculatorState());
InitializeComponent();
}
}