WPF MVVMサンプル(画面遷移)
1)概要
WPFでMVVMを使った画面遷移のサンプルを作ります。
戻る、次へボタンでページを切り替えます。
MVVMライブラリにはWCT MVVM Toolkit(Microsoft.Toolkit.Mvvm)を使用します。
2)環境
- Windows 10 Version 21H1
- .NET Framework 4.7.2
- Visual Studio 2019 Version 16.10.3
- WPF
- Microsoft.Toolkit.Mvvm Version 7.0.2
- ModernWpfUI Version 0.9.4
3)プロジェクトの作成
広く普及している.NET Framework 4.7.2で作ります。
10 (1803) |
10 (1809) |
10 (1903) |
10 (1909) |
10 (2004) |
10 (20H2) |
10 (21H1) |
|
---|---|---|---|---|---|---|---|
4.7.2 | 〇 | 〇 | 〇 | 〇 | 〇 | 〇 | 〇 |
4.8 | - | - | 〇 | 〇 | 〇 | 〇 | 〇 |
5.0 | - | - | - | - | - | - | - |
.NET 5.0で作る場合はこちら↓の3)~4)を、.NET 6.0で作る場合は12-3)~12-4-a)を参考にプロジェクトを作成してください。
新規プロジェクトでWPFアプリ(.NET Framework)
を選択します。
プロジェクト名にMvvmPageTrans
と入力し、フレームワークに.NET Framework 4.7.2
を選択します。
4)プロジェクトの設定
[ツール]-[オプション]-[NuGetパッケージマネージャー]-[既定のパッケージ管理方式]でPackageReference
を指定します。
5)NuGetパッケージ追加
以下のパッケージをNuGetで追加します。
Microsoft.Toolkit.Mvvm
ModernWpfUI
6)ファイル準備
MainWindow.xaml
を削除します。
プロジェクト直下にViewModels
フォルダとViews
フォルダを作成します。
ViewModels
フォルダにクラスMainWindowViewModel.cs
を追加します。
Views
フォルダにウィンドウ(WPF)MainWindow.xaml
を追加します。
7)画面デザイン変更
App.xaml
に
xmlns:ui="http://schemas.modernwpf.com/2019"
を追加し、
StartupUri
をViews/MainWindow.xaml
に修正し、
Application.Resources
にResourceDictionary
を追加します。
<Application x:Class="MvvmPageTrans.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MvvmPageTrans"
xmlns:ui="http://schemas.modernwpf.com/2019"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ThemeResources />
<ui:XamlControlsResources />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
</ResourceDictionary>
</Application.Resources>
</Application>
MainWindow.xaml
に
xmlns:ui="http://schemas.modernwpf.com/2019"
と
ui:WindowHelper.UseModernWindowStyle="True"
を追加します。
<Window x:Class="MvvmPageTrans.Views.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:local="clr-namespace:MvvmPageTrans.Views"
xmlns:ui="http://schemas.modernwpf.com/2019"
ui:WindowHelper.UseModernWindowStyle="True"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
</Grid>
</Window>
8)ViewModelの作成
8-1)ページのベースVMクラス作成
ViewModels
フォルダにPages
フォルダを作成し、クラスPageViewModelBase.cs
を追加します。
PageViewModelBase.cs
を以下のように編集します。
-
ObservableObject
を継承する抽象クラスにします。 - ページタイトルを保持するプロパティを用意します。
- ページの設定値一覧を文字列のコレクションで返すメソッドを用意します。既定値は空要素です。
using Microsoft.Toolkit.Mvvm.ComponentModel;
using System.Collections.Generic;
using System.Linq;
namespace MvvmPageTrans.ViewModels.Pages
{
/// <summary>
/// ページのベースVM
/// </summary>
public abstract class PageViewModelBase : ObservableObject
{
private string _pageTitle;
/// <summary>
/// ページタイトル
/// </summary>
public string PageTitle
{
get => _pageTitle;
set => SetProperty(ref _pageTitle, value);
}
/// <summary>
/// 設定文字列取得
/// </summary>
/// <returns></returns>
public virtual IEnumerable<string> GetSettingStrings()
{
return Enumerable.Empty<string>();
}
}
}
8-2)各ページのVMクラス作成
8-2-1)ファイルの用意
ViewModels/Pages
フォルダに以下のクラスを追加します。
StartPageViewModel.cs
Setting1PageViewModel.cs
Setting2PageViewModel.cs
EndPageViewModel.cs
PageViewModelBase
との関連は以下のようになります。
8-2-2)最初のページのVM作成
StartPageViewModel.cs
を以下のように編集します。
PageViewModelBase
を継承し、最初のページに表示するメッセージ用のstringプロパティを用意します。
namespace MvvmPageTrans.ViewModels.Pages
{
/// <summary>
/// スタートページのVM
/// </summary>
public class StartPageViewModel : PageViewModelBase
{
private string _message;
/// <summary>
/// スタートページに表示するメッセージ
/// </summary>
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
/// <summary>
/// コンストラクタ
/// </summary>
public StartPageViewModel()
{
PageTitle = "ページ1";
Message = "最初のページです。";
}
}
}
8-2-3)設定ページ1のVM作成
Setting1PageViewModel.cs
を以下のように編集します。
-
PageViewModelBase
を継承し、入力/設定項目用のboolプロパティを2つ用意します。 -
PageViewModelBase
のGetSettingStrings
メソッドをoverrideし、設定項目名と設定値を文字列で返すようにします。
using System.Collections.Generic;
namespace MvvmPageTrans.ViewModels.Pages
{
/// <summary>
/// 設定ページ1のVM
/// </summary>
public class Setting1PageViewModel : PageViewModelBase
{
private bool _check1;
/// <summary>
/// ON/OFF項目1
/// </summary>
public bool Check1
{
get => _check1;
set => SetProperty(ref _check1, value);
}
private bool _check2;
/// <summary>
/// ON/OFF項目2
/// </summary>
public bool Check2
{
get => _check2;
set => SetProperty(ref _check2, value);
}
/// <summary>
/// 設定文字列取得
/// </summary>
/// <returns></returns>
public override IEnumerable<string> GetSettingStrings()
{
yield return $"{nameof(Check1)}={Check1}";
yield return $"{nameof(Check2)}={Check2}";
}
/// <summary>
/// コンストラクタ
/// </summary>
public Setting1PageViewModel()
{
PageTitle = "ページ2";
Check1 = false;
Check2 = true;
}
}
}
8-2-4)設定ページ2のVM作成
Setting2PageViewModel.cs
を以下のように編集します。
-
PageViewModelBase
を継承し、入力/設定項目用のstringプロパティを2つ用意します。 -
PageViewModelBase
のGetSettingStrings
メソッドをoverrideし、設定項目名と設定値を文字列で返すようにします。
using System.Collections.Generic;
namespace MvvmPageTrans.ViewModels.Pages
{
/// <summary>
/// 設定ページ2のVM
/// </summary>
public class Setting2PageViewModel : PageViewModelBase
{
private string _text1;
/// <summary>
/// テキスト項目1
/// </summary>
public string Text1
{
get => _text1;
set => SetProperty(ref _text1, value);
}
private string _text2;
/// <summary>
/// テキスト項目2
/// </summary>
public string Text2
{
get => _text2;
set => SetProperty(ref _text2, value);
}
/// <summary>
/// 設定文字列取得
/// </summary>
/// <returns></returns>
public override IEnumerable<string> GetSettingStrings()
{
yield return $"{nameof(Text1)}={Text1}";
yield return $"{nameof(Text2)}={Text2}";
}
/// <summary>
/// コンストラクタ
/// </summary>
public Setting2PageViewModel()
{
PageTitle = "ページ3";
Text1 = "こんにちは。";
Text2 = "";
}
}
}
8-2-5)最終ページのVM作成
EndPageViewModel.cs
を以下のように編集します。
-
PageViewModelBase
を継承し、最終ページに設定値を一覧表示するためのstringプロパティを用意します。
namespace MvvmPageTrans.ViewModels.Pages
{
/// <summary>
/// 最後のページのVM
/// </summary>
public class EndPageViewModel : PageViewModelBase
{
private string _settingListText;
/// <summary>
/// 設定内容一覧テキスト
/// </summary>
public string SettingListText
{
get => _settingListText;
set => SetProperty(ref _settingListText, value);
}
/// <summary>
/// コンストラクタ
/// </summary>
public EndPageViewModel()
{
PageTitle = "ページ4";
}
}
}
8-3)メインのVMクラス作成
MainWindowViewModel.cs
を以下のように編集します。
-
ObservableObject
を継承するようにします。 - 現在表示しているページを保持するための
CurrentPage
プロパティを定義します。 - 各ページ用のVMを定義します。
- ページ管理用のリストを定義します。
- ページ切り替え用のコマンドを定義します。
using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Input;
using MvvmPageTrans.ViewModels.Pages;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MvvmPageTrans.ViewModels
{
/// <summary>
/// メインVM
/// </summary>
public class MainWindowViewModel : ObservableObject
{
private PageViewModelBase _currentPage;
/// <summary>
/// 現在ページ
/// </summary>
public PageViewModelBase CurrentPage
{
get => _currentPage;
set
{
SetProperty(ref _currentPage, value);
// ページ戻る/進むコマンドの有効/無効状態更新
PageBackCommand?.NotifyCanExecuteChanged();
PageNextCommand?.NotifyCanExecuteChanged();
}
}
/// <summary>
/// 最初のページVM
/// </summary>
public StartPageViewModel StartPage { get; }
/// <summary>
/// 設定ページ1のVM
/// </summary>
public Setting1PageViewModel Setting1Page { get; }
/// <summary>
/// 設定ページ2のVM
/// </summary>
public Setting2PageViewModel Setting2Page { get; }
/// <summary>
/// 最後のページVM
/// </summary>
public EndPageViewModel EndPage { get; }
/// <summary>
/// ページ管理リスト
/// </summary>
private List<PageViewModelBase> _pages;
/// <summary>
/// ページ戻るコマンド
/// </summary>
public IRelayCommand PageBackCommand { get; }
/// <summary>
/// ページ進むコマンド
/// </summary>
public IRelayCommand PageNextCommand { get; }
}
}
- コンストラクタで各ページ用のVMを初期化しつつページ管理リストに表示順で登録し、現在ページを
StartPage
に設定します。 - ページ切り替えコマンドの初期化を行います。
/// <summary>
/// コンストラクタ
/// </summary>
public MainWindowViewModel()
{
// ページVM初期化(ページの並び順と同じ順番で追加すること)
_pages = new List<PageViewModelBase>();
_pages.Add(StartPage = new StartPageViewModel());
_pages.Add(Setting1Page = new Setting1PageViewModel());
_pages.Add(Setting2Page = new Setting2PageViewModel());
_pages.Add(EndPage = new EndPageViewModel());
// 現在ページ設定
CurrentPage = StartPage;
// ページ戻る/進むコマンド初期化
PageBackCommand = new RelayCommand(PageBackExecute, PageBackCanExecute);
PageNextCommand = new RelayCommand(PageNextExecute, PageNextCanExecute);
}
- ページを切り替える処理を書きます。
- 最終ページを表示するとき、各ページの設定値を収集して
SettingListText
プロパティに設定します。
/// <summary>
/// ページ戻るコマンドの処理
/// </summary>
private void PageBackExecute()
{
// ページ管理リストを使用して、現在ページの1つ前を現在ページに設定
CurrentPage = _pages[_pages.FindIndex(x => x == CurrentPage) - 1];
}
/// <summary>
/// ページ戻るコマンド実行可否を設定
/// </summary>
/// <returns></returns>
private bool PageBackCanExecute()
{
// 現在ページが最初のページ以外ならコマンド実行可
return CurrentPage != _pages.First();
}
/// <summary>
/// ページ進むコマンドの処理
/// </summary>
private void PageNextExecute()
{
// ページ管理リストを使用して、現在ページの1つ次を現在ページに設定
CurrentPage = _pages[_pages.FindIndex(x => x == CurrentPage) + 1];
// 現在ページが最終ページでなければここで終了
if (CurrentPage != _pages.Last())
{
return;
}
// ここから最終ページ用の処理
var sb = new StringBuilder();
// 全設定ページの設定値を収集
foreach (var p in _pages)
{
// 設定情報取得
var settings = p.GetSettingStrings();
if (!settings.Any())
{
// 設定なし
continue;
}
// ページタイトル
sb.AppendLine($"■{p.PageTitle}");
// ページの設定項目
sb.AppendLine(string.Join(
Environment.NewLine,
settings.Select(x => $" * {x}")));
}
// 最終ページに設定値一覧を設定
EndPage.SettingListText = sb.ToString();
}
/// <summary>
/// ページ進むコマンド実行可否を設定
/// </summary>
/// <returns></returns>
private bool PageNextCanExecute()
{
// 現在ページが最終ページ以外ならコマンド実行可
return CurrentPage != _pages.Last();
}
9)Viewの作成
9-1)各ページ作成
9-1-1)ファイルの用意
Views
フォルダにPages
フォルダを作成し、以下のユーザーコントロール(WPF)を追加します。
StartPage.xaml
Setting1Page.xaml
Setting2Page.xaml
EndPage.xaml
9-1-2)最初のページ作成
StartPage.xaml
を開き、
UserControl
に
xmlns:vm="clr-namespace:MvvmPageTrans.ViewModels.Pages"
と
d:DataContext="{d:DesignInstance Type=StartPageViewModel, IsDesignTimeCreatable=True}"
を追加します。
DataContextはコードスニペットddc
で入力すると楽です。
ページ部分がわかりやすくなるよう、
Background="{DynamicResource SystemControlPageBackgroundChromeMediumLowBrush}"
を追加して背景色を変えます。
StackPanelにページ見出しとメッセージ用のTextBlockを追加し、VMのプロパティとバインディングします。
<UserControl x:Class="MvvmPageTrans.Views.Pages.StartPage"
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:MvvmPageTrans.Views.Pages"
xmlns:vm="clr-namespace:MvvmPageTrans.ViewModels.Pages"
d:DataContext="{d:DesignInstance Type=vm:StartPageViewModel,
IsDesignTimeCreatable=True}"
Background="{DynamicResource SystemControlPageBackgroundChromeMediumLowBrush}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<TextBlock Margin="0,0,0,20" Text="{Binding PageTitle, Mode=OneTime}" />
<TextBlock FontSize="40" Text="{Binding Message, Mode=OneTime}" />
</StackPanel>
</UserControl>
9-1-3)設定ページ1の作成
Setting1Page.xaml
を開き、
xmlns:ui~
, xmlns:vm~
, d:DataContext~
, Background~
を追加します。
StackPanelにページ見出しとトグルスイッチを追加し、VMのプロパティとバインディングします。
<UserControl x:Class="MvvmPageTrans.Views.Pages.Setting1Page"
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:MvvmPageTrans.Views.Pages"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:vm="clr-namespace:MvvmPageTrans.ViewModels.Pages"
d:DataContext="{d:DesignInstance Type=vm:Setting1PageViewModel,
IsDesignTimeCreatable=True}"
Background="{DynamicResource SystemControlPageBackgroundChromeMediumLowBrush}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<TextBlock Margin="0,0,0,20" Text="{Binding PageTitle, Mode=OneTime}" />
<ui:ToggleSwitch Header="チェック1" IsOn="{Binding Check1}" />
<ui:ToggleSwitch Header="チェック2" IsOn="{Binding Check2}" />
</StackPanel>
</UserControl>
9-1-4)設定ページ2の作成
Setting2Page.xaml
を開き、
xmlns:vm~
, d:DataContext~
, Background~
を追加します。
StackPanelにページ見出しと入力用のテキストボックスを追加し、VMのプロパティとバインディングします。
<UserControl x:Class="MvvmPageTrans.Views.Pages.Setting2Page"
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:MvvmPageTrans.Views.Pages"
xmlns:vm="clr-namespace:MvvmPageTrans.ViewModels.Pages"
d:DataContext="{d:DesignInstance Type=vm:Setting2PageViewModel,
IsDesignTimeCreatable=True}"
Background="{DynamicResource SystemControlPageBackgroundChromeMediumLowBrush}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<TextBlock Margin="0,0,0,20" Text="{Binding PageTitle, Mode=OneTime}" />
<TextBox Margin="0,0,0,5" Text="{Binding Text1}" />
<TextBox Margin="0,0,0,5" Text="{Binding Text2}" />
</StackPanel>
</UserControl>
9-1-5)最後のページの作成
EndPage.xaml
を開き、
xmlns:vm~
, d:DataContext~
, Background~
を追加します。
Gridを2行にし、ページ見出しと設定一覧表示用のテキストボックスを追加し、VMのプロパティとバインディングします。
<UserControl x:Class="MvvmPageTrans.Views.Pages.EndPage"
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:MvvmPageTrans.Views.Pages"
xmlns:vm="clr-namespace:MvvmPageTrans.ViewModels.Pages"
d:DataContext="{d:DesignInstance Type=vm:EndPageViewModel,
IsDesignTimeCreatable=True}"
Background="{DynamicResource SystemControlPageBackgroundChromeMediumLowBrush}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Margin="0,0,0,20"
Text="{Binding PageTitle, Mode=OneTime}" />
<TextBox
Grid.Row="1"
Margin="5"
IsReadOnly="True"
Text="{Binding SettingListText, Mode=OneWay}"
VerticalScrollBarVisibility="Visible" />
</Grid>
</UserControl>
9-2)App.xamlの編集
App.xaml
を開き、
xmlns:vm~
, xmlns:pg~
を追加します。
ResourceDictionary
に各ページのDataTemplate
を追加します。
DataType
で指定した型に基づいてDataTemplate
が自動で適用されます。
<Application
x:Class="MvvmPageTrans.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MvvmPageTrans"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:vm="clr-namespace:MvvmPageTrans.ViewModels.Pages"
xmlns:pg="clr-namespace:MvvmPageTrans.Views.Pages"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ThemeResources />
<ui:XamlControlsResources />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
<DataTemplate DataType="{x:Type vm:StartPageViewModel}">
<pg:StartPage />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:Setting1PageViewModel}">
<pg:Setting1Page />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:Setting2PageViewModel}">
<pg:Setting2Page />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:EndPageViewModel}">
<pg:EndPage />
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
9-3)メイン画面の作成
Window
に
xmlns:vm~
と
ResizeMode="CanResizeWithGrip"
を追加し、
Width
を400
, Height
を300
に編集します。
-
Window.DataContext
にVMを設定します。 -
ContentControl
にVMのCurrentPage
を設定します。 - 戻る、次へボタンにコマンドを設定します。
<Window
x:Class="MvvmPageTrans.Views.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:local="clr-namespace:MvvmPageTrans.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:vm="clr-namespace:MvvmPageTrans.ViewModels"
ResizeMode="CanResizeWithGrip"
Title="MainWindow"
Width="400"
Height="300"
ui:WindowHelper.UseModernWindowStyle="True"
mc:Ignorable="d">
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- ページ部分 -->
<ContentControl Margin="10" Content="{Binding CurrentPage, Mode=OneWay}" />
<StackPanel
Grid.Row="1"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
Margin="10"
Padding="20,5"
Command="{Binding PageBackCommand, Mode=OneTime}"
Content="< 戻る" />
<Button
Margin="10"
Padding="20,5"
Command="{Binding PageNextCommand, Mode=OneTime}"
Content="次へ >" />
</StackPanel>
</Grid>
</Window>
完成です。
10)実行
実行してみます。
最初のページ(ページ1)が表示されました。
最初のページなので戻るボタンは無効化しています。
次へボタンを押します。
設定ページ1(ページ2)が表示されました。
次へボタンを押します。
設定ページ2(ページ3)が表示されました。
次へボタンを押します。
最後のページ(ページ4)が表示されました。
設定ページの設定値が一覧表示されました。
最後のページなので次へボタンが無効化しています。
11)参考
Discussion