MVVM - คู่มือฉบับย่อ

วิธีที่ได้รับคำสั่งอย่างดีและอาจใช้ซ้ำได้มากที่สุดในการจัดระเบียบโค้ดของคุณคือการใช้รูปแบบ 'MVVM' Model, View, ViewModel (MVVM pattern) เป็นข้อมูลเกี่ยวกับแนวทางในการจัดระเบียบและโครงสร้างโค้ดของคุณเพื่อเขียนแอปพลิเคชันที่บำรุงรักษาทดสอบได้และขยายได้

Model - เพียงแค่เก็บข้อมูลและไม่มีส่วนเกี่ยวข้องกับตรรกะทางธุรกิจใด ๆ

ViewModel - ทำหน้าที่เชื่อมโยง / เชื่อมต่อระหว่าง Model และ View และทำให้สิ่งต่างๆดูสวยงาม

View - เพียงแค่เก็บข้อมูลที่จัดรูปแบบและมอบหมายทุกอย่างให้กับโมเดล

การนำเสนอแบบแยกส่วน

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

  • ด้วยการนำเสนอที่แยกจากกันคลาสอินเทอร์เฟซผู้ใช้จะง่ายกว่ามาก แน่นอนว่ามันมี XAML แต่โค้ดที่อยู่เบื้องหลังนั้นใช้งานได้จริง

  • ตรรกะของแอปพลิเคชันอยู่ในคลาสที่แยกต่างหากซึ่งมักเรียกว่าโมเดล

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

  • นักพัฒนาจำนวนมากพยายามใช้การผูกข้อมูลเพื่อเชื่อมต่อองค์ประกอบใน XAML โดยตรงกับคุณสมบัติในโมเดล

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

  • วิธีการนำเสนอข้อมูลของคุณมักจะแตกต่างจากวิธีการจัดโครงสร้างภายใน

  • ยิ่งไปกว่านั้นอินเทอร์เฟซผู้ใช้ส่วนใหญ่มีสถานะบางอย่างที่ไม่ได้อยู่ในรูปแบบแอปพลิเคชัน

  • ตัวอย่างเช่นหากอินเทอร์เฟซผู้ใช้ของคุณใช้การลากและวางสิ่งที่ต้องติดตามสิ่งต่างๆเช่นตอนนี้รายการที่ลากไปอยู่ที่ไหนลักษณะที่ปรากฏควรเปลี่ยนไปเมื่อเคลื่อนผ่านเป้าหมายที่ลดลงที่เป็นไปได้ เปลี่ยนเมื่อรายการถูกลากไป

  • สถานะประเภทนี้อาจมีความซับซ้อนอย่างน่าประหลาดใจและจำเป็นต้องได้รับการทดสอบอย่างละเอียด

  • ในทางปฏิบัติโดยปกติคุณต้องการให้คลาสอื่นนั่งอยู่ระหว่างอินเทอร์เฟซผู้ใช้และโมเดล สิ่งนี้มีสองบทบาทที่สำคัญ

    • ขั้นแรกจะปรับรูปแบบแอปพลิเคชันของคุณสำหรับมุมมองอินเทอร์เฟซผู้ใช้เฉพาะ

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

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

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

มีสามสิ่งสำคัญที่เกิดจากการใช้ MVVM ซึ่งมีดังต่อไปนี้

การบำรุงรักษา

  • การแยกโค้ดประเภทต่างๆออกจากกันอย่างชัดเจนควรทำให้ง่ายต่อการเข้าสู่ส่วนที่ละเอียดกว่าและเน้นเฉพาะอย่างน้อยหนึ่งส่วนและทำการเปลี่ยนแปลงโดยไม่ต้องกังวล

  • นั่นหมายความว่าคุณยังคงคล่องตัวและก้าวไปสู่รุ่นใหม่ได้อย่างรวดเร็ว

ทดสอบได้

  • ด้วย MVVM โค้ดแต่ละชิ้นมีความละเอียดมากขึ้นและหากนำไปใช้อย่างถูกต้องการพึ่งพาภายนอกและภายในของคุณจะอยู่ในส่วนของโค้ดที่แยกจากส่วนต่างๆด้วยตรรกะหลักที่คุณต้องการทดสอบ

  • ทำให้ง่ายขึ้นมากในการเขียนการทดสอบหน่วยเทียบกับตรรกะหลัก

  • ตรวจสอบให้แน่ใจว่าทำงานได้ถูกต้องเมื่อเขียนและทำงานต่อไปแม้ว่าสิ่งต่างๆจะเปลี่ยนไปในการบำรุงรักษา

ความสามารถในการขยาย

  • บางครั้งมันทับซ้อนกับความสามารถในการบำรุงรักษาเนื่องจากขอบเขตการแยกที่สะอาดและโค้ดที่ละเอียดกว่า

  • คุณมีโอกาสที่ดีกว่าในการทำให้ชิ้นส่วนเหล่านั้นสามารถนำกลับมาใช้ใหม่ได้มากขึ้น

  • นอกจากนี้ยังมีความสามารถในการแทนที่หรือเพิ่มโค้ดชิ้นใหม่ที่ทำสิ่งที่คล้ายกันในตำแหน่งที่เหมาะสมในสถาปัตยกรรม

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

  • ViewModel นั้นง่ายต่อการทดสอบหน่วยมากกว่าโค้ดเบื้องหลังหรือโค้ดที่ขับเคลื่อนด้วยเหตุการณ์
  • คุณสามารถทดสอบได้โดยไม่ต้องใช้ระบบอัตโนมัติและการโต้ตอบ UI ที่น่าอึดอัดใจ
  • เลเยอร์การนำเสนอและตรรกะอยู่คู่กันอย่างหลวม ๆ

ข้อเสีย

  • บางคนคิดว่าสำหรับ UI แบบธรรมดา MVVM อาจเกินความจำเป็น
  • ในกรณีที่ใหญ่กว่านั้นการออกแบบ ViewModel อาจทำได้ยาก
  • การดีบักจะค่อนข้างยากเมื่อเรามีการเชื่อมโยงข้อมูลที่ซับซ้อน

รูปแบบ MVVM ประกอบด้วยสามส่วน - Model, View และ ViewModel นักพัฒนาส่วนใหญ่ในช่วงเริ่มต้นจะสับสนเล็กน้อยว่า Model, View และ ViewModel ควรมีหรือไม่ควรมีอะไรบ้างและความรับผิดชอบของแต่ละส่วนคืออะไร

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

  • เลเยอร์การนำเสนอประกอบด้วยมุมมอง

  • โลจิคัลเลเยอร์เป็นโมเดลมุมมอง

  • เลเยอร์การนำเสนอคือการรวมกันของวัตถุแบบจำลอง

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

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

ความรับผิดชอบของโมเดล

โดยทั่วไปโมเดลเป็นสิ่งที่เข้าใจง่ายที่สุด เป็นโมเดลข้อมูลฝั่งไคลเอ็นต์ที่รองรับมุมมองในแอปพลิเคชัน

  • ประกอบด้วยวัตถุที่มีคุณสมบัติและตัวแปรบางตัวที่มีข้อมูลในหน่วยความจำ

  • คุณสมบัติเหล่านั้นบางอย่างอาจอ้างอิงอ็อบเจ็กต์โมเดลอื่นและสร้างกราฟอ็อบเจ็กต์ซึ่งโดยรวมคืออ็อบเจ็กต์โมเดล

  • โมเดลอ็อบเจ็กต์ควรแจ้งการเปลี่ยนแปลงคุณสมบัติซึ่งใน WPF หมายถึงการผูกข้อมูล

  • ความรับผิดชอบสุดท้ายคือการตรวจสอบความถูกต้องซึ่งเป็นทางเลือก แต่คุณสามารถฝังข้อมูลการตรวจสอบความถูกต้องบนโมเดลอ็อบเจ็กต์ได้โดยใช้คุณลักษณะการตรวจสอบการผูกข้อมูล WPF ผ่านอินเทอร์เฟซเช่น INotifyDataErrorInfo / IDataErrorInfo

ดูความรับผิดชอบ

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

  • ส่วนแบบคงที่คือลำดับชั้นของ XAML ที่กำหนดการควบคุมและโครงร่างของตัวควบคุมที่มุมมองประกอบด้วย

  • ส่วนไดนามิกก็เหมือนกับภาพเคลื่อนไหวหรือการเปลี่ยนแปลงสถานะที่กำหนดให้เป็นส่วนหนึ่งของมุมมอง

  • เป้าหมายหลักของ MVVM คือไม่ควรมีโค้ดอยู่ข้างหลังในมุมมอง

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

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

  • นอกจากนี้ยังมีโค้ดประเภทอื่น ๆ ที่ต้องอยู่ในโค้ดหลังโค้ดใด ๆ ที่จำเป็นต้องมีการอ้างอิงถึงองค์ประกอบ UI คือโค้ดดูโดยเนื้อแท้

ความรับผิดชอบของ ViewModel

  • ViewModel เป็นจุดหลักของแอปพลิเคชัน MVVM ความรับผิดชอบหลักของ ViewModel คือการให้ข้อมูลกับมุมมองเพื่อให้มุมมองนั้นสามารถใส่ข้อมูลนั้นบนหน้าจอได้

  • นอกจากนี้ยังช่วยให้ผู้ใช้สามารถโต้ตอบกับข้อมูลและเปลี่ยนแปลงข้อมูลได้

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

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

  • ViewModel ควรจัดการตรรกะการนำทางเช่นการตัดสินใจเมื่อถึงเวลาที่จะไปยังมุมมองอื่น

