You can start by separating business logic and UI

`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). It represents the entire I/O of your business logic (calculation on a calculator), you could expand it to support new features, such as history, 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(CalculatorLogic.Sub State);
        AddCommand = new CalculatorCommand(CalculatorLogic.Add, 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(); 
      }
    }

CalculatorWindow.xaml:

    <CalculatorWindow....>
    ...
      <TextBlock Text="{Binding State.Value}"/>
    ...
      <TextBlock Text="{Binding State.ErrorMessage}" Visibility="{Binding State.IsError, Converter={...bool-to-visible-converter}}"/>
    ...
      <Button Command="{Binding NumberCommand}" CommandParameter="0">0</Button> 
    ... 
      <Button Command="{Binding AddCommand}">+</Button>  
    </CalculatorWindow>