.NET MAUIがVisual Studio for Mac 17.4で利用可能になったので触ってみた

この記事は [N/S高等学校 Advent Calendar 2022](https://qiita.com/advent-calendar/2022/n-s-highschool) の２日目の記事になります。

# 概要
タイトル通りにはなりますが、MacでVisual Studioを触ったことがないので.NET MAUIがリリースされた＆Macでも利用可能になったということで触れてみました。ついでに最近のMVVMパターンの書き方も進化しているようなのでこちらも少し触れてみました。

# .NET MAUIとは
C#とXAMLを使ってWindows、Mac、Android、iOSのアプリケーションを作成するためのクロスプラットフォームフレームワークです。
今まではXamarinでしたがこれがMAUIに置き換わったのですね。

# 環境
執筆時時点でMacOS上でMAUIアプリの作成には以下の環境が必要でした。
* Visual Studio 2022 for Mac 17.4
* macOS Monterey 12.5
* Xcode 14.1

# 触るモノ
プロジェクトを作成すると出来上がる、ボタンをクリックした数をカウントするアプリです。
![スクリーンショット 2022-11-28 15.15.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1293517/dab9e1a0-6fe9-eafe-8069-310beb1f4c1f.png)

# プロジェクト作成時のコード
コードビハインドで記述されています。
`OnCounterClicked()`でボタンのテキストを直接変更しているだけですね。

```MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DefaultApp.MainPage">

    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">

            ~~~~~~中略~~~~~~

            <Button
                x:Name="CounterBtn"
                Text="Click me"
                SemanticProperties.Hint="Counts the number of times you click"
                Clicked="OnCounterClicked"
                HorizontalOptions="Center" />

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>
```

```MainPage.xaml.cs
public partial class MainPage : ContentPage
{
	int count = 0;

	public MainPage()
	{
		InitializeComponent();
	}

	private void OnCounterClicked(object sender, EventArgs e)
	{
		count++;

		if (count == 1)
			CounterBtn.Text = $"Clicked {count} time";
		else
			CounterBtn.Text = $"Clicked {count} times";

		SemanticScreenReader.Announce(CounterBtn.Text);
	}
}
```

# MVVMを自力で書く場合
せっかくなのでMVVMにしてみます。
`INotifyPropertyChanged`を実装したクラスを用意し、それを継承した`MainPageViewModel`(ViewModelクラス)を作成します。
ViewModelにはバインディングするためのプロパティとコマンドを書いていきます。

```MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:OldBinding.ViewModel"
             x:Class="OldBinding.MainPage">

    <ContentPage.BindingContext>
        <local:MainPageViewModel />
    </ContentPage.BindingContext>

    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">

            ~~~~~~中略~~~~~~

            <Button
                x:Name="CounterBtn"
                Text="{Binding Text}"
                SemanticProperties.Hint="Counts the number of times you click"
                Command="{Binding Command}"
                HorizontalOptions="Center" />

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>
```

```ViewModelBase.cs
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        private string NameProperty;

        public string Name
        {
            get
            {
                return NameProperty;
            }
            set
            {
                if (NameProperty != value)
                {
                    NameProperty = value;
                    NotifyPropertyChange("Name");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChange(string propName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }
        }
    }
```

```MainPageViewModel.cs
	public class MainPageViewModel : ViewModelBase
	{
        private string text = "click me";
        private int count = 0;

        public MainPageViewModel()
        {
            Command = new Command(Count);
        }

        public string Text
        {
            get
            {
                return text;
            }
            set
            {
                if (text != value)
                {
                    text = value;
                    NotifyPropertyChange("Text");
                }
            }
        }

        public ICommand Command
        {
            get;
            protected set;
        }

        private void Count()
        {
            count++;

            if (count == 1)
                Text = $"Clicked {count} time";
            else
                Text = $"Clicked {count} times";
        }
    }

```
うーん、とても面倒くさいですね。。

# CommunityToolkit.Mvvmを使ってみる
Microsoft謹製のMVVM開発をサポートしてくれる便利なライブラリーがあります。
Nugetパッケージの管理からライブラリーをインストールします。
![スクリーンショット 2022-11-28 17.19.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1293517/12d3ac24-0805-9905-376c-3e93a52dff42.png)

それではこのライブラリーを使ってコードを書いてみます。

```MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:NewBinding.ViewModel"
             x:Class="NewBinding.MainPage">

    <ContentPage.BindingContext>
        <local:MainPageViewModel />
    </ContentPage.BindingContext>

    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">

            ~~~~~~中略~~~~~~

            <Button
                x:Name="CounterBtn"
                Text="{Binding Text}"
                SemanticProperties.Hint="Counts the number of times you click"
                Command="{Binding CommandCommand}"
                HorizontalOptions="Center" />

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

```

```MainPageViewModel.cs
	[INotifyPropertyChanged]
	public partial class MainPageViewModel
	{
        private int count = 0;

        public MainPageViewModel()
		{
		}

		[ObservableProperty]
        private string text = "click me";

        [RelayCommand]
		private void Command()
		{
            count++;

            if (count == 1)
                Text = $"Clicked {count} time";
            else
                Text = $"Clicked {count} times";

        }
    }

```
なんということでしょう。コード量がこれだけになってしまいました。
`INotifyPropertyChanged`はクラスに属性をつけるだけで実装は不要となりました。
プロパティも変数に`ObservableProperty`属性をつけると自動的に生成してくれるようです。
この場合`text`から`Text`プロパティを生成してくれます。
コマンドに関してもメソッドに`RelayCommand`属性をつけると`メソッド名＋Command`というコマンドが自動生成されるようです。
サンプルのメソッド名が`Command`なので`CommandCommand`というおかしな名前になってしまいましたが、、
あと、クラスをpartialにしないとエラーになってしまうことが注意点でした。（なぜわざわざpartialにしないとダメなのでしょう。）

# 最後に
さて、今回初めてMac上でMAUIアプリを作成してみました。
前提条件としてXCodeのインストールが必要なのが手間ですがそこは仕方ありませんね。
Xamlには慣れる必要がありますが、C#でクロスプラットフォームアプリが作成できるのは魅力的ですね。
CommunityToolkit.Mvvmは裏でコードが自動生成されるので初見ではなんで動いてるんだ？と思わせかねないコードになってしまう感じがしますがルールさえ押さえておけばスッキリと書けるので良いのではないでしょうか。