ในบทนี้เราจะเรียนรู้วิธีการใช้รูปแบบ MVVM สำหรับหน้าจอป้อนข้อมูลอย่างง่ายและแอปพลิเคชัน WPF ที่คุณอาจคุ้นเคยอยู่แล้ว

มาดูตัวอย่างง่ายๆที่เราจะใช้แนวทาง MVVM

Step 1 - สร้างโครงการแอปพลิเคชัน WPF ใหม่ MVVMDemo

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

Step 3 - เพิ่มคลาส StudentModel ในโฟลเดอร์ Model และวางโค้ดด้านล่างในคลาสนั้น

using System.ComponentModel;

namespace MVVMDemo.Model {
 
   public class StudentModel {}
	
   public class Student : INotifyPropertyChanged {
      private string firstName; 
      private string lastName;
		
      public string FirstName { 
         get { 
            return firstName; 
         }
			
         set { 
            if (firstName != value) { 
               firstName = value; 
               RaisePropertyChanged("FirstName"); 
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
		
      public string LastName { 
         get {return lastName; } 
			
         set {
            if (lastName != value) { 
               lastName = value;
               RaisePropertyChanged("LastName");
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
		
      public string FullName { 
         get { 
            return firstName + " " + lastName; 
         } 
      }
		
      public event PropertyChangedEventHandler PropertyChanged;
		
      private void RaisePropertyChanged(string property) {
         if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
         } 
      } 
   } 
}

Step 4 - เพิ่มคลาส StudentViewModel อื่นในโฟลเดอร์ ViewModel และวางรหัสต่อไปนี้

using MVVMDemo.Model; 
using System.Collections.ObjectModel;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel { 
	
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
				
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      } 
   } 
}

Step 5 - เพิ่ม User Control (WPF) ใหม่โดยคลิกขวาที่โฟลเดอร์ Views และเลือก Add> New Item ...

Step 6- คลิกปุ่มเพิ่ม ตอนนี้คุณจะเห็นไฟล์ XAML เพิ่มรหัสต่อไปนี้ลงในไฟล์ StudentView.xaml ซึ่งมีองค์ประกอบ UI ที่แตกต่างกัน

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   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:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
		
         <ItemsControl ItemsSource = "{Binding Path = Students}">
			
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
                  <StackPanel Orientation = "Horizontal">
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/>
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/>
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/>
								
                  </StackPanel> 
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
				
         </ItemsControl> 
			
      </StackPanel> 
   </Grid> 
	
</UserControl>

Step 7 - เพิ่ม StudentView ลงในไฟล์ MainPage.xaml ของคุณโดยใช้รหัสต่อไปนี้

<Window x:Class = "MVVMDemo.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:MVVMDemo"
   xmlns:views = "clr-namespace:MVVMDemo.Views"
   mc:Ignorable = "d"
   Title = "MainWindow" Height = "350" Width = "525">
	
   <Grid>
      <views:StudentView x:Name = "StudentViewControl" Loaded = "StudentViewControl_Loaded"/>
   </Grid>
	
</Window>

Step 8 - นี่คือการนำไปใช้งานสำหรับเหตุการณ์ Loaded ในไฟล์ MainPage.xaml.cs ซึ่งจะอัปเดต View จาก ViewModel

using System.Windows;

namespace MVVMDemo {

   /// <summary>
      /// Interaction logic for MainWindow.xaml
   /// </summary>
	
   public partial class MainWindow : Window {
	
      public MainWindow() {
         InitializeComponent();
      }
		
      private void StudentViewControl_Loaded(object sender, RoutedEventArgs e) {
         MVVMDemo.ViewModel.StudentViewModel studentViewModelObject = 
            new MVVMDemo.ViewModel.StudentViewModel();
         studentViewModelObject.LoadStudents();
			
         StudentViewControl.DataContext = studentViewModelObject;
      }
   }
}

Step 9 - เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานคุณจะได้รับผลลัพธ์ต่อไปนี้บนหน้าต่างหลักของคุณ

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

ในบทนี้เราจะกล่าวถึงวิธีต่างๆที่คุณสามารถทำให้มุมมองของคุณเชื่อมต่อกับ ViewModel ก่อนอื่นมาดูที่ View first construction ซึ่งเราสามารถประกาศใน XAML ดังที่เราได้เห็นตัวอย่างในบทสุดท้ายที่เราได้เชื่อมต่อมุมมองจากหน้าต่างหลัก ตอนนี้เราจะเห็นวิธีอื่น ๆ ในการเชื่อมต่อมุมมอง

เราจะใช้ตัวอย่างเดียวกันในบทนี้ด้วย ต่อไปนี้เป็นการใช้งานคลาส Model เดียวกัน

using System.ComponentModel;

namespace MVVMDemo.Model {
 
   public class StudentModel {} 
	
   public class Student : INotifyPropertyChanged { 
      private string firstName; 
      private string lastName;
		
      public string FirstName { 
         get { return firstName; }
			
         set { 
            if (firstName != value) { 
               firstName = value; 
               RaisePropertyChanged("FirstName");
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
	
      public string LastName {
         get { return lastName; } 
			
         set { 
            if (lastName != value) { 
               lastName = value; 
               RaisePropertyChanged("LastName");
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
	
      public string FullName { 
         get { 
            return firstName + " " + lastName; 
         } 
      }
	
      public event PropertyChangedEventHandler PropertyChanged;
	
      private void RaisePropertyChanged(string property) { 
         if (PropertyChanged != null) { 
            PropertyChanged(this, new PropertyChangedEventArgs(property)); 
         } 
      } 
   }  
}

นี่คือการใช้งานคลาส ViewModel เวลานี้วิธี LoadStudents ถูกเรียกในตัวสร้างเริ่มต้น

using MVVMDemo.Model; 
using System.Collections.ObjectModel;

namespace MVVMDemo.ViewModel{ 

   public class StudentViewModel { 
	
      public StudentViewModel() { 
         LoadStudents(); 
      } 
		
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
			
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      } 
   } 
}

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

  • ดูโครงสร้างแรกใน XAML
  • ดูการก่อสร้างครั้งแรกใน Code-behind

ดูโครงสร้างแรกใน XAML

วิธีหนึ่งคือเพิ่ม ViewModel ของคุณเป็นองค์ประกอบที่ซ้อนกันใน setter สำหรับคุณสมบัติ DataContext ดังที่แสดงในโค้ดต่อไปนี้

<UserControl.DataContext> 
   <viewModel:StudentViewModel/> 
</UserControl.DataContext>

นี่คือไฟล์ View XAML ที่สมบูรณ์

<UserControl x:Class="MVVMDemo.Views.StudentView"
   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:MVVMDemo.Views"
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
   mc:Ignorable = "d"
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.DataContext>
      <viewModel:StudentViewModel/>
   </UserControl.DataContext>
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}">
			
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"> 
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                      <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/>
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate>
				
         </ItemsControl> 
      </StackPanel> 
   </Grid> 
	
</UserControl>

ดูการก่อสร้างครั้งแรกใน Code-behind

อีกวิธีหนึ่งคือคุณสามารถรับโครงสร้าง View first ได้โดยการสร้างโมเดลมุมมองด้วยตัวคุณเองในโค้ดด้านหลัง View ของคุณโดยตั้งค่าคุณสมบัติ DataContext ที่นั่นด้วยอินสแตนซ์

โดยทั่วไปคุณสมบัติ DataContext จะถูกตั้งค่าในเมธอดคอนสตรัคเตอร์ของมุมมอง แต่คุณยังสามารถเลื่อนการสร้างได้จนกว่าเหตุการณ์โหลดของมุมมองจะเริ่ม

using System.Windows.Controls;

namespace MVVMDemo.Views {
 
   /// <summary> 
      /// Interaction logic for StudentView.xaml 
   /// </summary> 
	
   public partial class StudentView : UserControl { 
      public StudentView() { 
         InitializeComponent(); 
         this.DataContext = new MVVMDemo.ViewModel.StudentViewModel(); 
      } 
   } 
}

เหตุผลหนึ่งในการสร้างโมเดลมุมมองใน Code-behind แทนที่จะเป็น XAML คือตัวสร้างโมเดล View รับพารามิเตอร์ แต่การแยกวิเคราะห์ XAML สามารถสร้างองค์ประกอบได้หากกำหนดไว้ในตัวสร้างเริ่มต้นเท่านั้น

ในกรณีนี้ไฟล์ XAML ของ View จะมีลักษณะดังที่แสดงในโค้ดต่อไปนี้

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   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:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" 
   d:DesignWidth = "300">
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}"> 
			
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"<
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/> 
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate>
				
         </ItemsControl> 
      </StackPanel> 
   </Grid>
	
</UserControl>

คุณสามารถประกาศ View นี้ใน MainWindow ดังที่แสดงในไฟล์ MainWindow.XAML

<Window x:Class = "MVVMDemo.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:MVVMDemo" 
   xmlns:views = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525"> 
	
   <Grid> 
      <views:StudentView x:Name = "StudentViewControl"/> 
   </Grid> 
	
</Window>

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

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

ในบทนี้เราจะพูดถึงวิธีการเชื่อมต่อ ViewModel มันเป็นความต่อเนื่องของบทสุดท้ายที่เราได้พูดถึง View first construction ตอนนี้รูปแบบต่อไปของการก่อสร้างครั้งแรกคือไฟล์meta-pattern ซึ่งเรียกว่า ViewModelLocator. เป็นรูปแบบหลอกและวางซ้อนทับรูปแบบ MVVM

  • ใน MVVM แต่ละ View จะต้องเชื่อมต่อกับ ViewModel

  • ViewModelLocator เป็นวิธีง่ายๆในการรวมรหัสไว้ที่ส่วนกลางและแยกส่วนมุมมองให้มากขึ้น

  • หมายความว่าไม่จำเป็นต้องรู้อย่างชัดเจนเกี่ยวกับประเภทของ ViewModel และวิธีการสร้าง

  • มีหลายวิธีในการใช้ ViewModelLocator แต่ที่นี่เราใช้วิธีที่คล้ายกันมากที่สุดซึ่งเป็นส่วนหนึ่งของกรอบงาน PRISM

ViewModelLocator เป็นมาตรฐานที่สอดคล้องกันประกาศและเชื่อมโยงกันอย่างหลวม ๆ ในการดูโครงสร้างแรกซึ่งทำให้กระบวนการรับ ViewModel เชื่อมต่อกับ View โดยอัตโนมัติ รูปต่อไปนี้แสดงถึงกระบวนการระดับสูงของ ViewModelLocator

Step 1 - ดูว่ากำลังสร้างมุมมองประเภทใด

Step 2 - ระบุ ViewModel สำหรับประเภท View นั้น ๆ

Step 3 - สร้าง ViewModel นั้น

Step 4 - ตั้งค่า Views DataContext เป็น ViewModel

เพื่อทำความเข้าใจแนวคิดพื้นฐานเรามาดูตัวอย่างง่ายๆของ ViewModelLocator โดยดำเนินการต่อจากตัวอย่างเดียวกันจากบทสุดท้าย หากคุณดูไฟล์ StudentView.xaml คุณจะเห็นว่าเราได้เชื่อมต่อ ViewModel แบบคงที่

ตามที่แสดงในโปรแกรมต่อไปนี้แสดงความคิดเห็นโค้ด XAML เหล่านี้จะลบโค้ดออกจาก Code-behind

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   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:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
	
   <!--<UserControl.DataContext> 
      <viewModel:StudentViewModel/> 
   </UserControl.DataContext>-->
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}">
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal">
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/> 
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
         </ItemsControl> 
      </StackPanel> 
   </Grid>
	
</UserControl>

ตอนนี้ให้สร้างโฟลเดอร์ใหม่ VML และเพิ่มคลาสสาธารณะใหม่ ViewModelLocator ซึ่งจะมีคุณสมบัติที่แนบมาเดียว (คุณสมบัติการพึ่งพา) AutoHookedUpViewModel ดังแสดงในรหัสต่อไปนี้

public static bool GetAutoHookedUpViewModel(DependencyObject obj) { 
   return (bool)obj.GetValue(AutoHookedUpViewModelProperty); 
}

public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) { 
   obj.SetValue(AutoHookedUpViewModelProperty, value); 
}

// Using a DependencyProperty as the backing store for AutoHookedUpViewModel. 
//This enables animation, styling, binding, etc...
 
public static readonly DependencyProperty AutoHookedUpViewModelProperty =
   DependencyProperty.RegisterAttached("AutoHookedUpViewModel",
   typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false,
   AutoHookedUpViewModelChanged));

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

private static void AutoHookedUpViewModelChanged(DependencyObject d, 
   DependencyPropertyChangedEventArgs e) { 
   if (DesignerProperties.GetIsInDesignMode(d)) return; 
   var viewType = d.GetType(); 
   string str = viewType.FullName; 
   str = str.Replace(".Views.", ".ViewModel."); 
	
   var viewTypeName = str; 
   var viewModelTypeName = viewTypeName + "Model"; 
   var viewModelType = Type.GetType(viewModelTypeName); 
   var viewModel = Activator.CreateInstance(viewModelType);
   ((FrameworkElement)d).DataContext = viewModel; 
}

ต่อไปนี้คือการนำคลาส ViewModelLocator ไปใช้อย่างสมบูรณ์

using System; 
using System.ComponentModel; 
using System.Windows;

namespace MVVMDemo.VML { 

   public static class ViewModelLocator { 
	
      public static bool GetAutoHookedUpViewModel(DependencyObject obj) {
         return (bool)obj.GetValue(AutoHookedUpViewModelProperty); 
      }
		
      public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) { 
         obj.SetValue(AutoHookedUpViewModelProperty, value); 
      }
		
      // Using a DependencyProperty as the backing store for AutoHookedUpViewModel. 
		
      //This enables animation, styling, binding, etc...
      public static readonly DependencyProperty AutoHookedUpViewModelProperty =
         DependencyProperty.RegisterAttached("AutoHookedUpViewModel", 
         typeof(bool), typeof(ViewModelLocator), new
         PropertyMetadata(false, AutoHookedUpViewModelChanged));
		
      private static void AutoHookedUpViewModelChanged(DependencyObject d,
         DependencyPropertyChangedEventArgs e) { 
         if (DesignerProperties.GetIsInDesignMode(d)) return; 
         var viewType = d.GetType(); 
			
         string str = viewType.FullName; 
         str = str.Replace(".Views.", ".ViewModel."); 
			
         var viewTypeName = str; 
         var viewModelTypeName = viewTypeName + "Model";
         var viewModelType = Type.GetType(viewModelTypeName); 
         var viewModel = Activator.CreateInstance(viewModelType);
			
        ((FrameworkElement)d).DataContext = viewModel; 
      } 
   } 
}

สิ่งแรกที่ต้องทำคือเพิ่มเนมสเปซเพื่อให้เราสามารถเข้าถึงประเภท ViewModelLocator นั้นในรูทของโปรเจ็กต์ของเรา จากนั้นในองค์ประกอบเส้นทางซึ่งเป็นประเภทมุมมองให้เพิ่มคุณสมบัติ AutoHookedUpViewModel และตั้งค่าเป็นจริง

xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"

นี่คือการใช้งานไฟล์ StudentView.xaml ที่สมบูรณ์

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   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:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
   
