びおりんのブログ

きままにアプリのできるまでを公開していきます

WPFのMVVMで電卓づくり(6)

四則演算子ボタンを押すと計算が行われる仕組みを実装します

今回は大筋で四則演算ができるところまで作成します

構想

四則演算子ボタンクリックによる処理の流れについては、下記で構想してます

View=>ViewModel=>Model=>ViewModel=>Model

  1. MainView:四則演算子ボタンにPushCommandを紐づけします

  2. MainViewModel:四則演算子ボタンクリック時にMainModelの計算処理を呼び出します

  3. MainModel:呼び出した計算処理で計算に必要なプロパティを更新します

  4. MainViewModel:計算結果をMainViewに紐づけしたプロパティに代入し画面更新させます

  5. MainView:MainViewModelの更新を受けて計算結果を出力画面に表示させます

下準備

数字以外のボタンをクリックした際の操作の種類(四則演算等)を
取りまとめたOperationKindを適当な名前空間に定義します

   enum OperationKind
    {
        None = 0,
        Equal,
        Plus,
        Minus,
        Multiply,
        Divide,
        Clear,
        ClearAll,
        Reverse,
        GrandTotal,
        Persent,
    }

MainModelに計算に必要なプロパティを追加します

       // 四則演算される値
        private double OriginalValue = 0D;
        // 四則演算する値
        private double AffectlValue = 0D;
        // 出力画面表示テキスト
        public string DisplayText { get; private set; } = "0";
        // 操作の種類(四則演算等)
        public OperationKind Operation { get; private set; } = OperationKind.None;
        // 四則演算する値を現在取り扱っているかどうかのフラグ
        public bool IsAdditional { get; set; }

MainModel.SetText()で計算に必要な値が更新されるように変更します

       public void SetText(string str)
        {
            this.DisplayText = this.IsAdditional ? AffectlValue.ToString() : OriginalValue.ToString();

            if (this.DisplayText.Equals("0"))
            {
                if (str.Equals("0") || str.Equals("00"))
                {
                    return;
                }
                this.DisplayText = string.Empty;
            }

            this.DisplayText += str;

            if (double.TryParse(this.DisplayText, out double displayValue))
            {
                if (this.IsAdditional)
                {
                    this.AffectlValue = displayValue;
                }
                else
                {
                    this.OriginalValue = displayValue;
                }
            }
        }

実装

構想に沿って各MVVMクラスに追記していきます

1. MainView

四則演算子ボタンにPushCommandを紐づけします

     <!--ボタンの様式-->
        <Style TargetType="Button">
            <Setter Property="FontSize" Value="40"/>
            <Setter Property="FontStyle" Value="Normal"/>
            <Setter Property="Background" Value="Black"/>
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="FontFamily" Value="arial"/>
            <Setter Property="Command" Value="{Binding Path=PushCommand}"/>
        </Style>

ボタンの数が増えてきましたので実装を少しでも楽にするため
ボタンのStyleにPushCommandの紐づけを記述して様式化しました

     <!--四則演算ボタン-->
        <Button Grid.Row="3" Grid.Column="4" Content="+" CommandParameter="Plus"
                Grid.RowSpan="2"/>
        <Button Grid.Row="3" Grid.Column="5" Content="-" CommandParameter="Minus"/>
        <Button Grid.Row="2" Grid.Column="4" Content="×" CommandParameter="Multiply"/>
        <Button Grid.Row="2" Grid.Column="5" Content="÷" CommandParameter="Divide"/>
        <Button Grid.Row="5" Grid.Column="6" Content="=" CommandParameter="Equal"/>

四則演算子ボタンには、個別でCommandParameterを追加しました

2. MainViewModel

