【WPF】Window、Page、UserControlの違い【C#】

# 概要
WPFでは「Window」「Page」「UserControl」のように様々なView用のコントロール (クラス) があります。Viewひとつとっても、この3つの中から適切なものを選択することで、アプリケーションの設計が向上します。
本稿ではこのView用の3つのコントロールについて、違いをまとめてみました。
また、WindowにPageを埋め込む、PageにUserControlを埋め込む等、いくつかサンプルコードも記載しました。

# Windowとは
独立したウィンドウとして扱われます。
他のWindowやPage、UserControlのホストになる存在です。

# Pageとは
Window内のページ内の一部を表現するものです。
単独では独立したウィンドウとして表示はできないため、独立したウィンドウとしての機能は持っていません。
なので、他のコントロール内 (WindowやPageなど) でのみ動作します。
入れ子のように、PageにPageを埋め込むことも可能です。

# UserControlとは
再利用できるUIコンポーネントを作成するために使用されます。
カスタムコントロールを作成するための基盤クラスになるイメージです。
他のWindowやPageの一部分に追加するような形で使用するのが一般的です。
なのでPageと同様、UserControl単体では独立したウィンドウとして表示できず、あくまで他のコントロール内で使用されることを前提としたものになります。
Pageと同様入れ子のように、UserControlにUserControlを埋め込むことも可能です。

# サンプルコード
以下にWindowにPageを埋め込む、PageにUserControlを埋め込むサンプルコードを記載しました。あくまでサンプルコードで、示した例以外にもいくつか方法はあります (あると思います)。

## WindowにPage (UserControl) を入れるサンプルコード

### View
```xml:HogeWindow.xaml
<Window x:Class="HogeNamespace.HogeWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       	xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        Title="HogeWindow" Height="500" Width="700">
        <Window.DataContext>
    		<vm:HogeWindowViewModel />
    	</Window.DataContext>
    	<i:Interaction.Triggers>
    		<i:EventTrigger EventName="Loaded">
    			<i:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=HogeWindow}" />
    		</i:EventTrigger>
    	</i:Interaction.Triggers> 
    <Grid>
        <!-- メインコンテンツ -->
        <StackPanel>
            <Button Content="ボタン1" Click="Button_Click"/>
        </StackPanel>
        <!-- フレーム（Pageを表示するためのコンテナ） -->
        <Frame x:Name="SampleFrame" />
    </Grid>
</Window>
```

### ViewModel

```C#:HogeWindowViewModel.cs
    public class HogeWindowViewModel
    {
        /// <summary>
        /// Loadedイベント
        /// </summary>
        public ICommand LoadedCommand => _LoadedCommand ?? (_LoadedCommand = new DelegateCommand<Window>((window) =>
        {      
        // HogePageという名前のPageをnewする
        var hogePage = new HogePage();
        var pageVM = hogePage.DataContext as HogePageViewModel;

        // HogeWindowにHogePageをセット
        var frame = _window.FindName("ContentFrame") as Frame;
        frame.Content = hogePage;
        
        }));
        private DelegateCommand<Window> _LoadedCommand;
    }
```

### Viewの`Interaction.Triggers`部分について
上記のコードは`Interaction.Triggers`と`InvokeCommandAction`を使用して、Windowのロード時にViewModelでロード処理を行うようにできます。
以下に少し説明を記載しました。
#### Interaction.Triggers
`Interaction.Triggers`はUIの要素に対し、イベントのトリガーを設定するためのものです。
`EventName`を指定して、ここではwindowのロードが終わったタイミング (Loaded) をトリガーとして指定しています。
#### InvokeCommandAction
`InvokeCommandAction`を使用することで、ViewModelのメソッドを呼び出すことができます。
`Command`プロパティでViewModelのコマンドを指定でき、`CommandParameter`でコマンドに渡すパラメータを指定しています。
また、`ElementName`は現在のXAMLファイルで定義されている要素を参照するために使用され、XAML内の他の要素からデータをバインドすることができます。ここでは`HogeWindow`を指定して、Window自体をViewModelにバインドしています。

### `Frame`について
`Frame`はPageやUserControlを表示するためのコンテナとして使用されます。

### ViewModelの`ICommand`について
`ICommand`インターフェースを実装することで、Viewで定義した`Command`プロパティからViewModelのコマンドを呼び出すことができます。
:::

### ViewModelの`DelegateCommand`について
`DelegateCommand`はprism (WPFのMVVMパターンでの開発を支援するフレームワーク) で提供されているコマンドクラスです。

:::note
上記のPage部分をUserControlに変更すると、WindowにUserControlも追加できます。
※UserControlにはTitleプロパティは無いので要注意です。
:::

## PageにUserControlを入れるサンプルコード1

```xml:HogeControl.xaml
<UserControl x:Class="HogeNamespace.HogeControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
    <Grid>
        <!-- メインコンテンツ -->
    </Grid>
</UserControl>
 ```
 
```C#:HogeControlViewModel.cs
    public class HogeControlViewModel
    {
        // 省略
    }
```
```xml:HogePage.xaml
<Page
	x:Class="HogeNamespace.HogePage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:view="clr-namespace:HogeNamespace">
    <StackPanel>
        <view:HogeControl DataContext="{Binding HogeControlViewModel}" />
    </StackPanel>
 ```
