Mvvm-hierarchies-and-navigation
MVVM –階層とナビゲーション
MVVMアプリケーションを構築する場合、通常、複雑な情報画面を親ビューと子ビューのセットに分解します。子ビューは、パネルまたはコンテナコントロールの親ビュー内に含まれ、使用の階層を形成します。
- 複雑なビューを分解した後、独自のXAMLファイルに分離するすべての子コンテンツが必ずしもMVVMビューである必要があるわけではありません。
- コンテンツのチャンクは、画面に何かを表示するための構造を提供するだけであり、そのコンテンツに対するユーザーによる入力や操作はサポートしていません。
- 別のViewModelを必要としない場合もありますが、親ViewModelによって公開されたプロパティに基づいてレンダリングするXAMLチャンクだけにすることもできます。
- 最後に、ViewsとViewModelsの階層がある場合、親ViewModelが通信のハブになり、各子ViewModelが他の子ViewModelsとその親から可能な限り分離されたままになることができます。
異なるビュー間の単純な階層を定義する例を見てみましょう。 新しいWPFアプリケーションプロジェクト MVVMHierarchiesDemo を作成します
- ステップ1 *-3つのフォルダー(Model、ViewModel、およびViews)をプロジェクトに追加します。
- ステップ2 *-次の図に示すように、ModelフォルダーにCustomerクラスとOrderクラス、ViewsフォルダーにCustomerListViewとOrderView、ViewModelフォルダーにCustomerListViewModelとOrderViewModelを追加します。
- ステップ3 *-CustomerListViewとOrderViewの両方にテキストブロックを追加します。 これがCustomerListView.xamlファイルです。
<UserControl x:Class="MVVMHierarchiesDemo.Views.CustomerListView"
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:MVVMHierarchiesDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<TextBlock Text = "Customer List View"/>
</Grid>
</UserControl>
次に、OrderView.xamlファイルを示します。
<UserControl x:Class = "MVVMHierarchiesDemo.Views.OrderView"
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:MVVMHierarchiesDemo.Views" mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<TextBlock Text = "Order View"/>
</Grid>
</UserControl>
ここで、これらのビューをホストするための何かが必要になります。これは単純なアプリケーションであるため、MainWindow内の適切な場所です。 ビューを配置してナビゲーション形式で切り替えることができるコンテナコントロールが必要です。 この目的のために、MainWindow.xamlファイルにContentControlを追加する必要があり、そのコンテンツプロパティを使用して、それをViewModel参照にバインドします。
次に、リソースディクショナリの各ビューのデータテンプレートを定義します。 以下はMainWindow.xamlファイルです。 各データテンプレートがデータ型(ViewModel型)を対応するビューにマップする方法に注意してください。
<Window x:Class = "MVVMHierarchiesDemo.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:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content = "{Binding CurrentView}"/>
</Grid>
</Window>
現在のビューモデルがCustomerListViewModelのインスタンスに設定されると、ViewModelが接続されたCustomerListViewがレンダリングされます。 これは注文ViewModelであり、OrderViewなどをレンダリングします。
現在、CurrentViewModelプロパティと、プロパティ内のViewModelの現在の参照を切り替えることができるいくつかのロジックとコマンドを持つViewModelが必要です。
MainWindowViewModelと呼ばれるこのMainWindowのViewModelを作成しましょう。 XAMLからViewModelのインスタンスを作成し、それを使用してウィンドウのDataContextプロパティを設定するだけです。 このために、ViewModelsのINotifyPropertyChangedの実装をカプセル化する基本クラスを作成する必要があります。
このクラスの背後にある主なアイデアは、適切な通知を簡単にトリガーできるように、INotifyPropertyChanged実装をカプセル化し、派生クラスにヘルパーメソッドを提供することです。 以下は、BindableBaseクラスの実装です。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class BindableBase : INotifyPropertyChanged {
protected virtual void SetProperty<T>(ref T member, T val,
[CallerMemberName] string propertyName = null) {
if (object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
CurrentViewModelプロパティを使用してビューの切り替えを実際に開始します。 このプロパティの設定を駆動する方法が必要です。 そして、エンドユーザーが顧客リストまたは注文ビューに移動するようにコマンドを実行できるようにします。 最初に、ICommandインターフェイスを実装するプロジェクトに新しいクラスを追加します。 以下は、ICommandインターフェイスの実装です。
using System;
using System.Windows.Input;
namespace MVVMHierarchiesDemo {
public class MyICommand<T> : ICommand {
Action<T> _TargetExecuteMethod;
Func<T, bool> _TargetCanExecuteMethod;
public MyICommand(Action<T> executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod) {
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
T tparm = (T)parameter;
return _TargetCanExecuteMethod(tparm);
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
//Beware - should use weak references if command instance lifetime is
longer than lifetime of UI objects that get hooked up to command
//Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod((T)parameter);
}
}
#endregion
}
}
次に、これらへのいくつかのトップレベルナビゲーションをViewModelsに設定する必要があり、その切り替えのロジックはMainWindowViewModel内に属する必要があります。 このために、文字列の宛先を取得してCurrentViewModelプロパティを返す、navigateと呼ばれるメソッドを使用します。
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
これらの異なるビューのナビゲーションのために、MainWindow.xamlファイルに2つのボタンを追加する必要があります。 以下は、完全なXAMLファイルの実装です。
<Window x:Class = "MVVMHierarchiesDemo.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:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto"/>
<RowDefinition Height = "*"/>
</Grid.RowDefinitions>
<Grid x:Name = "NavBar">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "*"/>
<ColumnDefinition Width = "*"/>
<ColumnDefinition Width = "*"/>
</Grid.ColumnDefinitions>
<Button Content = "Customers"
Command = "{Binding NavCommand}"
CommandParameter = "customers"
Grid.Column = "0"/>
<Button Content = "Order"
Command = "{Binding NavCommand}"
CommandParameter = "orders"
Grid.Column = "2"/>
</Grid>
<Grid x:Name = "MainContent" Grid.Row = "1">
<ContentControl Content = "{Binding CurrentViewModel}"/>
</Grid>
</Grid>
</Window>
以下は、MainWindowViewModelの完全な実装です。
using MVVMHierarchiesDemo.ViewModel;
using MVVMHierarchiesDemo.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class MainWindowViewModel : BindableBase {
public MainWindowViewModel() {
NavCommand = new MyICommand<string>(OnNav);
}
private CustomerListViewModel custListViewModel = new CustomerListViewModel();
private OrderViewModel orderViewModelModel = new OrderViewModel();
private BindableBase _CurrentViewModel;
public BindableBase CurrentViewModel {
get {return _CurrentViewModel;}
set {SetProperty(ref _CurrentViewModel, value);}
}
public MyICommand<string> NavCommand { get; private set; }
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
}
}
BindableBaseクラスからすべてのViewModelを派生します。 上記のコードをコンパイルして実行すると、次の出力が表示されます。
ご覧のとおり、MainWindowには2つのボタンとCurrentViewModelのみが追加されています。 ボタンをクリックすると、その特定のビューに移動します。 [顧客]ボタンをクリックすると、CustomerListViewが表示されます。
理解を深めるために、上記の例を段階的に実行することをお勧めします。