   <!--<UserControl.DataContext> 
      <viewModel:StudentViewModel/> 
   </UserControl.DataContext>-->

   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}"> 
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"> 
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/>
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
         </ItemsControl> 
      </StackPanel>
   </Grid> 
	
</UserControl>

เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานคุณจะเห็นว่า ViewModelLocator กำลังเชื่อมต่อ ViewModel สำหรับ View นั้น ๆ

สิ่งสำคัญที่ต้องสังเกตเกี่ยวกับเรื่องนี้คือมุมมองไม่ได้เชื่อมโยงกันอีกต่อไปว่า ViewModel เป็นประเภทใดหรือสร้างอย่างไร นั่นคือทั้งหมดที่ถูกย้ายออกไปยังตำแหน่งกลางภายใน ViewModelLocator

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

  • สำหรับการเชื่อมโยงข้อมูลคุณต้องมีมุมมองหรือชุดขององค์ประกอบ UI ที่สร้างขึ้นจากนั้นคุณต้องมีวัตถุอื่นที่การเชื่อมโยงจะชี้ไป

  • องค์ประกอบ UI ในมุมมองถูกผูกไว้กับคุณสมบัติที่ ViewModel เปิดเผย

  • ลำดับที่สร้าง View และ ViewModel ขึ้นอยู่กับสถานการณ์เนื่องจากเราได้กล่าวถึง View ก่อน

  • View และ ViewModel จะถูกสร้างขึ้นและ DataContext ของ View จะถูกตั้งค่าเป็น ViewModel

  • การเชื่อมโยงอาจเป็นการเชื่อมโยงข้อมูล OneWay หรือ TwoWay เพื่อส่งข้อมูลไปมาระหว่าง View และ ViewModel

มาดูการเชื่อมโยงข้อมูลในตัวอย่างเดียวกัน ด้านล่างนี้คือรหัส XAML ของ StudentView

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   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:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">

   <!--<UserControl.DataContext> 
      <viewModel:StudentViewModel/> 
   </UserControl.DataContext>--> 

   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}"> 
            <ItemsControl.ItemTemplate>
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"> 
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/>
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/> 
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
         </ItemsControl> 
      </StackPanel> 
   </Grid> 