四則演算子ボタンクリック時にMainModelの計算処理を呼び出します

       public MainViewModel()
        {
            var model = new MainModel();

            this.DisplayText = model.DisplayText;
            RaisePropertyChanged(nameof(this.DisplayText));

            this.PushCommand = new DelegateCommand(
                param => 
                {
                    if (param == null) { return; }
                    var str = param.ToString();
                    if (model != null)
                    {
                        if (int.TryParse(str, out int num))
                        {
                            model.SetText(str);
                        }
                        else
                        {
                            model.SetOperation(str);
                        }
                        this.DisplayText = model.DisplayText;
                        RaisePropertyChanged(nameof(this.DisplayText));
                    }
                },
                param =>
                {
                    return true;
                });
        }

計算処理はmodel.SetOperation()としました
四則演算子ボタンクリック時の引数paramは
数字でないため計算処理に到達します

       public void RaisePropertyChanged(string name) 
        {
            var args = new PropertyChangedEventArgs(name);
            PropertyChanged?.Invoke(this, args);
        }

3. MainModel

呼び出した計算処理で計算に必要なプロパティを更新します

       public void SetOperation(string str) 
        {
            double displayValue;
            if (!double.TryParse(this.DisplayText, out displayValue)) { return; }

            if (Enum.TryParse(str, out OperationKind ope))
            {
                switch (ope)
                {
                    case OperationKind.Equal:
                        if (this.IsAdditional)
                        {
                            Calculate(this.Operation);
                            this.IsAdditional = false;
                        }
                        this.Operation = OperationKind.None;
                        break;
                    case OperationKind.Plus:
                    case OperationKind.Minus:
                    case OperationKind.Multiply:
                    case OperationKind.Divide:
                        if (this.IsAdditional)
                        {
                            Calculate(this.Operation);
                        }
                        else
                        {
                            this.IsAdditional = true;
                        }
                        this.Operation = ope;
                        break;
                    default:
                        break;
                }
            }
        }

        public void Calculate(OperationKind ope)
        {
            if (!double.TryParse(this.DisplayText, out double displayValue)
             || this.AffectlValue != displayValue)
            {
                return;
            }
            switch (ope) 
            {
                case OperationKind.Plus:
                    this.OriginalValue += this.AffectlValue;
                    this.AffectlValue = 0;
                    this.DisplayText = this.OriginalValue.ToString();
                    break;
                case OperationKind.Minus:
                    this.OriginalValue -= this.AffectlValue;
                    this.AffectlValue = 0;
                    this.DisplayText = this.OriginalValue.ToString();
                    break;
                case OperationKind.Multiply:
                    this.OriginalValue *= this.AffectlValue;
                    this.AffectlValue = 0;
                    this.DisplayText = this.OriginalValue.ToString();
                    break;
                case OperationKind.Divide:
                    if (this.AffectlValue == 0)
                    {
                        this.OriginalValue = 0;
                        this.AffectlValue = 0;
                        this.DisplayText = "E";
                    }
                    else 
                    {
                        this.OriginalValue /= this.AffectlValue;
                        this.AffectlValue = 0;
                        this.DisplayText = this.OriginalValue.ToString();
                    }
                    break;
                default:
                    break;
            }
        }

0除算のときだけエラーとして出力画面に「E」を表示させるようにしました

4. MainViewModel

計算結果をMainViewに紐づけしたプロパティに代入し画面更新させます

       public void RaisePropertyChanged(string name) 
        {
            var args = new PropertyChangedEventArgs(name);
            PropertyChanged?.Invoke(this, args);
        }

MainViewと紐づけるプロパティが今後増えることを見越して
MainView上のプロパティ更新の手続きをメソッド化しました

5. MainView

MainViewModelの更新を受けて計算結果を出力画面に表示させます
特に今回追記はないので動作確認結果を掲載します

起動後「1」「+」「2」「×」「3」「=」とした場合の画面表示
f:id:kazoojapan1985:20200505022309p:plain

起動後「1」「÷」「0」「=」とした場合の画面表示
f:id:kazoojapan1985:20200505022604p:plain

今後の更新

実装の全体像を説明するのが負担になってきたので
Git環境でも構築して途中経過を載せていくようにしようと考えています

記事では、要点だけまとめるような形で模索してます