WPF で MVVM パターンを使った画面遷移の方法を紹介しています。
ここで紹介するサンプルアプリは次の通りです。

画面遷移のやり方を知りたいという方はぜひ参考にしてみて下さい。
画面遷移の方法

今回ここで紹介する画面遷移は、MainWindow.xaml の特定の領域を切り替えて表示する方法です。具体的には、画面遷移で表示する画面を UserControl で作成し、特定の領域内に UserControl をセットして、条件によって UserControl を入れ替えて表示するというものになります。

画面遷移をよく使うシーンとしてボタンをクリックしたら、そのクリックしたボタンによって画面を切り替える場合が挙げられます。

他にも1つのボタンをクリックすると「画面A」→「画面B」→「画面C」のように順番に画面遷移するシーンが挙げられます。
ここでは上記のイメージで動作するサンプルを紹介します。
ContentControlで画面遷移する

WPF は MVVM パターンによる開発が推奨されているので、このパターンを採用したソースコードを紹介したいと思います。
まずは、プロジェクトにフォルダとクラス、ユーザーコントロールを下図のように追加します。ソリューションエクスプローラーを開いて、プロジェクト名を選択後に右クリックして、右クリックメニューの中から[追加]を選ぶとフォルダとクラス、ユーザーコントロールを追加することができます。

簡単にプロジェクトの階層について説明します。
Command フォルダには ICommand を継承したDelegateCommand クラスを追加しています。このクラスについては、次の記事で紹介していますので、ここでは省略します。

ViewModels には INotifyPropertyChanged を継承した ViewModelBase クラスを追加しています。更に各 View のViewModel クラスを作成し、ViewModelBase を継承させています。
Views には 画面遷移する UI 画面の UserControl(WPF) を追加しています。
ViewModelの作成
まずは ViewModelBase クラスを作成します。このクラスは INotifyPropertyChanged インターフェイスを継承する抽象クラスにします。
INotifyPropertyChanged を実装するためのイベントハンドラと実行メソッドを記述します。
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Sample.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
// INotifyPropertyChanged を実装するためのイベントハンドラ
public event PropertyChangedEventHandler PropertyChanged;
// プロパティ名によって自動的にセットされる
public void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
AViewModel.cs と BViewModel.cs と CViewModel.cs は ViewModelBase を継承し、コンストラクタを用意します。
3つのクラスは次のように記述します。
namespace Sample.ViewModels
{
public class AViewModel : ViewModelBase
{
public AViewModel() { }
}
}
namespace Sample.ViewModels
{
class BViewModel: ViewModelBase
{
public BViewModel() { }
}
}
namespace Sample.ViewModels
{
class CViewModel : ViewModelBase
{
public CViewModel() { }
}
}
続いて、MainWindowViewModel.cs にViewModelBase を継承して、画面に表示する UserControl の ViewModel を設定する ActiveView プロパティを用意します。
また、MainWindow のボタンをクリックした時に実行されるコマンドを用意します。このコマンドの実行メソッドではコマンドのパラメーターによって、ActiveView プロパティにセットする ViewModel を切り替えます。
MainWindowViewModel.cs は次のように記述します。
using Sample.Command;
namespace Sample.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private ViewModelBase activeView = new AViewModel();
//画面に表示するUserControlのViewModelを設定するプロパティ
public ViewModelBase ActiveView
{
get { return activeView; }
set
{
if (activeView != value)
{
activeView = value;
OnPropertyChanged("ActiveView");
}
}
}
//ボタンのコマンドプロパティ
public DelegateCommand ScreenTransitionCommand { get; }
//コンストラクタ
public MainWindowViewModel()
{
ScreenTransitionCommand = new DelegateCommand(screenTransitionExcute);
}
//ボタンのコマンド実行メソッド
private void screenTransitionExcute(string param)
{
//コマンドのパラメーターによって
//ActiveViewにセットするViewModelを切り替える
switch (param)
{
case "AView":
ActiveView = new AViewModel();
break;
case "BView":
ActiveView = new BViewModel();
break;
case "CView":
ActiveView = new CViewModel();
break;
}
}
}
}
Viewの作成
画面遷移する UserControl(WPF) を設定します。
AView.xaml と BView.xaml と CView.xaml には Label の Content にユーザーコントロールの名前を記述して、画面が切り替わっていることが分かるようにします。
3つのユーザーコントロールの XAML は次のように記述します。
<UserControl
x:Class="Sample.Views.AView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Sample.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Label
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="AViewの画面です"
FontSize="32" />
</UserControl>

<UserControl
x:Class="Sample.Views.BView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Sample.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Label
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="BViewの画面です"
FontSize="32" />
</UserControl>

<UserControl x:Class="Sample.Views.CView"
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:local="clr-namespace:Sample.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Label
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="CViewの画面です"
FontSize="32" />
</UserControl>