</UserControl>
  • หากคุณดูรหัส XAML ด้านบนคุณจะเห็นว่า ItemsControl ถูกผูกไว้กับคอลเลกชันนักเรียนที่เปิดเผยโดย ViewModel

  • นอกจากนี้คุณยังสามารถดูได้ว่าคุณสมบัติของ Student model มีการเชื่อมโยงของแต่ละบุคคลเช่นกันและสิ่งเหล่านี้จะถูกผูกไว้กับ Textboxes และ TextBlock

  • ItemSource ของ ItemsControl สามารถผูกกับคุณสมบัติ Students ได้เนื่องจาก DataContext โดยรวมสำหรับ View ถูกตั้งค่าเป็น ViewModel

  • การผูกคุณสมบัติแต่ละรายการที่นี่ยังเป็นการรวม DataContext แต่จะไม่เชื่อมโยงกับ ViewModel เองเนื่องจากวิธีการทำงานของ ItemSource

  • เมื่อแหล่งไอเท็มเชื่อมโยงกับคอลเล็กชันมันจะแสดงผลคอนเทนเนอร์สำหรับแต่ละไอเท็มในการแสดงผลและตั้งค่า DataContext ของคอนเทนเนอร์นั้นเป็นไอเท็ม ดังนั้น DataContext โดยรวมสำหรับแต่ละกล่องข้อความและบล็อกข้อความภายในแถวจะเป็นนักเรียนแต่ละคนในคอลเล็กชัน และคุณยังสามารถดูได้ว่าการผูกข้อมูลเหล่านี้สำหรับ TextBox เป็นการผูกข้อมูลแบบ TwoWay และสำหรับ TextBlock เป็นการผูกข้อมูล OneWay เนื่องจากคุณไม่สามารถแก้ไข TextBlock ได้

เมื่อคุณเรียกใช้แอปพลิเคชันนี้อีกครั้งคุณจะเห็นผลลัพธ์ต่อไปนี้

ตอนนี้ให้เราเปลี่ยนข้อความในกล่องข้อความที่สองของแถวแรกจาก Allain เป็น Upston และกดแท็บเพื่อสูญเสียโฟกัส คุณจะเห็นว่าข้อความ TextBlock ได้รับการอัปเดตด้วย

เนื่องจากการผูกของกล่องข้อความถูกตั้งค่าเป็น TwoWay และจะอัปเดตโมเดลด้วยและจากแบบจำลองอีกครั้งมีการอัปเดต TextBlock

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

ใน MVVM มีรูปแบบหลักอีกรูปแบบหนึ่งซึ่งเรียกว่า ViewModel first construction

  • แนวทางการสร้างแบบแรกของ ViewModel ใช้ประโยชน์จากความสามารถของเทมเพลตข้อมูลโดยนัยใน WPF

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

มาดูตัวอย่างง่ายๆของเราอีกครั้งซึ่งคุณจะเข้าใจวิธีดูโมเดลก่อนใช้ประโยชน์จากเทมเพลตข้อมูลโดยเฉพาะเทมเพลตข้อมูลโดยนัย นี่คือการนำคลาส StudentViewModel ของเราไปใช้งาน

using MVVMDemo.Model; 
using System.Collections.ObjectModel;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel {
	
      public StudentViewModel() { 
         LoadStudents(); 
      } 
		
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
			
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      } 
   } 
}

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

<UserControl.Resources> 
   <DataTemplate x:Key = "studentsTemplate">
	
      <StackPanel Orientation = "Horizontal"> 
         <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
            Width = "100" Margin = "3 5 3 5"/> 
				
         <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
            Width = "100" Margin = "0 5 3 5"/> 
				
         <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
            Margin = "0 5 3 5"/> 
      </StackPanel> 
		
   </DataTemplate> 
</UserControl.Resources>

ตอนนี้เพิ่มกล่องรายการและข้อมูลผูกกล่องรายการนั้นกับคุณสมบัติของนักเรียนตามที่แสดงในรหัสต่อไปนี้

<ListBox ItemsSource = "{Binding Students}" ItemTemplate = "{StaticResource studentsTemplate}"/>

ในส่วนทรัพยากร DataTemplate มีคีย์ของ studentsTemplate จากนั้นในการใช้เทมเพลตนั้นจริงเราจำเป็นต้องใช้คุณสมบัติ ItemTemplate ของ ListBox ตอนนี้คุณจะเห็นได้ว่าเราสั่งให้ listbox ใช้เทมเพลตนั้นเพื่อแสดงผลนักเรียนเหล่านั้น ต่อไปนี้คือการนำไฟล์ StudentView.xaml ไปใช้งานอย่างสมบูรณ์

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   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:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.Resources> 
      <DataTemplate x:Key = "studentsTemplate"> 
		
         <StackPanel Orientation = "Horizontal"> 
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
					
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>
	
   <Grid> 
      <ListBox 
         ItemsSource = "{Binding Students}" 
         ItemTemplate = "{StaticResource studentsTemplate}"/> 
   </Grid>
	
</UserControl>

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

ในการสร้างเทมเพลตโดยนัยเราจำเป็นต้องลบคุณสมบัติ ItemTemplate ออกจากกล่องรายการและเพิ่มคุณสมบัติ DataType ในข้อกำหนดเทมเพลตของเราดังที่แสดงในโค้ดต่อไปนี้

<UserControl.Resources> 
   <DataTemplate DataType = "{x:Type data:Student}">
	
      <StackPanel Orientation = "Horizontal"> 
         <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
            Width = "100" Margin = "3 5 3 5"/> 
				
         <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
            Width = "100" Margin = "0 5 3 5"/> 
				
         <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
            Margin = "0 5 3 5"/> 
				
      </StackPanel> 
		
   </DataTemplate> 
</UserControl.Resources>
 
<Grid> 
   <ListBox ItemsSource = "{Binding Students}"/> 
</Grid>

ใน DataTemplate ส่วนขยายมาร์กอัป x: Type มีความสำคัญมากซึ่งเปรียบเสมือนตัวดำเนินการประเภทหนึ่งใน XAML ดังนั้นโดยพื้นฐานแล้วเราต้องชี้ไปที่ประเภทข้อมูล Student ซึ่งอยู่ใน MVVMDemo โมเดลเนมสเปซ ต่อไปนี้เป็นไฟล์ XAML ที่อัปเดตแล้ว

<UserControl x:Class="MVVMDemo.Views.StudentView" 
   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:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:data = "clr-namespace:MVVMDemo.Model" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.Resources> 
      <DataTemplate DataType = "{x:Type data:Student}"> 
		
         <StackPanel Orientation = "Horizontal"> 
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
					
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>
	
   <Grid>
      <ListBox ItemsSource = "{Binding Students}"/> 
   </Grid> 
	
</UserControl>

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

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

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

ดู / ViewModel การสื่อสารผ่านคำสั่ง

รูปแบบคำสั่งได้รับการจัดทำเป็นเอกสารอย่างดีและมักใช้รูปแบบการออกแบบเป็นเวลาสองสามทศวรรษ ในรูปแบบนี้มีผู้แสดงหลักสองคนคือผู้เรียกร้องและผู้รับ

Invoker

  • Invoker เป็นโค้ดส่วนหนึ่งที่สามารถเรียกใช้ตรรกะที่จำเป็นบางอย่างได้

  • โดยทั่วไปแล้วจะเป็นองค์ประกอบ UI ที่ผู้ใช้โต้ตอบด้วยในบริบทของกรอบงาน UI

  • มันอาจเป็นโค้ดตรรกะอีกชิ้นที่อื่นในแอปพลิเคชัน

ผู้รับ

  • ตัวรับคือตรรกะที่มีไว้สำหรับการดำเนินการเมื่อผู้เรียกใช้เริ่มทำงาน

  • ในบริบทของ MVVM ตัวรับมักเป็นวิธีการใน ViewModel ของคุณที่ต้องถูกเรียกใช้

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

มาดูตัวอย่างง่ายๆที่คุณจะได้เรียนรู้คำสั่งและวิธีใช้คำสั่งเหล่านี้เพื่อสื่อสารระหว่าง View และ ViewModel ในบทนี้เราจะดำเนินการต่อด้วยตัวอย่างเดียวกันจากบทสุดท้าย

ในไฟล์ StudentView.xaml เรามี ListBox ที่เชื่อมต่อข้อมูลนักเรียนจาก ViewModel ตอนนี้เรามาเพิ่มปุ่มสำหรับลบนักเรียนออกจาก ListBox

สิ่งสำคัญคือการทำงานกับคำสั่งบนปุ่มนั้นง่ายมากเพราะมีคุณสมบัติคำสั่งเพื่อเชื่อมต่อกับ ICommand

ดังนั้นเราจึงสามารถแสดงคุณสมบัติบน ViewModel ของเราที่มี ICommand และผูกเข้ากับคุณสมบัตินั้นจากคุณสมบัติคำสั่งของปุ่มดังที่แสดงในโค้ดต่อไปนี้

<Button Content = "Delete" 
   Command = "{Binding DeleteCommand}" 
   HorizontalAlignment = "Left" 
   VerticalAlignment = "Top" 
   Width = "75" />

มาเพิ่มคลาสใหม่ในโครงการของคุณซึ่งจะใช้อินเทอร์เฟซ ICommand ต่อไปนี้คือการนำอินเทอร์เฟซ ICommand มาใช้

using System; 
using System.Windows.Input;

namespace MVVMDemo { 

   public class MyICommand : ICommand { 
      Action _TargetExecuteMethod; 
      Func<bool> _TargetCanExecuteMethod;
		
      public MyICommand(Action executeMethod) {
         _TargetExecuteMethod = executeMethod; 
      }
		
      public MyICommand(Action executeMethod, Func<bool> canExecuteMethod){ 
         _TargetExecuteMethod = executeMethod;
         _TargetCanExecuteMethod = canExecuteMethod; 
      }
		
      public void RaiseCanExecuteChanged() { 
         CanExecuteChanged(this, EventArgs.Empty); 
      }
		
      bool ICommand.CanExecute(object parameter) { 
		
         if (_TargetCanExecuteMethod != null) { 
            return _TargetCanExecuteMethod(); 
         } 
			
         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(); 
         } 
      } 
   } 
}

