UWPアプリをMVVMで作成するときに最初にやること

C#でUWPアプリケーションを新しく作成するときにまずやることをここに残しておきます。

※Visual Studio 2017を前提にしています。

# Visual Studio でプロジェクトの新規作成

[すでに作成した方は飛ばしてください。](#プロジェクト作成直後にやること)

## 空のプロジェクトを作成

メニューからプロジェクトを新規作成。
![18.png](https://qiita-image-store.s3.amazonaws.com/0/106819/f1d58368-3842-e2c4-1111-fed93399e35b.png)

空白のアプリ(ユニバーサルWindows)を選んで保存場所を選びます。
![17.png](https://qiita-image-store.s3.amazonaws.com/0/106819/bd777d51-1f6a-38a5-3839-fc0a289762b8.png)

コンパイルするターゲットとなるバージョンと、使うAPIに応じて最小バージョンを適当に選びます。
![19.png](https://qiita-image-store.s3.amazonaws.com/0/106819/81c722cf-dbb9-ee5e-1532-8501efdc6b6e.png)

作成直後はこのようなフォルダ構造になっています。
![20.png](https://qiita-image-store.s3.amazonaws.com/0/106819/46a33b3c-2436-71e3-b552-27bc525e7e3f.png)



# プロジェクト作成直後にやること

## MVVMとローカライズ用のフォルダを作成

作成するフォルダは以下の5つ

- Models
- Views
- ViewModels
- Strings
- Strings\ja-jp

上3つはMVVM用、下2つはローカライズ用です。ローカライズ用フォルダは、[言語]()に応じて増やしていきます。
詳しくは「[UI 文字列をリソースに格納する](https://docs.microsoft.com/ja-jp/windows/uwp/globalizing/put-ui-strings-into-resources)」を参考に。

![1.png](https://qiita-image-store.s3.amazonaws.com/0/106819/f2664efc-d4cb-78f7-5b73-520d9d0c7826.png)


## MainPageをViewsフォルダに移動

プロジェクトに最初から含まれているMainPageをViewsフォルダに移動していきます。ここでは以下の作業をします。

- MainPage.xaml,MainPage.xaml.csファイルを移動
- MainPageクラスの名前空間を修正


### MainPage.xaml,MainPage.xaml.csファイルを移動

MainPage.xamlをドラッグ&ドロップでViewsフォルダに持っていきます。これで、コードビハインドであるMainPage.xaml.csも一緒にViewsフォルダに入ります。

![2.png](https://qiita-image-store.s3.amazonaws.com/0/106819/2d9551fa-8a40-4287-f37d-329b8961ec95.png)

### MainPageクラスの名前空間を修正

MainPageクラスは、最初はプロジェクト名と同じ名前空間の直下に配置されます。これをViewsフォルダに入れててもコンパイルできるように、Views名前空間に移動します。
修正する場所は、MainPage.xamlとMainPage.xaml.cs、忘れていけないのがApp.xaml.csの最初に表示するページを指定するところです。


1つ目はMainPage.xamlのx:Class
![3.png](https://qiita-image-store.s3.amazonaws.com/0/106819/3591b7f6-ef8e-3e1e-a472-83406fe2c479.png)

2つ目はMainPage.xaml.csのMainPageクラスがあるnamespace
![4.png](https://qiita-image-store.s3.amazonaws.com/0/106819/9d12330b-a61a-f846-7045-55a201a10830.png)

3つ目はApp.xaml.csのOnLaunched関数の中にあるNavigateの引数
![5.png](https://qiita-image-store.s3.amazonaws.com/0/106819/1403f71b-e842-af5a-c987-27a456fa50c1.png)

MainPage.xamlを直すついでに、xamlタグ内で用いる名前空間にviewsとviewmodelsを追加しておくと便利です。
![16.png](https://qiita-image-store.s3.amazonaws.com/0/106819/612c1ab9-1a6c-165e-41cc-b5a885a2c985.png)


## ViewModelの作成

MainPageをViewsに移動したら、これに対応するViewModelを作成します。

### ViewModel用に補助クラスの作成

ViewModelからView, ModelからViewへプロパティの変更を知らせるために、INotifyPropertyChangedを頻繁に使うため、これを実装したクラスを用意しておくと継承だけで使うことができて便利です。
先人の知恵「[Observable.cs](https://github.com/Microsoft/WindowsTemplateStudio/blob/master/templates/_composition/MVVMBasic/Project/Helpers/Observable.cs)」を利用します。

```csharp:Observable.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;

// https://github.com/Microsoft/WindowsTemplateStudio/blob/master/templates/_composition/MVVMBasic/Project/Helpers/Observable.cs から引用
// 名前空間を修正
namespace App8.Helpers
{
    public class Observable : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
        {
            if (Equals(storage, value))
            {
                return;
            }

            storage = value;
            OnPropertyChanged(propertyName);
        }

        protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
```

### MainPageViewModel.csの作成

Observable.csをもとに、MainPageに対応するViewModelクラスを作成します。初期化関数にViewを渡せるようにしておいて、ViewModelからViewを弄れるようにしておきます。

```csharp:MainPageViewModel.cs
namespace App8.ViewModels
{
    public class MainPageViewModel : Helpers.Observable
    {
        public Views.MainPage View { get; private set; } = null;

        public void Initialize(Views.MainPage mainPage)
        {
            View = mainPage;
        }
    }
}
```

### MainPage.xaml.csの修正

MainPageViewModelクラスとの対応を入れます。具体的にはプロパティの追加、ViewModelの初期化コードの呼び出し、データコンテキストの紐づけです。

```csharp:MainPage.xaml.cs
using Windows.UI.Xaml.Controls;

// 空白ページの項目テンプレートについては、https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x411 を参照してください

namespace App8.Views
{
    /// <summary>
    /// それ自体で使用できる空白ページまたはフレーム内に移動できる空白ページ。
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public ViewModels.MainPageViewModel ViewModel { get; private set; } = new ViewModels.MainPageViewModel();

        public MainPage()
        {
            this.InitializeComponent();
            ViewModel.Initialize(this);
            this.DataContext = ViewModel;
        }
    }
}
```

## 多言語化(ローカライズ)

### Stringsリソース

![8.png](https://qiita-image-store.s3.amazonaws.com/0/106819/d5f00f07-44de-2f91-9d06-4f90957e6b58.png)

### 補助クラス

文字列はローカライズ文字列を取得するための拡張メソッドを作っておくと便利です。

[ResourceExtensions.cs](https://github.com/Microsoft/WindowsTemplateStudio/blob/master/templates/Projects/Default/Helpers/ResourceExtensions.cs)

```csharp:ResourceExtensions.cs
using System;
using System.Runtime.InteropServices;
using Windows.ApplicationModel.Resources;

// https://github.com/Microsoft/WindowsTemplateStudio/blob/master/templates/Projects/Default/Helpers/ResourceExtensions.cs から引用
// 名前空間を修正
namespace App8.Helpers
{
    internal static class ResourceExtensions
    {
        private static ResourceLoader _resLoader = new ResourceLoader();

        public static string GetLocalized(this string resourceKey)
        {
            return _resLoader.GetString(resourceKey);
        }
    }
}
```


# 余談

毎回やるのは地味に面倒なので、Microsoftの素晴らしいテンプレート「[Windows Template Studio](https://aka.ms/wtsinstall)」を使う手もあります。高機能なので、コードが複雑になりますが、Master-Detailや設定画面などを最初から作れるので便利です。