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가 표시되는 것을 볼 수 있습니다.

더 나은 이해를 위해 위의 예를 단계별로 실행하는 것이 좋습니다.