อย่างที่คุณเห็นนี่เป็นการใช้งาน ICommand แบบง่าย ๆ โดยที่เรามีผู้รับมอบสิทธิ์สองคนคนหนึ่งสำหรับ executeMethod และอีกหนึ่งคนสำหรับ canExecuteMethod ซึ่งสามารถส่งต่อในการก่อสร้างได้

ในการใช้งานข้างต้นมีตัวสร้างที่โอเวอร์โหลดสองตัวตัวหนึ่งสำหรับ executeMethod เท่านั้นและอีกตัวหนึ่งสำหรับทั้ง executeMethod และฉัน canExecuteMethod

มาเพิ่มคุณสมบัติของประเภท MyICommand ในคลาส StudentView Model ตอนนี้เราต้องสร้างอินสแตนซ์ใน StudentViewModel เราจะใช้ตัวสร้างที่โอเวอร์โหลดของ MyICommand ที่รับสองพารามิเตอร์

public MyICommand DeleteCommand { get; set;} 

public StudentViewModel() { 
   LoadStudents(); 
   DeleteCommand = new MyICommand(OnDelete, CanDelete); 
}

ตอนนี้เพิ่มการใช้งานวิธี OnDelete และ CanDelete

private void OnDelete() { 
   Students.Remove(SelectedStudent); 
}

private bool CanDelete() { 
   return SelectedStudent != null; 
}

เรายังต้องเพิ่ม SelectedStudent ใหม่เพื่อให้ผู้ใช้สามารถลบ Selected Item จาก ListBox ได้

private Student _selectedStudent;
 
public Student SelectedStudent { 
   get { 
      return _selectedStudent; 
   } 
	
   set { 
      _selectedStudent = value;
      DeleteCommand.RaiseCanExecuteChanged(); 
   } 
}

ต่อไปนี้คือการนำคลาส ViewModel ที่สมบูรณ์ไปใช้งาน

using MVVMDemo.Model; 

using System.Collections.ObjectModel; 
using System.Windows.Input; 
using System;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel { 
	
      public MyICommand DeleteCommand { get; set;} 
		
      public StudentViewModel() { 
         LoadStudents(); 
         DeleteCommand = new MyICommand(OnDelete, CanDelete); 
      }
		
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
			
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      }
		
      private Student _selectedStudent; 
		
      public Student SelectedStudent { 
         get {
            return _selectedStudent; 
         } 
			
         set { 
            _selectedStudent = value;
            DeleteCommand.RaiseCanExecuteChanged(); 
         } 
      }
		
      private void OnDelete() { 
         Students.Remove(SelectedStudent); 
      }
		
      private bool CanDelete() { 
         return SelectedStudent != null; 
      }
   } 
}

ใน StudentView.xaml เราจำเป็นต้องเพิ่มคุณสมบัติ SelectedItem ในกล่องรายการซึ่งจะผูกกับคุณสมบัติ SelectStudent

<ListBox ItemsSource = "{Binding Students}" SelectedItem = "{Binding SelectedStudent}"/>

ต่อไปนี้เป็นไฟล์ xaml ที่สมบูรณ์

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   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:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:data = "clr-namespace:MVVMDemo.Model" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d"
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.Resources> 
      <DataTemplate DataType = "{x:Type data:Student}"> 
		
         <StackPanel Orientation = "Horizontal"> 
			
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
					
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>
	
   <Grid> 
      <StackPanel Orientation = "Horizontal"> 
         <ListBox ItemsSource = "{Binding Students}" 
            SelectedItem = "{Binding SelectedStudent}"/> 
				
         <Button Content = "Delete" 
            Command = "{Binding DeleteCommand}"
            HorizontalAlignment = "Left" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
      </StackPanel> 
   </Grid>
	
</UserControl>

เมื่อโค้ดด้านบนถูกคอมไพล์และรันคุณจะเห็นหน้าต่างต่อไปนี้

คุณจะเห็นว่าปุ่มลบถูกปิดใช้งาน จะเปิดใช้งานเมื่อคุณเลือกรายการใด ๆ

เมื่อคุณเลือกรายการใด ๆ และกดลบ คุณจะเห็นว่ารายการที่เลือกถูกลบและปุ่มลบอีกครั้งจะถูกปิดใช้งาน

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

เมื่อสร้างแอปพลิเคชัน 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 ปรากฏขึ้น

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

ในบทนี้เราจะเรียนรู้เกี่ยวกับการตรวจสอบความถูกต้อง นอกจากนี้เราจะดูวิธีที่สะอาดในการตรวจสอบความถูกต้องด้วยการผูก WPF ที่รองรับอยู่แล้ว แต่ผูกเข้ากับส่วนประกอบ MVVM

การตรวจสอบความถูกต้องใน MVVM

  • เมื่อแอปพลิเคชันของคุณเริ่มยอมรับการป้อนข้อมูลจากผู้ใช้ปลายทางคุณต้องพิจารณาตรวจสอบความถูกต้องของอินพุตนั้น

  • ตรวจสอบให้แน่ใจว่าสอดคล้องกับความต้องการโดยรวมของคุณ

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

  • โปรดทราบว่าตรรกะที่สนับสนุนการตรวจสอบความถูกต้องของคุณและกำหนดกฎที่มีอยู่สำหรับคุณสมบัติใดที่ควรเป็นส่วนหนึ่งของ Model หรือ ViewModel ไม่ใช่ View เอง

คุณยังคงสามารถใช้ทุกวิธีในการแสดงการตรวจสอบความถูกต้องที่รองรับโดยการผูกข้อมูล WPF ได้แก่ -

  • มีการตั้งค่าข้อยกเว้นในการโยนทรัพย์สิน
  • การติดตั้งอินเทอร์เฟซ IDataErrorInfo
  • การใช้ INotifyDataErrorInfo
  • ใช้กฎการตรวจสอบ WPF

โดยทั่วไปแนะนำให้ใช้ INotifyDataErrorInfo และได้รับการแนะนำให้รู้จักกับ WPF .net 4.5 และสนับสนุนการสืบค้นวัตถุสำหรับข้อผิดพลาดที่เกี่ยวข้องกับคุณสมบัติและยังแก้ไขข้อบกพร่องสองสามข้อด้วยตัวเลือกอื่น ๆ ทั้งหมด โดยเฉพาะจะช่วยให้สามารถตรวจสอบความถูกต้องแบบอะซิงโครนัสได้ อนุญาตให้คุณสมบัติมีข้อผิดพลาดมากกว่าหนึ่งข้อที่เกี่ยวข้อง

การเพิ่มการตรวจสอบความถูกต้อง

มาดูตัวอย่างที่เราจะเพิ่มการรองรับการตรวจสอบความถูกต้องให้กับมุมมองการป้อนข้อมูลของเราและในแอปพลิเคชันขนาดใหญ่คุณอาจต้องใช้สถานที่นี้ในแอปพลิเคชันของคุณ บางครั้งใน Views บางครั้งใน ViewModels และบางครั้งบนวัตถุตัวช่วยเหล่านี้จะมีการห่อหุ้มรอบวัตถุแบบจำลอง

เป็นแนวทางปฏิบัติที่ดีในการวางการสนับสนุนการตรวจสอบความถูกต้องไว้ในคลาสพื้นฐานทั่วไปซึ่งคุณสามารถรับช่วงจากสถานการณ์ต่างๆได้

คลาสฐานจะรองรับ INotifyDataErrorInfo เพื่อให้การตรวจสอบถูกทริกเกอร์เมื่อคุณสมบัติเปลี่ยนไป

สร้างเพิ่มคลาสใหม่ชื่อ ValidatableBindableBase เนื่องจากเรามีคลาสพื้นฐานสำหรับการจัดการการเปลี่ยนแปลงคุณสมบัติแล้วเรามารับคลาสฐานจากมันและใช้อินเทอร์เฟซ INotifyDataErrorInfo ด้วย

ต่อไปนี้คือการนำคลาส ValidatableBindableBase ไปใช้

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 

//using System.ComponentModel.DataAnnotations; 
using System.Linq; 
using System.Runtime.CompilerServices; 
using System.Text;
using System.Threading.Tasks; 
using System.Windows.Controls;

namespace MVVMHierarchiesDemo { 

   public class ValidatableBindableBase : BindableBase, INotifyDataErrorInfo { 
      private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

      public event EventHandler<DataErrorsChangedEventArgs> 
         ErrorsChanged = delegate { };

      public System.Collections.IEnumerable GetErrors(string propertyName) {
		
         if (_errors.ContainsKey(propertyName)) 
            return _errors[propertyName]; 
         else 
            return null; 
      }
      
      public bool HasErrors { 
         get { return _errors.Count > 0; } 
      }
		
      protected override void SetProperty<T>(ref T member, T val, 
         [CallerMemberName] string propertyName = null) {
		
         base.SetProperty<T>(ref member, val, propertyName);
         ValidateProperty(propertyName, val);
      }
		
      private void ValidateProperty<T>(string propertyName, T value) {
         var results = new List<ValidationResult>();
			
         //ValidationContext context = new ValidationContext(this); 
         //context.MemberName = propertyName;
         //Validator.TryValidateProperty(value, context, results);

         if (results.Any()) {
            //_errors[propertyName] = results.Select(c => c.ErrorMessage).ToList(); 
         } else { 
            _errors.Remove(propertyName); 
         }
			
         ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); 
      } 
   } 
}

