MVVM - ลำดับชั้นและการนำทาง

เมื่อสร้างแอปพลิเคชัน MVVM โดยทั่วไปคุณจะแยกหน้าจอข้อมูลที่ซับซ้อนออกเป็นชุดของมุมมองหลักและมุมมองย่อยโดยที่มุมมองลูกจะอยู่ในมุมมองหลักในแผงควบคุมหรือตัวควบคุมคอนเทนเนอร์และสร้างลำดับชั้นของการใช้งานเอง

  • หลังจากแยกมุมมองที่ซับซ้อนแล้วไม่ได้หมายความว่าเนื้อหาย่อยแต่ละส่วนที่คุณแยกออกเป็นไฟล์ XAML ของตัวเองจำเป็นต้องเป็นมุมมอง MVVM

  • ส่วนของเนื้อหาเป็นเพียงโครงสร้างในการแสดงผลบางสิ่งบนหน้าจอและไม่สนับสนุนการป้อนข้อมูลหรือการจัดการใด ๆ โดยผู้ใช้สำหรับเนื้อหานั้น

  • อาจไม่จำเป็นต้องมี ViewModel แยกต่างหาก แต่อาจเป็น XAML แบบก้อนที่แสดงผลตามคุณสมบัติที่ผู้ปกครอง ViewModel เปิดเผย

  • สุดท้ายหากคุณมีลำดับชั้นของ Views และ ViewModels พาเรนต์ ViewModel สามารถกลายเป็นฮับสำหรับการสื่อสารเพื่อให้ ViewModel ลูกแต่ละตัวยังคงแยกออกจาก ViewModels ลูกอื่น ๆ และจากพาเรนต์ได้มากที่สุด

ลองดูตัวอย่างที่เราจะกำหนดลำดับชั้นอย่างง่ายระหว่างมุมมองต่างๆ สร้างโปรเจ็กต์ WPF Application ใหม่MVVMHierarchiesDemo

Step 1 - เพิ่มสามโฟลเดอร์ (Model, ViewModel และ Views) ลงในโปรเจ็กต์ของคุณ

Step 2 - เพิ่มคลาสลูกค้าและใบสั่งในโฟลเดอร์ Model, CustomerListView และ OrderView ในโฟลเดอร์ Views และ CustomerListViewModel และ OrderViewModel ในโฟลเดอร์ ViewModel ดังที่แสดงในภาพต่อไปนี้

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 ของเราเนื่องจากเป็นแอปพลิเคชันที่เรียบง่าย เราต้องการตัวควบคุมคอนเทนเนอร์ที่เราสามารถวางมุมมองของเราและเปลี่ยนมุมมองในการนำทางได้ เพื่อจุดประสงค์นี้เราจำเป็นต้องเพิ่ม ContentControl ในไฟล์ MainWindow.xaml ของเราและเราจะใช้คุณสมบัติเนื้อหาและเชื่อมโยงเข้ากับการอ้างอิง 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 โมเดลจะแสดง CustomerListView พร้อมกับ ViewModel ที่เชื่อมต่อ มันคือ ViewModel คำสั่งมันจะแสดงผล OrderView และอื่น ๆ

ตอนนี้เราต้องการ ViewModel ที่มีคุณสมบัติ CurrentViewModel และตรรกะและคำสั่งบางอย่างเพื่อให้สามารถสลับการอ้างอิงปัจจุบันของ ViewModel ภายในคุณสมบัติได้

มาสร้าง ViewModel สำหรับ MainWindow ชื่อ MainWindowViewModel เราสามารถสร้างอินสแตนซ์ของ ViewModel ของเราจาก XAML และใช้สิ่งนั้นเพื่อตั้งค่าคุณสมบัติ DataContext ของหน้าต่าง สำหรับสิ่งนี้เราจำเป็นต้องสร้างคลาสพื้นฐานเพื่อห่อหุ้มการใช้งาน INotifyPropertyChanged สำหรับ ViewModels ของเรา

แนวคิดหลักที่อยู่เบื้องหลังคลาสนี้คือการห่อหุ้มการใช้งาน 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

private void OnNav(string destination) {
 
   switch (destination) { 
      case "orders": 
         CurrentViewModel = orderViewModelModel; 
      break; 
      case "customers": 
      default: 
         CurrentViewModel = custListViewModel; 
      break; 
   } 
}

สำหรับการนำทางของ Views ที่แตกต่างกันเราต้องเพิ่มปุ่มสองปุ่มในไฟล์ 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; 
         } 
      } 
   } 
}

รับ ViewModels ทั้งหมดของคุณจากคลาส BindableBase เมื่อโค้ดด้านบนถูกคอมไพล์และดำเนินการคุณจะเห็นผลลัพธ์ต่อไปนี้

อย่างที่คุณเห็นเราได้เพิ่มปุ่มเพียงสองปุ่มและ CurrentViewModel บนหน้าต่างหลักของเรา หากคุณคลิกปุ่มใด ๆ ระบบจะนำทางไปยังมุมมองนั้น ให้คลิกที่ปุ่มลูกค้าและคุณจะเห็นว่า CustomerListView ปรากฏขึ้น

เราขอแนะนำให้คุณดำเนินการตามตัวอย่างข้างต้นในลักษณะทีละขั้นตอนเพื่อความเข้าใจที่ดีขึ้น