MainWindow.xaml では、ContentControl の Content に MainWindowViewModel の ActiveView をバインドします。
Grid のリソース内で DataTemplate を使って、DataType に ViewModel を指定し、それに紐づく View を指定して関連付けをします。
これにより、ActiveView にセットされた ViewModel に関連付けされた View が表示されるわけです。
画面を切り替える為のボタンを3つ用意し、MainWindowViewModel の ScreenTransitionCommand をボタンのコマンドにバインドします。更に CommandParameter にパラメーターを設定します。
MainWindow.xaml は次のように記述します。
<Window
x:Class="Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:view="clr-namespace:Sample.Views"
xmlns:vm="clr-namespace:Sample.ViewModels"
Title="MainWindow"
Width="550"
Height="350"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<GroupBox Margin="10,5,10,10">
<GroupBox.Header>
<Label Content="ボタン一覧"/>
</GroupBox.Header>
<StackPanel Grid.Column="0">
<Button Content="AViewを開く" Command="{Binding ScreenTransitionCommand}" CommandParameter="AView" Margin="10"/>
<Button Content="BViewを開く" Command="{Binding ScreenTransitionCommand}" CommandParameter="BView" Margin="10"/>
<Button Content="CViewを開く" Command="{Binding ScreenTransitionCommand}" CommandParameter="CView" Margin="10"/>
</StackPanel>
</GroupBox>
<Grid Grid.Column="1">
<Grid.Resources>
<DataTemplate DataType="{x:Type vm:AViewModel}">
<view:AView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:BViewModel}">
<view:BView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:CViewModel}">
<view:CView/>
</DataTemplate>
</Grid.Resources>
<ContentControl Content="{Binding ActiveView}"/>
</Grid>
</Grid>
</Window>

お疲れ様でした。これで完成です。
動作確認
それでは実行してみましょう。
最初に表示される画面は、AView 画面です。
左側にあるボタンをクリックすると、画面が切り替わります。

やりかったことが実現できていました!!
おまけ
AView に Button と TextBlock を追加して、ボタンがクリックされた回数をテキストブロックに表示する処理を追加します。
AView の XAML を次のように変更します。
<UserControl
x:Class="Sample.Views.AView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Sample.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
Background="#fff"
mc:Ignorable="d">
<StackPanel Margin="10,100,20,0" Orientation="Vertical">
<Label HorizontalContentAlignment="Center" Content="AViewの画面" />
<Button
Width="100"
Command="{Binding CountCommand}"
Content="クリック" />
<TextBlock Text="{Binding ClickCount, StringFormat=クリックした回数:{0}}" TextAlignment="Center" />
</StackPanel>
</UserControl>

AView の ViewModel にはクリックした回数を保持する ClickCount プロパティを用意して、ボタンのコマンドに CountCommand をバインドします。コマンドの実行メソッドには ClickCount をインクリメントする処理を記述します。
AViewModel.cs を次のように変更します。
using ScreenTransition1.Command;
namespace Sample.ViewModels
{
public class AViewModel : ViewModelBase
{
private int clickCount = 0;
//クリックした回数を保持するプロパティ
public int ClickCount
{
get { return clickCount; }
set
{
if (clickCount != value)
{
clickCount = value;
OnPropertyChanged("ClickCount");
}
}
}
//ボタンのコマンド
public DelegateCommand CountCommand { get; }
//コンストラクタ
public AViewModel()
{
CountCommand = new DelegateCommand(CountExcute);
}
//コマンドの実行メソッド
private void CountExcute()
{
ClickCount++;
}
}
}
さて、プログラムを実行してみましょう。

上図を見ると、AView でカウントアップした値が画面遷移するとリセットされています。リセットされる原因は、MainWindowViewModel の コマンドの実行メソッドで AViewModel を都度、インスタンス化して ActiveView に代入しているからです。そのため前回の状態が保持されずにリセットされているのです。
画面遷移しても値が保持されるように、MainWindowViewModel に ViewModel のインスタンスを格納するプロパティ(viewModelTable)を作って、コンストラクタで ViewModel のインスタンスをプロパティに追加します。あとは ActiveView にプロパティに格納したインスタンスを代入すればOKです。
ViewModel のインスタンスはコンストラクタ以外では行わないようにします。
MainWindowViewModel.cs を次のように変更します。
using ScreenTransition1.Command;
using System.Collections.Generic;
namespace Sample.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private ViewModelBase activeView = null;
//画面に表示するUserControlのViewModelを設定するプロパティ
public ViewModelBase ActiveView
{
get { return activeView; }
set
{
if (activeView != value)
{
activeView = value;
OnPropertyChanged("ActiveView");
}
}
}
//ボタンのコマンドプロパティ
public DelegateCommand ScreenTransitionCommand { get; }
// ViewModelのインスタンスを格納するプロパティ
private Dictionary<string, ViewModelBase> viewModelTable { get; }
//コンストラクタ
public MainWindowViewModel()
{
ScreenTransitionCommand = new DelegateCommand(screenTransitionExcute);
//Dictionaryを使い、Keyはコマンドパラメーターの値を指定し、
//ValueはViewModelのインスタンスを指定する
viewModelTable = new Dictionary<string, ViewModelBase>
{
{ "AView", new AViewModel() },
{ "BView", new BViewModel() },
{ "CView", new CViewModel() },
};
ActiveView = viewModelTable["AView"];
}
//ボタンのコマンド実行メソッド
private void screenTransitionExcute(string param)
{
//コマンドのパラメーターによって
//ActiveViewにセットするViewModelを切り替える
ActiveView = viewModelTable[param];
}
}
}
プログラムを実行してみましょう。

これで別の画面に切り替えても前回の状態が保持されるようになりました。
以上、最後まで読んで頂きありがとうございました。

プログラミングスキルを身に付けるなら、プログラミングを効率良く学べる
「プログラミングスクール」がオススメです。
特にこんな方にオススメ!!
これからエンジニアを目指したい
プログラミングの専門性を高めたい
プログラミングを学んで副業をしたい
エンジニアに転職して年収をアップさせたい
プログラミングを触ったことがない未経験からでも、プログラミングスクールで学習すれば、エンジニアへ就職・転職することも可能です。
あなたの「行動力」と「やる気」で、あなたの人生を大きく変えるチャンスになることでしょう。
プログラミングスクールに興味がある方は是非チェックしてみてください。