ตอนนี้เพิ่ม AddEditCustomerView และ AddEditCustomerViewModel ในโฟลเดอร์ที่เกี่ยวข้อง ต่อไปนี้เป็นรหัสของ AddEditCustomerView.xaml

<UserControl x:Class = "MVVMHierarchiesDemo.Views.AddEditCustomerView"
   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> 
      <Grid.RowDefinitions> 
         <RowDefinition Height = "Auto" /> 
         <RowDefinition Height = "Auto" />
      </Grid.RowDefinitions>
		
      <Grid x:Name = "grid1" 
         HorizontalAlignment = "Left" 
         DataContext = "{Binding Customer}" 
         Margin = "10,10,0,0" 
         VerticalAlignment = "Top">
			
         <Grid.ColumnDefinitions> 
            <ColumnDefinition Width = "Auto" /> 
            <ColumnDefinition Width = "Auto" /> 
         </Grid.ColumnDefinitions>
		
         <Grid.RowDefinitions> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
         </Grid.RowDefinitions>
		
         <Label Content = "First Name:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "0" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "firstNameTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "0" 
            Text = "{Binding FirstName, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Last Name:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "1" 
            VerticalAlignment = "Center" /> 
			
         <TextBox x:Name = "lastNameTextBox"
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "1" 
            Text = "{Binding LastName, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Email:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "2" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "emailTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "2" 
            Text = "{Binding Email, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Phone:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "3" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "phoneTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "3" 
            Text = "{Binding Phone, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
      </Grid> 

      <Grid Grid.Row = "1"> 
         <Button Content = "Save" 
            Command = "{Binding SaveCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "25,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" />
		
         <Button Content = "Add" 
            Command = "{Binding SaveCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "25,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
		
         <Button Content = "Cancel" 
            Command = "{Binding CancelCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "150,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
      </Grid>
		
   </Grid> 
	
</UserControl>

ต่อไปนี้คือการใช้งาน AddEditCustomerViewModel

using MVVMHierarchiesDemo.Model;

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.ViewModel { 

   class AddEditCustomerViewModel : BindableBase { 
	
      public AddEditCustomerViewModel() {
         CancelCommand = new MyIcommand(OnCancel); 
         SaveCommand = new MyIcommand(OnSave, CanSave); 
      } 
		
      private bool _EditMode; 
		
      public bool EditMode { 
         get { return _EditMode; } 
         set { SetProperty(ref _EditMode, value);} 
      }
		
      private SimpleEditableCustomer _Customer;
		
      public SimpleEditableCustomer Customer { 
         get { return _Customer; } 
         set { SetProperty(ref _Customer, value);} 
      }

      private Customer _editingCustomer = null;
		
      public void SetCustomer(Customer cust) {
         _editingCustomer = cust; 
			
         if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged; 
         Customer = new SimpleEditableCustomer();
         Customer.ErrorsChanged += RaiseCanExecuteChanged;
         CopyCustomer(cust, Customer); 
      }
		
      private void RaiseCanExecuteChanged(object sender, EventArgs e) { 
         SaveCommand.RaiseCanExecuteChanged(); 
      }

      public MyIcommand CancelCommand { get; private set; }
      public MyIcommand SaveCommand { get; private set; }

      public event Action Done = delegate { };
		
      private void OnCancel() { 
         Done(); 
      }

      private async void OnSave() { 
         Done(); 
      }
		
      private bool CanSave() { 
         return !Customer.HasErrors; 
      }  
   } 
}

ต่อไปนี้คือการนำคลาส SimpleEditableCustomer ไปใช้

using System;
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.Model { 

   public class SimpleEditableCustomer : ValidatableBindableBase { 
      private Guid _id; 
		
      public Guid Id { 
         get { return _id; } 
         set { SetProperty(ref _id, value); } 
      }
		
      private string _firstName; 
      [Required]
		
      public string FirstName { 
         get { return _firstName; } 
         set { SetProperty(ref _firstName, value); } 
      }
		
      private string _lastName; 
      [Required] 
		
      public string LastName {  
         get { return _lastName; } 
         set { SetProperty(ref _lastName, value); } 
      }
		
      private string _email; 
      [EmailAddress] 
		
      public string Email {
         get { return _email; } 
         set { SetProperty(ref _email, value); } 
      }
		
      private string _phone; 
      [Phone] 
		
      public string Phone { 
         get { return _phone; } 
         set { SetProperty(ref _phone, value); } 
      } 
   } 
}

เมื่อโค้ดด้านบนถูกคอมไพล์และรันคุณจะเห็นหน้าต่างต่อไปนี้

เมื่อคุณกดปุ่มเพิ่มลูกค้าคุณจะเห็นมุมมองต่อไปนี้ เมื่อผู้ใช้เว้นช่องว่างไว้ช่องนั้นจะถูกไฮไลต์และปุ่มบันทึกจะปิดใช้งาน

ในบทนี้เราจะพูดคุยสั้น ๆ เกี่ยวกับการฉีดแบบพึ่งพา เราได้กล่าวถึงการผูกข้อมูลที่แยกมุมมองและ ViewModels ออกจากกันแล้วซึ่งช่วยให้พวกเขาสามารถสื่อสารได้โดยไม่ทราบแน่ชัดว่าเกิดอะไรขึ้นที่ส่วนอื่น ๆ ของการสื่อสาร

ตอนนี้เราต้องการสิ่งที่คล้ายกันเพื่อแยก ViewModel ของเราออกจากบริการไคลเอ็นต์

ในช่วงแรก ๆ ของการเขียนโปรแกรมเชิงวัตถุนักพัฒนาประสบปัญหาในการสร้างและเรียกใช้อินสแตนซ์ของคลาสในแอปพลิเคชัน มีการเสนอวิธีแก้ปัญหาต่างๆสำหรับปัญหานี้

ในช่วงไม่กี่ปีที่ผ่านมาการฉีดแบบพึ่งพาและการควบคุมแบบผกผัน (IoC) ได้รับความนิยมในหมู่นักพัฒนาและมีความสำคัญเหนือกว่าโซลูชันรุ่นเก่าบางอย่างเช่นรูปแบบ Singleton

การฉีดขึ้นอยู่กับภาชนะ / IoC

IoC และการฉีดพึ่งพาเป็นรูปแบบการออกแบบสองแบบที่มีความสัมพันธ์กันอย่างใกล้ชิดและโดยพื้นฐานแล้วคอนเทนเนอร์นั้นเป็นส่วนหนึ่งของรหัสโครงสร้างพื้นฐานที่ทำทั้งสองรูปแบบให้คุณ

  • รูปแบบ IoC เป็นเรื่องเกี่ยวกับการมอบหมายความรับผิดชอบในการก่อสร้างและรูปแบบการฉีดพึ่งพาเป็นเรื่องเกี่ยวกับการให้การอ้างอิงกับวัตถุที่สร้างขึ้นแล้ว

  • พวกเขาทั้งสองสามารถถือเป็นแนวทางสองเฟสในการสร้าง เมื่อคุณใช้คอนเทนเนอร์คอนเทนเนอร์จะมีความรับผิดชอบหลายประการดังนี้ -

    • สร้างวัตถุเมื่อถูกถาม
    • คอนเทนเนอร์จะกำหนดว่าวัตถุนั้นขึ้นอยู่กับอะไร
    • การสร้างการอ้างอิงเหล่านั้น
    • ฉีดเข้าไปในวัตถุที่กำลังสร้าง
    • ทำซ้ำกระบวนการ

ลองมาดูกันว่าเราจะใช้การฉีดพึ่งพาเพื่อแยกการแยกส่วนระหว่าง ViewModels และบริการไคลเอ็นต์ได้อย่างไร เราจะวางสายการจัดการการบันทึกแบบฟอร์ม AddEditCustomerViewModel โดยใช้การฉีดขึ้นต่อกันที่เกี่ยวข้องกับสิ่งนั้น

ก่อนอื่นเราต้องสร้างอินเทอร์เฟซใหม่ในโครงการของเราในโฟลเดอร์ Services หากคุณไม่มีโฟลเดอร์บริการในโครงการของคุณให้สร้างก่อนและเพิ่มอินเทอร์เฟซต่อไปนี้ในโฟลเดอร์ Services

using MVVMHierarchiesDemo.Model; 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.Services { 

   public interface ICustomersRepository { 
      Task<List<Customer>> GetCustomersAsync(); 
      Task<Customer> GetCustomerAsync(Guid id); 
      Task<Customer> AddCustomerAsync(Customer customer); 
      Task<Customer> UpdateCustomerAsync(Customer customer); 
      Task DeleteCustomerAsync(Guid customerId); 
   } 
}

ต่อไปนี้คือการนำ ICustomersRepository ไปใช้

using MVVMHierarchiesDemo.Model; 

