概要
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
<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
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パターンでの開発を支援するフレームワーク) で提供されているコマンドクラスです。
上記のPage部分をUserControlに変更すると、WindowにUserControlも追加できます。
※UserControlにはTitleプロパティは無いので要注意です。
PageにUserControlを入れるサンプルコード1
<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>
public class HogeControlViewModel
{
// 省略
}
<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>
public class HogePageViewModel
{
// 省略
}
ViewのDataContext
部分について
DataContext
は上記のxamlのように<view:HogeControl DataContext="{Binding HogeControlViewModel}" />
のような書き方で指定することも可能です。
PageにUserControlを入れるサンプルコード2
PageにUserControlを入れるサンプルコード1の応用として、下記のようにも実装できます。
<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>
public class HogeControl1ViewModel
{
public bool IsVisible {get; set; }
// HogeControl1の処理 (省略)
}
<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>
public class HogeControl2ViewModel
{
public bool IsVisible {get; set; }
// HogeControl2の処理 (省略)
}
<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>
public class HogeControl3ViewModel
{
public bool IsVisible {get; set; }
// HogeControl3の処理 (省略)
}
<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>
public class HogePageViewModel
{
// 画面上部のViewModel側の実装 (省略)
// 画面下部部分のViewModelをまとめたクラス
public ParentViewModel ParentViewModel { get; set; }
/// <summary>
/// コンストラクタ
/// </summary>
public HogePageViewModel()
{
ParentViewModel = new ParentViewModel();
}
}
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
を呼び出す定義が行えます。
xamlでbool型をVisibility型に変換するConverterクラスを使用していますが、Converterクラスについては下記を参照ください。
【WPF】Converterの作成方法【C#】
おわりに
xamlは結構複雑なところもあるので、記事を記載していくことで自分の頭も整理される感覚になりました。