```C#:HogePageViewModel.cs
    public class HogePageViewModel
    {
        // 省略
    }
```

### Viewの`DataContext`部分について
`DataContext`は上記のxamlのように`<view:HogeControl DataContext="{Binding HogeControlViewModel}" />`のような書き方で指定することも可能です。

# PageにUserControlを入れるサンプルコード2

[PageにUserControlを入れるサンプルコード1](#pageにusercontrolを入れるサンプルコード1)の応用として、下記のようにも実装できます。

```xml:HogeControl1.xaml
<UserControl x:Class="HogeNamespace.HogeControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:converter="Hoge.Converters"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
    <UserControl.Resources>
        <ResourceDictionary>
            <converter:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}">
        <!-- HogeControl1のメインコンテンツ -->
    </Grid>
</UserControl>
 ```
 
```C#:HogeControl1ViewModel.cs
    public class HogeControl1ViewModel
    {
        public bool IsVisible {get; set; }

        // HogeControl1の処理 (省略)
    }
```

```xml:HogeControl2.xaml
<UserControl x:Class="HogeNamespace.HogeControl2"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:converter="Hoge.Converters"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
    <UserControl.Resources>
        <ResourceDictionary>
            <converter:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}">
        <!-- HogeControl2のメインコンテンツ -->
    </Grid>
</UserControl>
 ```
 
```C#:HogeControl2ViewModel.cs
    public class HogeControl2ViewModel
    {
        public bool IsVisible {get; set; }

        // HogeControl2の処理 (省略)
    }
```

```xml:HogeControl3.xaml
<UserControl x:Class="HogeNamespace.HogeControl3"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:converter="Hoge.Converters"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
    <UserControl.Resources>
        <ResourceDictionary>
            <converter:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}">
        <!-- HogeControl3のメインコンテンツ -->
    </Grid>
</UserControl>
 ```
 
```C#:HogeControl3ViewModel.cs
    public class HogeControl3ViewModel
    {
        public bool IsVisible {get; set; }

        // HogeControl3の処理 (省略)
    }
```

```xml:HogePage.xaml
<Page
	x:Class="HogeNamespace.HogePage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:view="clr-namespace:HogeNamespace">
    <StackPanel>
    <!-- Page上部の定義 (省略) -->
        <StackPanel>
        <!-- Page下部の定義 -->
            <view:HogeControl DataContext="{Binding ParentViewModel.HogeControl1ViewModel}" />
            <view:HogeControl DataContext="{Binding ParentViewModel.HogeControl2ViewModel}" />
            <view:HogeControl DataContext="{Binding ParentViewModel.HogeControl3ViewModel}" />
        </StackPanel>
    <StackPanel>
 ```
 
```C#:HogePageViewModel.cs
    public class HogePageViewModel
    {   
        // 画面上部のViewModel側の実装 (省略)

        // 画面下部部分のViewModelをまとめたクラス
        public ParentViewModel ParentViewModel { get; set; }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public HogePageViewModel()
        {
            ParentViewModel = new ParentViewModel();
        }
    }
```

```C#:ParentViewModel.cs
    public class ParentViewModel
    {
       public HogeControl1ViewModel HogeControl1ViewModel { get; }

       public HogeControl2ViewModel HogeControl2ViewModel { get; }

       public HogeControl3ViewModel HogeControl3ViewModel { get; }
       
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public ParentViewModel()
        {
            HogeControl1ViewModel = new HogeControl1ViewModel();
            HogeControl2ViewModel = new HogeControl2ViewModel();
            HogeControl3ViewModel = new HogeControl3ViewModel();
        }
    }
```

上記のように、`HogePage.xaml`の画面の下部を定義している部分に、UserControl (`HogeControl`) が3つ定義されているとします。
3つの`HogeControl`のViewModelにはそれぞれ`Visible`プロパテを定義しておき、必ず1つのみ`Visible = true`となり、それ以外の2つは`Visible = false`となるよう実装しておきます。(その実装はここでは省略しています)
そうすると、`HogePage.xaml`の画面下部分は、`Visible = true`となっている`HogeControl`のみ表示されるようになります。なので特定の操作での処理で、表示する`HogeControl`を変えてあげると、ユーザーの操作に合わせて`HogePage`の画面下部に表示される`UserControl`の種類を変えることができます。

また、ここではコードが煩雑にならないよう`HogePageViewModel.cs`に3つの`HogeControl`をまとめたクラス (`ParentViewModel`) を作成しました。このようにしても、`HogePage.xaml`からは`DataContext="{Binding ParentViewModel.HogeControl3ViewModel}"`のようにドット`.`を使用して各`HogeControl`を呼び出す定義が行えます。

:::note
xamlでbool型をVisibility型に変換するConverterクラスを使用していますが、Converterクラスについては下記を参照ください。
[【WPF】Converterの作成方法【C#】](https://qiita.com/MetroOsamu/items/58503e935b21dc392cc4)
:::

# おわりに
xamlは結構複雑なところもあるので、記事を記載していくことで自分の頭も整理される感覚になりました。