MVVM – 계층 및 탐색
MVVM 애플리케이션을 빌드 할 때 일반적으로 복잡한 정보 화면을 상위 및 하위보기 집합으로 분해합니다. 여기서 하위보기는 패널 또는 컨테이너 컨트롤의 상위보기 내에 포함되고 자체 사용 계층을 형성합니다.
복잡한 뷰를 분해 한 후 자체 XAML 파일로 분리하는 모든 자식 콘텐츠가 반드시 MVVM 뷰 여야한다는 의미는 아닙니다.
콘텐츠 청크는 화면에 무언가를 렌더링하기위한 구조를 제공 할 뿐이며 해당 콘텐츠에 대한 사용자의 입력이나 조작을 지원하지 않습니다.
별도의 ViewModel이 필요하지 않을 수 있지만 부모 ViewModel에 의해 노출 된 속성을 기반으로 렌더링되는 청크 XAML 일 수 있습니다.
마지막으로, View와 ViewModel의 계층이있는 경우 부모 ViewModel은 통신의 허브가 될 수 있으므로 각 자식 ViewModel은 다른 자식 ViewModel과 가능한 한 많이 분리 된 상태를 유지할 수 있습니다.
서로 다른 뷰간에 간단한 계층 구조를 정의하는 예를 살펴 보겠습니다. 새 WPF 응용 프로그램 프로젝트 만들기MVVMHierarchiesDemo
Step 1 − 프로젝트에 세 개의 폴더 (Model, ViewModel 및 Views)를 추가합니다.
Step 2 − 다음 그림과 같이 Model 폴더에 Customer와 Order 클래스를 추가하고 Views 폴더에 CustomerListView와 OrderView를 추가하고 ViewModel 폴더에 CustomerListViewModel과 OrderViewModel을 추가합니다.
Step 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 속성을 설정할 수 있습니다. 이를 위해 ViewModel에 대한 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
}
}
이제 ViewModel에 대한 최상위 탐색을 설정해야하며 해당 전환에 대한 논리는 MainWindowViewModel 내에 있어야합니다. 이를 위해 문자열 대상을 가져와 CurrentViewModel 속성을 반환하는 탐색시 호출되는 메서드를 사용할 것입니다.
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
이러한 다른 뷰를 탐색하려면 MainWindow.xaml 파일에 두 개의 버튼을 추가해야합니다. 다음은 완전한 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에 두 개의 버튼과 CurrentViewModel 만 추가했습니다. 버튼을 클릭하면 특정보기로 이동합니다. Customers 버튼을 클릭하면 CustomerListView가 표시되는 것을 볼 수 있습니다.
더 나은 이해를 위해 위의 예를 단계별로 실행하는 것이 좋습니다.