1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Hello Xamarin! MVVM編

Last updated at Posted at 2021-01-21

この記事は前回のHello Xamarin!の続きです。

目的

前回の記事で書いた四則演算をするアプリをMVVMで作る。

MVVMとは

MVVMとは、Model - View - ViewModel の頭文字を取った設計パターンです。 Modelはビジネスロジックを、Viewは表示、入力を、ViewModelはViewのロジックを担当します。 こちらの記事が詳しいです。 MVVMパターンの常識 ― 「M」「V」「VM」の役割とは?

ViewModelとModelを適切に設計できれば、View(プラットフォーム)に依存しない再利用性が高いコードができあがります。

できたもの

![スクリーンショット 2021-01-21 0.41.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1033036/0a4117fa-df54-2ebf-9a9e-1053edefe752.png)

スクリーンショット 2021-01-21 0.39.09.png

前回作ったものに、0で割ろうとすると計算できないようにする機能をつけました。

コード

View(Xaml)

```Xml:MainPage.Xaml
<ContentPage.BindingContext>
    <local:MainPageViewModel/>
</ContentPage.BindingContext>

<StackLayout Orientation="Vertical" Margin="30,50,0,0">
    <StackLayout Orientation="Horizontal">
        <Entry Text="{Binding LeftNumber}" WidthRequest="100" VerticalOptions="Start"/>
        <Picker ItemsSource="{Binding Operators}" SelectedIndex="{Binding SelectedIndex}"/>
        <Entry Text="{Binding RightNumber}" WidthRequest="100" VerticalOptions="Start" >
        </Entry>
        <Label Text="=" VerticalOptions="Center"/>
        <Label Text="{Binding Answer}" WidthRequest="100" VerticalOptions="Center"/>
    </StackLayout>
    <Button Text="計算" Command="{Binding CalculateCommand}"/>
</StackLayout> 
``` ContentPageにMainPageViewModelをバインドします。

ViewModel(C#)

```C#:MainPageViewModel.cs public class MainPageViewModel : INotifyPropertyChanged { enum Operator { Add = 0, Sub = 1, Mul = 2, Div = 3 }
    static readonly string[] operators = new[] { "+", "-", "×", "÷" };

    public DelegateCommand CalculateCommand { get; private set; }

    public event PropertyChangedEventHandler PropertyChanged;

    readonly Calculater calculater = new Calculater();

    public double LeftNumber { get; set; }
    
    
    double rightNumber;
    public double RightNumber
    {
        get => rightNumber;
        set
        {
            rightNumber = value;
            UpdateIsValid();
        }
    }

    double answer;      
    public double Answer
    {
        get => answer;
        set
        {
            answer = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Answer)));
        }
    }

    public string[] Operators { get; private set; }

    int selectedIndex;
    public int SelectedIndex
    {
        get => selectedIndex;
        set
        {
            selectedIndex = value;
            UpdateIsValid();
        }
    }

    public bool IsValid { get; private set; } = true;

    public MainPageViewModel()
    {
        Operators = operators;
        CalculateCommand = new DelegateCommand(Calculate, () => IsValid);   
    }

    void Calculate() => Answer = calculater.Calculate(LeftNumber, RightNumber, Operators[SelectedIndex]);

    void UpdateIsValid()
    {
        IsValid = !(selectedIndex == (int)Operator.Div && rightNumber == 0.0);
        CalculateCommand.RaiseCanExecuteChanged();
    }
}

ViewModelのプロパティが変更したことをViewに通知するため、INotifyPropertyChangedを実装し、イベントを発火させます。
今回はプロパティの数が少ないのでそんなに苦ではないですが、数が増えるとsetterで毎回同じようなことを書かないといけないのでだるいです。
そこで普通はINotifyPropertyChangedを実装したベースクラスを作ってそれを継承します。

```C#:BindableBase
public abstract class BindableBase : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected bool SetProperty<T>(ref T oldValue, T value, [CallerMemberName] string propertyName = null)
        {
            if (oldValue.Equals(value)) {
                return false;
            } else {
                oldValue = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                return true;
            }
        }

        protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
BindableBaseを継承したViewModel
public class ViewModel : BindableBase
{
        double answer;
        public double Answer
        {
            get => answer;
            set => SetProperty(ref answer, value);
        }
    }

setterでSetProperty()を呼ぶことで変更も通知もしてくれます。

Command

ボタンを押した時の処理などはICommandインターフェースを実装したCommandクラスにやらせます。actionに処理、canExecuteに実行できるかどうか、デリゲートで渡してあげます。
DelegateCommand
public class DelegateCommand : ICommand
{
        Action action;
        Func<bool> canExecute;
        public event EventHandler CanExecuteChanged;

        public DelegateCommand(Action action, Func<bool> canExecute)
        {
            this.action = action;
            this.canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return canExecute();
        }

        public void Execute(object parameter)
        {
            action?.Invoke();
        }

        public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);  
    }

これらのBindableBaseやDelegateCommandといったクラスはPrismというライブラリにあるので、それを素直に使いましょう。

Model(C#)

```C#:Calculater.cs public class Calculater { public double Calculate(double leftNumber, double rightNumber, string ope) { switch (ope) { case "+": return leftNumber + rightNumber; case "-": return leftNumber - rightNumber; case "×": return leftNumber * rightNumber; case "÷": if (rightNumber == 0) { throw new InvalidOperationException(); } else { return leftNumber / rightNumber; }
            default:
                throw new InvalidOperationException();
        }
    }
}

<h1>まとめ</h1>
四則演算アプリをMVVMパターンで作成しました。
前回のコードビハインドでやった場合と比べてコード量がかなり増えました。
小規模のアプリなら素直にコードビハインドで書いた方が楽ですね。
本当は入力チェックをやろうと思ったんですが、WPFと違いINotifyDataErrorInfoを実装するだけじゃできないぽい。(調査不足で違うこと言ってたらすみません)
ダルかったので今回は省きました。

人生で2回目のQiitaですが、記事を書くのってかなり大変なんですね。
知識を共有してくれる世の中の素晴らしい先輩エンジニアの方々には頭があがりません。
いつもありがとうございます。

次はWPFで作ったアプリをXamarinに移行してみたいと思います。(できるかは知らん)

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?