using System; 
using System.Collections.Generic; 
using System.Linq; using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.Services { 

   public class CustomersRepository : ICustomersRepository {
      ZzaDbContext _context = new ZzaDbContext();

      public Task<List<Customer>> GetCustomersAsync() { 
         return _context.Customers.ToListAsync(); 
      }

      public Task<Customer> GetCustomerAsync(Guid id) { 
         return _context.Customers.FirstOrDefaultAsync(c => c.Id == id); 
      }
		
      public async Task<Customer> AddCustomerAsync(Customer customer){ 
         _context.Customers.Add(customer); 
         await _context.SaveChangesAsync(); 
         return customer;
      }

      public async Task<Customer> UpdateCustomerAsync(Customer customer) {
		
         if (!_context.Customers.Local.Any(c => c.Id == customer.Id)) { 
            _context.Customers.Attach(customer); 
         } 
			
         _context.Entry(customer).State = EntityState.Modified;
         await _context.SaveChangesAsync(); 
         return customer;
			
      }

      public async Task DeleteCustomerAsync(Guid customerId) {
         var customer = _context.Customers.FirstOrDefault(c => c.Id == customerId); 
			
         if (customer != null) {
            _context.Customers.Remove(customer); 
         }
			
         await _context.SaveChangesAsync(); 
      } 
   } 
}

วิธีง่ายๆในการจัดการบันทึกคือการเพิ่มอินสแตนซ์ใหม่ของ ICustomersRepository ใน AddEditCustomerViewModel และโอเวอร์โหลดตัวสร้าง AddEditCustomerViewModel และ CustomerListViewModel

private ICustomersRepository _repo; 

public AddEditCustomerViewModel(ICustomersRepository repo) { 
   _repo = repo; 
   CancelCommand = new MyIcommand(OnCancel);
   SaveCommand = new MyIcommand(OnSave, CanSave); 
}

อัพเดตเมธอด OnSave ตามที่แสดงในโค้ดต่อไปนี้

private async void OnSave() { 
   UpdateCustomer(Customer, _editingCustomer); 
	
   if (EditMode) 
      await _repo.UpdateCustomerAsync(_editingCustomer); 
   else 
      await _repo.AddCustomerAsync(_editingCustomer); 
   Done(); 
} 

private void UpdateCustomer(SimpleEditableCustomer source, Customer target) { 
   target.FirstName = source.FirstName; 
   target.LastName = source.LastName; 
   target.Phone = source.Phone; 
   target.Email = source.Email; 
}

ต่อไปนี้คือ AddEditCustomerViewModel ที่สมบูรณ์

using MVVMHierarchiesDemo.Model; 
using MVVMHierarchiesDemo.Services; 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text;
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.ViewModel { 

   class AddEditCustomerViewModel : BindableBase { 
      private ICustomersRepository _repo; 
		
      public AddEditCustomerViewModel(ICustomersRepository repo) { 
         _repo = repo;
         CancelCommand = new MyIcommand(OnCancel); 
         SaveCommand = new MyIcommand(OnSave, CanSave); 
      } 
		
      private bool _EditMode; 
		
      public bool EditMode { 
         get { return _EditMode; } 
         set { SetProperty(ref _EditMode, value); } 
      }

      private SimpleEditableCustomer _Customer; 
		
      public SimpleEditableCustomer Customer { 
         get { return _Customer; } 
         set { SetProperty(ref _Customer, value); } 
      }
		
      private Customer _editingCustomer = null;

      public void SetCustomer(Customer cust) { 
         _editingCustomer = cust; 
			
         if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged; 
         Customer = new SimpleEditableCustomer();
         Customer.ErrorsChanged += RaiseCanExecuteChanged;
         CopyCustomer(cust, Customer); 
      }

      private void RaiseCanExecuteChanged(object sender, EventArgs e) { 
         SaveCommand.RaiseCanExecuteChanged(); 
      }

      public MyIcommand CancelCommand { get; private set; } 
      public MyIcommand SaveCommand { get; private set; }

      public event Action Done = delegate { };
		
      private void OnCancel() { 
         Done(); 
      }

      private async void OnSave() { 
         UpdateCustomer(Customer, _editingCustomer); 
			
         if (EditMode) 
            await _repo.UpdateCustomerAsync(_editingCustomer); 
         else 
            await _repo.AddCustomerAsync(_editingCustomer); 
         Done(); 
      }

      private void UpdateCustomer(SimpleEditableCustomer source, Customer target) { 
         target.FirstName = source.FirstName; 
         target.LastName = source.LastName; 
         target.Phone = source.Phone; 
         target.Email = source.Email; 
      }

      private bool CanSave() { 
         return !Customer.HasErrors; 
      }
		
      private void CopyCustomer(Customer source, SimpleEditableCustomer target) { 
         target.Id = source.Id; 
			
         if (EditMode) { 
            target.FirstName = source.FirstName; 
            target.LastName = source.LastName; 
            target.Phone = source.Phone; 
            target.Email = source.Email; 
         }
      } 
   } 
}

เมื่อรวบรวมและดำเนินการโค้ดด้านบนคุณจะเห็นผลลัพธ์เดียวกัน แต่ตอนนี้ ViewModels แยกออกจากกันอย่างหลวม ๆ

เมื่อคุณกดปุ่มเพิ่มลูกค้าคุณจะเห็นมุมมองต่อไปนี้ เมื่อผู้ใช้เว้นช่องว่างไว้ช่องนั้นจะถูกไฮไลต์และปุ่มบันทึกจะปิดใช้งาน

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

  • ในแอ็พพลิเคชัน WPF ที่ใช้รูปแบบการออกแบบ MVVM (Model-View-ViewModel) โมเดลมุมมองเป็นส่วนประกอบที่รับผิดชอบในการจัดการตรรกะและสถานะการนำเสนอของแอปพลิเคชัน

  • ไฟล์ code-behind ของมุมมองไม่ควรมีโค้ดสำหรับจัดการเหตุการณ์ที่ยกขึ้นจากองค์ประกอบส่วนติดต่อผู้ใช้ (UI) เช่นปุ่มหรือ ComboBox และไม่ควรมีตรรกะเฉพาะโดเมนใด ๆ

  • ตามหลักการแล้วโค้ดด้านหลังของ View มีเพียงตัวสร้างที่เรียกใช้เมธอด InitializeComponent และอาจมีโค้ดเพิ่มเติมเพื่อควบคุมหรือโต้ตอบกับเลเยอร์มุมมองที่ยากหรือไม่มีประสิทธิภาพในการแสดงใน XAML เช่นภาพเคลื่อนไหวที่ซับซ้อน

มาดูตัวอย่างเหตุการณ์การคลิกปุ่มง่ายๆในแอปพลิเคชันของเรา ต่อไปนี้เป็นรหัส XAML ของไฟล์ MainWindow.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>

คุณจะเห็นว่าคุณสมบัติของปุ่ม Click ไม่ได้ใช้ในไฟล์ XAML ด้านบน แต่ใช้คุณสมบัติ Command และ CommandParameter เพื่อโหลดมุมมองที่แตกต่างกันเมื่อกดปุ่ม ตอนนี้คุณต้องกำหนดการใช้งานคำสั่งในไฟล์ MainWindowViewModel.cs แต่ไม่ใช่ในไฟล์ View ต่อไปนี้คือการใช้งาน 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 ปรากฏขึ้น

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

แนวคิดเบื้องหลังการทดสอบหน่วยคือการใช้โค้ด (หน่วย) แบบแยกส่วนและเขียนวิธีการทดสอบที่ใช้โค้ดในลักษณะที่คาดหวังจากนั้นทดสอบเพื่อดูว่าได้ผลลัพธ์ตามที่คาดหวังหรือไม่

  • การทดสอบหน่วยจะถูกรวบรวมเช่นเดียวกับส่วนอื่น ๆ ของโครงการ

  • นอกจากนี้ยังดำเนินการโดยซอฟต์แวร์ทดสอบซึ่งสามารถเร่งความเร็วในการทดสอบแต่ละครั้งได้อย่างมีประสิทธิภาพยกนิ้วโป้งขึ้นหรือยกนิ้วลงเพื่อระบุว่าการทดสอบผ่านหรือล้มเหลวตามลำดับ

ลองมาดูตัวอย่างที่สร้างขึ้นก่อนหน้านี้ ต่อไปนี้คือการนำ Student Model ไปใช้

using System.ComponentModel;

namespace MVVMDemo.Model {
 
   public class StudentModel {}
	
   public class Student : INotifyPropertyChanged { 
      private string firstName; 
      private string lastName;

      public string FirstName { 
         get { return firstName; }
			
         set { 
            if (firstName != value) { 
               firstName = value; 
               RaisePropertyChanged("FirstName");
               RaisePropertyChanged("FullName"); 
            } 
         }
      }

      public string LastName { 
         get { return lastName; } 
			
         set { 
            if (lastName != value) { 
               lastName = value; 
               RaisePropertyChanged("LastName");
               RaisePropertyChanged("FullName");
            } 
         } 
      }

      public string FullName { 
         get { 
            return firstName + " " + lastName; 
         } 
      }

      public event PropertyChangedEventHandler PropertyChanged;

      private void RaisePropertyChanged(string property) { 
         if (PropertyChanged != null) { 
            PropertyChanged(this, new PropertyChangedEventArgs(property)); 
         } 
      } 
   } 
}

ต่อไปนี้คือการใช้งาน StudentView

<UserControl x:Class="MVVMDemo.Views.StudentView" 
   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:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:data = "clr-namespace:MVVMDemo.Model" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">

   <UserControl.Resources> 
      <DataTemplate DataType = "{x:Type data:Student}"> 
		
         <StackPanel Orientation = "Horizontal"> 
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>

   <Grid> 
      <StackPanel Orientation = "Horizontal"> 
         <ListBox ItemsSource = "{Binding Students}"
            SelectedItem = "{Binding SelectedStudent}"/> 
				
         <Button Content = "Delete" 
            Command = "{Binding DeleteCommand}" 
            HorizontalAlignment = "Left" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
      </StackPanel> 
   </Grid>

</UserControl>

ต่อไปนี้คือการใช้งาน StudentViewModel

using MVVMDemo.Model;
 
using System.Collections.ObjectModel; 
using System.Windows.Input; 
using System;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel { 
	
      public MyICommand DeleteCommand { get; set;}
		
      public StudentViewModel() { 
         LoadStudents(); 
         DeleteCommand = new MyICommand(OnDelete, CanDelete); 
      }

      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }

      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();

         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
			
         Students = students; 
      } 
		
      private Student _selectedStudent; 
		
      public Student SelectedStudent { 
         get { 
            return _selectedStudent; 
         } 
			
         set { 
            _selectedStudent = value;
            DeleteCommand.RaiseCanExecuteChanged(); 
         } 
      } 
		
      private void OnDelete() { 
         Students.Remove(SelectedStudent); 
      }

      private bool CanDelete() { 
         return SelectedStudent != null; 
      }
		
      public int GetStudentCount() { 
         return Students.Count; 
      } 
   } 
}

ต่อไปนี้คือไฟล์ MainWindow.xaml

<Window x:Class = "MVVMDemo.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:MVVMDemo" 
   xmlns:views = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525">

   <Grid> 
      <views:StudentView x:Name = "StudentViewControl"/> 
   </Grid>
 
</Window>

ต่อไปนี้คือการใช้งาน MyICommand ซึ่งใช้อินเทอร์เฟซ ICommand

using System; 
using System.Windows.Input;

namespace MVVMDemo { 

   public class MyICommand : ICommand { 
      Action _TargetExecuteMethod; 
      Func<bool> _TargetCanExecuteMethod;

      public MyICommand(Action executeMethod) { 
         _TargetExecuteMethod = executeMethod; 
      }

      public MyICommand(Action executeMethod, Func<bool> canExecuteMethod) { 
         _TargetExecuteMethod = executeMethod;
         _TargetCanExecuteMethod = canExecuteMethod; 
      }

      public void RaiseCanExecuteChanged() {
         CanExecuteChanged(this, EventArgs.Empty); 
      }
		
      bool ICommand.CanExecute(object parameter) { 
		
         if (_TargetCanExecuteMethod != null) { 
            return _TargetCanExecuteMethod();
         } 
			
         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(); 
         } 
      } 
   }
}

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

ในการเขียนการทดสอบหน่วยสำหรับตัวอย่างข้างต้นให้เพิ่มโครงการทดสอบใหม่ลงในโซลูชัน

เพิ่มการอ้างอิงไปยังโครงการโดยคลิกขวาที่การอ้างอิง

เลือกโครงการที่มีอยู่แล้วคลิกตกลง

ตอนนี้ให้เราเพิ่มแบบทดสอบง่ายๆซึ่งจะตรวจสอบจำนวนนักเรียนตามที่แสดงในรหัสต่อไปนี้

using System; 

using Microsoft.VisualStudio.TestTools.UnitTesting; 
using MVVMDemo.ViewModel;

namespace MVVMTest { 
   [TestClass] 
	
   public class UnitTest1 { 
      [TestMethod] 
		
      public void TestMethod1() { 
         StudentViewModel sViewModel = new StudentViewModel(); 
         int count = sViewModel.GetStudentCount();
         Assert.IsTrue(count == 3); 
      } 
   } 
}

ในการดำเนินการทดสอบนี้ให้เลือกทดสอบ→เรียกใช้→ตัวเลือกเมนูการทดสอบทั้งหมด

คุณสามารถเห็นใน Test Explorer ว่าการทดสอบผ่านแล้วเนื่องจากใน StudentViewModel มีการเพิ่มนักเรียนสามคน เปลี่ยนเงื่อนไขการนับจาก 3 เป็น 4 ดังแสดงในรหัสต่อไปนี้

using System; 

using Microsoft.VisualStudio.TestTools.UnitTesting; 
using MVVMDemo.ViewModel;

namespace MVVMTest { 
   [TestClass] 
	
   public class UnitTest1 { 
      [TestMethod] public void TestMethod1() {
         StudentViewModel sViewModel = new StudentViewModel(); 
         int count = sViewModel.GetStudentCount();
         Assert.IsTrue(count == 4);
      } 
   } 
}

เมื่อแผนการทดสอบถูกดำเนินการอีกครั้งคุณจะเห็นว่าการทดสอบล้มเหลวเนื่องจากจำนวนนักเรียนไม่เท่ากับ 4

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

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

  • Prism
  • ไฟ MVVM
  • คาลิเบิร์นไมโคร

ปริซึม

Prism ให้คำแนะนำในรูปแบบของตัวอย่างและเอกสารประกอบที่ช่วยให้คุณออกแบบและสร้างแอปพลิเคชันเดสก์ท็อป Windows Presentation Foundation (WPF) ที่หลากหลายยืดหยุ่นและดูแลรักษาได้ง่าย Rich Internet Applications (RIA) ที่สร้างขึ้นด้วยปลั๊กอินเบราว์เซอร์ Microsoft Silverlight และแอปพลิเคชัน Windows

  • ปริซึมใช้รูปแบบการออกแบบที่รวบรวมหลักการออกแบบสถาปัตยกรรมที่สำคัญเช่นการแยกข้อกังวลและการเชื่อมต่อแบบหลวม ๆ

  • Prism ช่วยให้คุณออกแบบและสร้างแอปพลิเคชันโดยใช้ส่วนประกอบแบบหลวม ๆ ที่สามารถพัฒนาได้อย่างอิสระ แต่สามารถรวมเข้ากับแอปพลิเคชันโดยรวมได้อย่างง่ายดายและราบรื่น

  • แอปพลิเคชันประเภทนี้เรียกว่าแอปพลิเคชันคอมโพสิต

ปริซึมมีคุณสมบัติหลายอย่างที่พร้อมใช้งาน ต่อไปนี้เป็นคุณสมบัติที่สำคัญบางประการของปริซึม

รูปแบบ MVVM

ปริซึมรองรับรูปแบบ MVVM มีคลาส Bindablebase คล้ายกับคลาสที่ใช้งานในบทก่อนหน้านี้

มันมี ViewModelLocator ที่ยืดหยุ่นซึ่งมีข้อตกลง แต่ช่วยให้คุณสามารถลบล้างข้อตกลงเหล่านั้นและเชื่อมต่อ Views และ ViewModels ของคุณอย่างเปิดเผยควบคู่กันไปอย่างหลวม ๆ

ความเป็นโมดูลาร์

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

องค์ประกอบ UI / ภูมิภาค

เป็นความสามารถในการเชื่อมต่อมุมมองเข้ากับคอนเทนเนอร์โดยไม่ต้องดูที่กำลังทำการเสียบจำเป็นต้องมีการอ้างอิงอย่างชัดเจนไปยังคอนเทนเนอร์ UI เอง

การนำทาง

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

คำสั่ง

ปริซึมมีคำสั่งดังนั้นจึงมีคำสั่งมอบหมายที่คล้ายกับ MyICommand ที่เราเคยใช้ในบทก่อนหน้านี้มากยกเว้นว่ามันมีความทนทานเป็นพิเศษเพื่อปกป้องคุณจากการรั่วไหลของหน่วยความจำ

เหตุการณ์ Pub / Sub

Prism ยังรองรับเหตุการณ์ Pub / Sub เหตุการณ์เหล่านี้เป็นเหตุการณ์ที่เกิดขึ้นร่วมกันอย่างหลวม ๆ ซึ่งผู้เผยแพร่และผู้ติดตามอาจมีช่วงชีวิตที่แตกต่างกันและไม่จำเป็นต้องมีการอ้างอิงถึงกันอย่างชัดเจนเพื่อสื่อสารผ่านเหตุการณ์ต่างๆ

ไฟ MVVM

MVVM Light ผลิตโดย Laurent Bugnion และช่วยให้คุณแยกมุมมองของคุณออกจากโมเดลของคุณซึ่งจะสร้างแอพพลิเคชั่นที่สะอาดและง่ายต่อการบำรุงรักษาและขยาย

  • นอกจากนี้ยังสร้างแอปพลิเคชันที่สามารถทดสอบได้และช่วยให้คุณมีเลเยอร์อินเทอร์เฟซผู้ใช้ที่บางกว่ามาก (ซึ่งยากต่อการทดสอบโดยอัตโนมัติ)

  • ชุดเครื่องมือนี้ให้ความสำคัญเป็นพิเศษในการเปิดและแก้ไขอินเทอร์เฟซผู้ใช้ใน Blend รวมถึงการสร้างข้อมูลเวลาออกแบบเพื่อให้ผู้ใช้ Blend "เห็นบางสิ่ง" เมื่อทำงานกับการควบคุมข้อมูล

คาลิเบิร์นไมโคร

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

  • Caliburn Micro เป็นเฟรมเวิร์กขนาดเล็ก แต่ทรงพลังซึ่งออกแบบมาสำหรับการสร้างแอปพลิเคชันบนแพลตฟอร์ม XAML ทั้งหมด

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