MVVM - Hướng dẫn nhanh

Cách sắp xếp hợp lý và có lẽ là cách tái sử dụng nhiều nhất để tổ chức mã của bạn là sử dụng mẫu 'MVVM'. CácModel, View, ViewModel (MVVM pattern) là tất cả về việc hướng dẫn bạn cách tổ chức và cấu trúc mã của bạn để viết các ứng dụng có thể bảo trì, có thể kiểm tra và có thể mở rộng.

Model - Nó chỉ đơn giản là giữ dữ liệu và không liên quan đến bất kỳ logic kinh doanh nào.

ViewModel - Nó hoạt động như một liên kết / kết nối giữa Mô hình và Chế độ xem và làm cho mọi thứ trông đẹp mắt.

View - Nó chỉ đơn giản là giữ dữ liệu được định dạng và về cơ bản ủy thác mọi thứ cho Model.

Bản trình bày riêng biệt

Để tránh các sự cố do đặt logic ứng dụng trong mã phía sau hoặc XAML, tốt nhất bạn nên sử dụng một kỹ thuật được gọi là bản trình bày tách biệt. Chúng tôi đang cố gắng tránh điều này, trong đó chúng tôi sẽ có XAML và mã phía sau với mức tối thiểu cần thiết để làm việc trực tiếp với các đối tượng giao diện người dùng. Các lớp giao diện người dùng cũng chứa mã cho các hành vi tương tác phức tạp, logic ứng dụng và mọi thứ khác như thể hiện trong hình sau ở bên trái.

  • Với cách trình bày riêng biệt, lớp giao diện người dùng đơn giản hơn nhiều. Tất nhiên, nó có XAML, nhưng mã đằng sau ít thực tế.

  • Logic ứng dụng thuộc một lớp riêng biệt, thường được gọi là mô hình.

  • Tuy nhiên, đây không phải là toàn bộ câu chuyện. Nếu bạn dừng lại ở đây, bạn có khả năng sẽ lặp lại một sai lầm rất phổ biến dẫn bạn đến con đường điên rồ ràng buộc dữ liệu.

  • Rất nhiều nhà phát triển cố gắng sử dụng liên kết dữ liệu để kết nối trực tiếp các phần tử trong XAML với các thuộc tính trong mô hình.

  • Đôi khi điều này có thể ổn, nhưng thường thì không. Vấn đề là mô hình hoàn toàn quan tâm đến các vấn đề về những gì ứng dụng làm, chứ không phải về cách người dùng tương tác với ứng dụng.

  • Cách bạn trình bày dữ liệu thường hơi khác với cách nó được cấu trúc bên trong.

  • Hơn nữa, hầu hết các giao diện người dùng có một số trạng thái không thuộc mô hình ứng dụng.

  • Ví dụ: nếu giao diện người dùng của bạn sử dụng tính năng kéo và thả, điều gì đó cần theo dõi những thứ như vị trí mục đang được kéo ngay bây giờ, hình thức của nó sẽ thay đổi như thế nào khi nó di chuyển qua các mục tiêu thả có thể và cách các mục tiêu thả đó cũng có thể thay đổi khi mục được kéo qua chúng.

  • Loại trạng thái này có thể trở nên phức tạp một cách đáng ngạc nhiên và cần được kiểm tra kỹ lưỡng.

  • Trong thực tế, bạn thường muốn một số lớp khác nằm giữa giao diện người dùng và mô hình. Điều này có hai vai trò quan trọng.

    • Đầu tiên, nó điều chỉnh mô hình ứng dụng của bạn cho một giao diện người dùng cụ thể.

    • Thứ hai, đó là nơi mà bất kỳ logic tương tác tầm thường nào tồn tại và do đó, ý tôi là cần có mã để giao diện người dùng của bạn hoạt động theo cách bạn muốn.

Mẫu MVVM cuối cùng là cấu trúc hiện đại của mẫu MVC, vì vậy mục tiêu chính vẫn giống nhau để cung cấp sự tách biệt rõ ràng giữa logic miền và lớp trình bày. Dưới đây là một số ưu điểm và nhược điểm của mẫu MVVM.

Lợi ích chính là cho phép tách biệt thực sự giữa Chế độ xem và Mô hình ngoài việc đạt được sự tách biệt và hiệu quả mà bạn đạt được khi có điều đó. Điều đó có nghĩa là trong điều kiện thực tế là khi mô hình của bạn cần thay đổi, nó có thể được thay đổi dễ dàng mà không cần chế độ xem và ngược lại.

Có ba điều chính quan trọng phát sinh khi áp dụng MVVM như sau.

Khả năng bảo trì

  • Việc tách biệt rõ ràng các loại mã khác nhau sẽ giúp bạn dễ dàng đi vào một hoặc một số phần chi tiết và tập trung hơn và thực hiện các thay đổi mà không cần lo lắng.

  • Điều đó có nghĩa là bạn có thể vẫn nhanh nhẹn và tiếp tục chuyển sang các bản phát hành mới một cách nhanh chóng.

Khả năng kiểm tra

  • Với MVVM, mỗi đoạn mã chi tiết hơn và nếu nó được triển khai đúng cách thì các phần phụ thuộc bên ngoài và bên trong của bạn sẽ nằm trong các đoạn mã riêng biệt từ các phần có logic cốt lõi mà bạn muốn kiểm tra.

  • Điều đó làm cho việc viết các bài kiểm tra đơn vị dựa trên logic cốt lõi dễ dàng hơn rất nhiều.

  • Đảm bảo rằng nó hoạt động đúng khi được viết và tiếp tục hoạt động ngay cả khi mọi thứ thay đổi trong quá trình bảo trì.

Khả năng mở rộng

  • Đôi khi nó trùng lặp với khả năng bảo trì, vì ranh giới phân tách rõ ràng và các đoạn mã chi tiết hơn.

  • Bạn có cơ hội tốt hơn để làm cho bất kỳ bộ phận nào trong số đó có thể tái sử dụng nhiều hơn.

  • Nó cũng có khả năng thay thế hoặc thêm các đoạn mã mới làm những việc tương tự vào đúng vị trí trong kiến ​​trúc.

Mục đích rõ ràng của mẫu MVVM là trừu tượng hóa Chế độ xem, làm giảm số lượng logic nghiệp vụ trong mã phía sau. Tuy nhiên, sau đây là một số lợi thế vững chắc khác -

  • ViewModel dễ kiểm tra đơn vị hơn so với mã phía sau hoặc mã hướng sự kiện.
  • Bạn có thể kiểm tra nó mà không cần tự động hóa và tương tác giao diện người dùng khó xử.
  • Lớp trình bày và lớp logic được kết hợp lỏng lẻo.

Nhược điểm

  • Một số người nghĩ rằng đối với giao diện người dùng đơn giản, MVVM có thể quá mức cần thiết.
  • Tương tự như vậy trong các trường hợp lớn hơn, có thể khó thiết kế ViewModel.
  • Gỡ lỗi sẽ hơi khó khăn khi chúng ta có các ràng buộc dữ liệu phức tạp.

Mẫu MVVM bao gồm ba phần - Model, View và ViewModel. Hầu hết các nhà phát triển khi bắt đầu đều hơi bối rối về những gì Mô hình, Chế độ xem và Mô hình xem nên hoặc không nên chứa và trách nhiệm của từng phần là gì.

Trong chương này, chúng ta sẽ tìm hiểu trách nhiệm của từng phần của mẫu MVVM để bạn có thể hiểu rõ ràng loại mã nào sẽ đi đến đâu. MVVM thực sự là một kiến ​​trúc phân lớp cho phía máy khách như thể hiện trong hình sau.

  • Lớp trình bày bao gồm các khung nhìn.

  • Lớp logic là các mô hình khung nhìn.

  • Lớp trình bày là sự kết hợp của các đối tượng mô hình.

  • Các dịch vụ khách sản xuất và duy trì chúng hoặc truy cập trực tiếp trong một ứng dụng hai tầng hoặc thông qua các cuộc gọi dịch vụ đến và sau đó đến ứng dụng của bạn.

  • Các dịch vụ khách hàng không chính thức là một phần của mẫu MVVM nhưng nó thường được sử dụng với MVVM để đạt được sự phân tách sâu hơn và tránh trùng lặp mã.

Trách nhiệm của người mẫu

Nói chung, mô hình là mô hình đơn giản nhất để hiểu. Đây là mô hình dữ liệu phía máy khách hỗ trợ các khung nhìn trong ứng dụng.

  • Nó bao gồm các đối tượng có thuộc tính và một số biến để chứa dữ liệu trong bộ nhớ.

  • Một số thuộc tính đó có thể tham chiếu đến các đối tượng mô hình khác và tạo ra biểu đồ đối tượng mà tổng thể là các đối tượng mô hình.

  • Đối tượng mô hình nên nâng cao thông báo thay đổi thuộc tính mà trong WPF có nghĩa là liên kết dữ liệu.

  • Trách nhiệm cuối cùng là xác thực là tùy chọn, nhưng bạn có thể nhúng thông tin xác thực vào các đối tượng mô hình bằng cách sử dụng các tính năng xác thực liên kết dữ liệu WPF thông qua các giao diện như INotifyDataErrorInfo / IDataErrorInfo

Xem trách nhiệm

Mục đích và trách nhiệm chính của khung nhìn là xác định cấu trúc của những gì người dùng nhìn thấy trên màn hình. Cấu trúc có thể chứa phần tĩnh và phần động.

  • Phần tĩnh là cấu trúc phân cấp XAML xác định các điều khiển và cách bố trí các điều khiển mà một dạng xem bao gồm.

  • Phần động giống như hoạt ảnh hoặc thay đổi trạng thái được xác định là một phần của Chế độ xem.

  • Mục tiêu chính của MVVM là không có mã phía sau trong chế độ xem.

  • Không thể có mã phía sau trong chế độ xem. Theo quan điểm này, bạn cần ít nhất hàm tạo và một lệnh gọi để khởi tạo thành phần.

  • Ý tưởng là mã logic xử lý sự kiện, hành động và thao tác dữ liệu không nên nằm trong mã phía sau trong View.

  • Ngoài ra còn có các loại mã khác phải đi trong mã đằng sau bất kỳ mã nào bắt buộc phải có tham chiếu đến phần tử giao diện người dùng vốn là mã chế độ xem.

Trách nhiệm của ViewModel

  • ViewModel là điểm chính của ứng dụng MVVM. Trách nhiệm chính của ViewModel là cung cấp dữ liệu cho chế độ xem, để chế độ xem đó có thể đưa dữ liệu đó lên màn hình.

  • Nó cũng cho phép người dùng tương tác với dữ liệu và thay đổi dữ liệu.

  • Trách nhiệm chính khác của ViewModel là đóng gói logic tương tác cho một khung nhìn, nhưng điều đó không có nghĩa là tất cả logic của ứng dụng phải đi vào ViewModel.

  • Nó sẽ có thể xử lý trình tự các cuộc gọi thích hợp để thực hiện điều đúng đắn xảy ra dựa trên người dùng hoặc bất kỳ thay đổi nào trên chế độ xem.

  • ViewModel cũng nên quản lý bất kỳ logic điều hướng nào như quyết định thời điểm điều hướng đến một chế độ xem khác.

Trong chương này, chúng ta sẽ học cách sử dụng các mẫu MVVM cho màn hình nhập liệu đơn giản và ứng dụng WPF mà bạn có thể đã sử dụng.

Hãy xem một ví dụ đơn giản mà chúng ta sẽ sử dụng cách tiếp cận MVVM.

Step 1 - Tạo một dự án Ứng dụng WPF mới MVVMDemo.

Step 2 - Thêm ba thư mục (Model, ViewModel và Views) vào dự án của bạn.

Step 3 - Thêm lớp StudentModel trong thư mục Model và dán mã bên dưới vào lớp đó

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 - Thêm một lớp StudentViewModel khác vào thư mục ViewModel và dán đoạn mã sau.

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 - Thêm Kiểm soát người dùng mới (WPF) bằng cách nhấp chuột phải vào thư mục Chế độ xem và chọn Thêm> Mục mới…

Step 6- Nhấn nút Thêm. Bây giờ bạn sẽ thấy tệp XAML. Thêm mã sau vào tệp StudentView.xaml chứa các phần tử giao diện người dùng khác nhau.

<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 - Bây giờ hãy thêm StudentView vào tệp MainPage.xaml của bạn bằng cách sử dụng mã sau.

<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 - Đây là cách triển khai cho sự kiện Đã tải trong tệp MainPage.xaml.cs, tệp này sẽ cập nhật Chế độ xem từ 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 - Khi đoạn mã trên được biên dịch và thực thi, bạn sẽ nhận được kết quả sau trên cửa sổ chính của mình.

Chúng tôi khuyên bạn nên thực hiện ví dụ trên theo cách từng bước để hiểu rõ hơn.

Trong chương này, chúng tôi sẽ đề cập đến các cách khác nhau mà bạn có thể đưa quan điểm của mình kết nối với ViewModel. Đầu tiên, chúng ta hãy xem bản dựng đầu tiên của View, nơi chúng ta có thể khai báo nó trong XAML. Như chúng ta đã thấy ví dụ trong chương trước, nơi chúng ta đã nối một khung nhìn từ cửa sổ chính. Bây giờ chúng ta sẽ xem những cách khác để nối các lượt xem.

Chúng tôi cũng sẽ sử dụng cùng một ví dụ trong chương này. Sau đây là thực hiện cùng một lớp 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)); 
         } 
      } 
   }  
}

Đây là triển khai lớp ViewModel. Lúc này phương thức LoadStudents được gọi trong hàm tạo mặc định.

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; 
      } 
   } 
}

Cho dù dạng xem là Cửa sổ, Người dùng kiểm soát hay Trang, trình phân tích cú pháp thường hoạt động từ trên xuống dưới và từ trái sang phải. Nó gọi hàm tạo mặc định cho mỗi phần tử khi nó gặp nó. Có hai cách để xây dựng một khung nhìn. Bạn có thể sử dụng bất kỳ trên chúng.

  • Xem công trình đầu tiên trong XAML
  • Xem công trình đầu tiên ở phần sau mã

Xem công trình đầu tiên trong XAML

Một cách là chỉ cần thêm ViewModel của bạn dưới dạng một phần tử lồng nhau trong bộ thiết lập cho thuộc tính DataContext như được hiển thị trong đoạn mã sau.

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

Đây là tệp View XAML hoàn chỉnh.

<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>

Xem công trình đầu tiên ở phần sau mã

Một cách khác là bạn có thể xây dựng View đầu tiên bằng cách chỉ cần tự xây dựng mô hình view trong đoạn mã đằng sau View của bạn bằng cách đặt thuộc tính DataContext ở đó với instance.

Thông thường, thuộc tính DataContext được đặt trong phương thức xây dựng của chế độ xem, nhưng bạn cũng có thể trì hoãn việc xây dựng cho đến khi sự kiện Tải của chế độ xem kích hoạt.

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(); 
      } 
   } 
}

Một lý do để xây dựng mô hình khung nhìn trong Code-Behind thay vì XAML là phương thức xây dựng mô hình View nhận các tham số, nhưng phân tích cú pháp XAML chỉ có thể xây dựng các phần tử nếu được định nghĩa trong phương thức khởi tạo mặc định.

Bây giờ trong trường hợp này, tệp XAML của Chế độ xem sẽ giống như được hiển thị trong đoạn mã sau.

<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>

Bạn có thể khai báo Dạng xem này trong MainWindow như được hiển thị trong tệp 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>

Khi đoạn mã trên được biên dịch và thực thi, bạn sẽ thấy kết quả sau trên cửa sổ chính của mình.

Chúng tôi khuyên bạn nên thực hiện ví dụ trên theo cách từng bước để hiểu rõ hơn.

Trong chương này, chúng tôi sẽ trình bày cách kết nối ViewModel. Đây là phần tiếp theo của chương trước, trong đó chúng ta đã thảo luận về việc xây dựng View đầu tiên. Bây giờ, hình thức tiếp theo của công trình đầu tiên làmeta-pattern được gọi là ViewModelLocator. Nó là một mô hình giả và được xếp lớp trên mô hình MVVM.

  • Trong MVVM, mỗi View cần được nối với ViewModel của nó.

  • ViewModelLocator là một cách tiếp cận đơn giản để tập trung mã và phân tách chế độ xem nhiều hơn.

  • Nó có nghĩa là nó không cần phải biết rõ ràng về kiểu ViewModel và cách xây dựng nó.

  • Có một số cách tiếp cận khác nhau để sử dụng ViewModelLocator, nhưng ở đây chúng tôi sử dụng cách tương tự nhất với cách tiếp cận là một phần của khuôn khổ PRISM.

ViewModelLocator cung cấp một cách chuẩn, nhất quán, có tính khai báo và được kết hợp lỏng lẻo để thực hiện việc xây dựng chế độ xem đầu tiên tự động hóa quá trình kết nối ViewModel với Chế độ xem. Hình sau thể hiện quy trình cấp cao của ViewModelLocator.

Step 1 - Tìm ra kiểu View nào đang được xây dựng.

Step 2 - Xác định ViewModel cho loại View cụ thể đó.

Step 3 - Xây dựng ViewModel đó.

Step 4 - Đặt View DataContext thành ViewModel.

Để hiểu khái niệm cơ bản, chúng ta hãy xem ví dụ đơn giản của ViewModelLocator bằng cách tiếp tục ví dụ tương tự từ chương trước. Nếu bạn nhìn vào tệp StudentView.xaml, bạn sẽ thấy rằng chúng tôi đã kết nối tĩnh ViewModel.

Bây giờ như được hiển thị trong chương trình sau, nhận xét mã XAML này cũng xóa mã khỏi Code-phía sau.

<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>

Bây giờ, hãy tạo một thư mục mới VML và thêm một lớp công khai ViewModelLocator mới sẽ chứa một thuộc tính đính kèm duy nhất (thuộc tính phụ thuộc) AutoHookedUpViewModel như được hiển thị trong đoạn mã sau.

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));

Và bây giờ bạn có thể thấy một định nghĩa thuộc tính đính kèm cơ bản. Để thêm hành vi vào thuộc tính, chúng ta cần thêm một trình xử lý sự kiện đã thay đổi cho thuộc tính này, chứa quy trình tự động kết nối ViewModel cho Chế độ xem. Mã để thực hiện việc này như sau:

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; 
}

Sau đây là phần triển khai hoàn chỉnh của lớp 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; 
      } 
   } 
}

Điều đầu tiên cần làm là thêm một không gian tên để chúng ta có thể truy cập loại ViewModelLocator đó trong thư mục gốc của dự án của chúng ta. Sau đó, trên phần tử định tuyến là một loại chế độ xem, hãy thêm thuộc tính AutoHookedUpViewModel và đặt nó thành true.

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

Đây là phần triển khai hoàn chỉnh của tệp 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>

Khi đoạn mã trên được biên dịch và thực thi, bạn sẽ thấy ViewModelLocator đang kết nối ViewModel cho View cụ thể đó.

Một điều quan trọng cần lưu ý về điều này là chế độ xem không còn được kết hợp theo cách với loại ViewModel của nó hoặc cách nó được xây dựng. Tất cả những thứ đó đã được chuyển đến vị trí trung tâm bên trong ViewModelLocator.

Trong chương này, chúng ta sẽ tìm hiểu cách liên kết dữ liệu hỗ trợ mẫu MVVM. Liên kết dữ liệu là tính năng chính giúp phân biệt MVVM với các mẫu phân tách giao diện người dùng khác như MVC và MVP.

  • Đối với liên kết dữ liệu, bạn cần có một chế độ xem hoặc tập hợp các phần tử giao diện người dùng được xây dựng và sau đó bạn cần một số đối tượng khác mà các liên kết sẽ trỏ tới.

  • Các phần tử giao diện người dùng trong một dạng xem được liên kết với các thuộc tính được hiển thị bởi ViewModel.

  • Thứ tự mà View và ViewModel được xây dựng tùy thuộc vào tình huống, vì chúng tôi đã đề cập đến View trước.

  • Một View và ViewModel được xây dựng và DataContext của View được đặt thành ViewModel.

  • Liên kết có thể là liên kết dữ liệu OneWay hoặc TwoWay để chuyển dữ liệu qua lại giữa View và ViewModel.

Hãy xem xét các ràng buộc dữ liệu trong cùng một ví dụ. Dưới đây là mã XAML của 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>
  • Nếu bạn nhìn vào mã XAML ở trên, bạn sẽ thấy rằng ItemsControl được liên kết với bộ sưu tập Sinh viên được hiển thị bởi ViewModel.

  • Bạn cũng có thể thấy rằng thuộc tính của mô hình Sinh viên cũng có các ràng buộc riêng lẻ của chúng và những ràng buộc này được liên kết với Hộp văn bản và Khối văn bản.

  • ItemSource của ItemsControl có thể liên kết với thuộc tính Students, vì DataContext tổng thể cho Chế độ xem được đặt thành ViewModel.

  • Các ràng buộc riêng lẻ của các thuộc tính ở đây cũng là các liên kết DataContext, nhưng chúng không ràng buộc với chính ViewModel, do cách hoạt động của ItemSource.

  • Khi một nguồn mục liên kết với bộ sưu tập của nó, nó sẽ hiển thị một vùng chứa cho mỗi mục khi hiển thị và nó đặt DataContext của vùng chứa đó thành mục đó. Vì vậy, DataContext tổng thể cho mỗi hộp văn bản và khối văn bản trong một hàng sẽ là một Sinh viên riêng lẻ trong bộ sưu tập. Và bạn cũng có thể thấy rằng các ràng buộc này đối với TextBox là liên kết dữ liệu TwoWay và đối với TextBlock thì đó là liên kết dữ liệu OneWay vì bạn không thể chỉnh sửa TextBlock.

Khi bạn chạy lại ứng dụng này, bạn sẽ thấy kết quả sau.

Bây giờ chúng ta hãy thay đổi văn bản trong hộp văn bản thứ hai của hàng đầu tiên từ Allain thành Upston và nhấn tab để mất tiêu điểm. Bạn sẽ thấy rằng văn bản TextBlock cũng được cập nhật.

Điều này là do các ràng buộc của TextBox được đặt thành TwoWay và nó cũng cập nhật Mô hình, và từ mô hình, TextBlock lại được cập nhật.

Mẫu mô tả giao diện tổng thể và hình thức trực quan của điều khiển. Đối với mỗi điều khiển, có một mẫu mặc định được liên kết với nó để cung cấp hình thức cho điều khiển đó. Trong ứng dụng WPF, bạn có thể dễ dàng tạo các mẫu của riêng mình khi bạn muốn tùy chỉnh hành vi trực quan và hình thức trực quan của điều khiển. Kết nối giữa logic và mẫu có thể đạt được bằng cách liên kết dữ liệu.

Trong MVVM, có một dạng chính khác được gọi là bản dựng đầu tiên của ViewModel.

  • Phương pháp tiếp cận xây dựng đầu tiên của ViewModel tận dụng khả năng của các mẫu dữ liệu ngầm trong WPF.

  • Mẫu dữ liệu ngầm định có thể tự động chọn một mẫu thích hợp từ từ điển tài nguyên hiện tại cho một phần tử sử dụng liên kết dữ liệu. Họ thực hiện điều này dựa trên loại đối tượng dữ liệu được hiển thị bằng liên kết dữ liệu. Đầu tiên, bạn cần có một số phần tử liên kết với một đối tượng dữ liệu.

Hãy xem lại ví dụ đơn giản của chúng tôi, trong đó bạn sẽ hiểu cách bạn có thể thực hiện chế độ xem mô hình trước tiên tận dụng các mẫu dữ liệu, cụ thể là các mẫu dữ liệu ngầm định. Đây là việc triển khai lớp StudentViewModel của chúng tôi.

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; 
      } 
   } 
}

Bạn có thể thấy rằng ViewModel ở trên là không thay đổi. Chúng ta sẽ tiếp tục với ví dụ tương tự từ chương trước. Lớp ViewModel này chỉ hiển thị thuộc tính bộ sưu tập Sinh viên và đưa nó vào quá trình xây dựng. Hãy chuyển đến tệp StudentView.xaml, loại bỏ triển khai hiện có và xác định mẫu dữ liệu trong phần Tài nguyên.

<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>

Bây giờ, thêm một hộp danh sách và dữ liệu liên kết hộp danh sách đó với thuộc tính Sinh viên như được hiển thị trong đoạn mã sau.

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

Trong phần Resource, DataTemplate có một khóa là studentTemplate và để thực sự sử dụng mẫu đó, chúng ta cần sử dụng thuộc tính ItemTemplate của một ListBox. Vì vậy, bây giờ bạn có thể thấy rằng chúng tôi hướng dẫn hộp danh sách sử dụng mẫu cụ thể đó để hiển thị các Học sinh đó. Sau đây là quá trình triển khai hoàn chỉnh tệp 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>

Khi đoạn mã trên được biên dịch và thực thi, bạn sẽ thấy cửa sổ sau chứa một ListBox. Mỗi ListBoxItem chứa dữ liệu đối tượng lớp Sinh viên được hiển thị trên TextBlock và hộp Văn bản.

Để làm cho đây là một mẫu ngầm định, chúng ta cần xóa thuộc tính ItemTemplate khỏi hộp danh sách và thêm thuộc tính DataType vào định nghĩa mẫu của chúng tôi như được hiển thị trong đoạn mã sau.

<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>

Trong DataTemplate, phần mở rộng đánh dấu x: Type rất quan trọng, nó giống như một loại toán tử trong XAML. Vì vậy, về cơ bản chúng ta cần trỏ đến kiểu dữ liệu Sinh viên trong không gian tên MVVMDemo.Model. Sau đây là tệp XAML hoàn chỉnh được cập nhật.

<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>

Khi bạn chạy lại ứng dụng này, bạn sẽ vẫn nhận được kết xuất tương tự của Mẫu dữ liệu sinh viên vì nó tự động ánh xạ loại đối tượng đang được hiển thị bằng cách định vị DataTemplate thích hợp.

Chúng tôi khuyên bạn nên thực hiện ví dụ trên theo phương pháp từng bước để hiểu rõ hơn.

Trong chương này, chúng ta sẽ học cách thêm tính tương tác vào các ứng dụng MVVM và cách gọi logic rõ ràng. Bạn cũng sẽ thấy rằng tất cả những điều này được thực hiện bằng cách duy trì khớp nối lỏng lẻo và cấu trúc tốt là trung tâm của mô hình MVVM. Để hiểu tất cả điều này, trước tiên chúng ta hãy tìm hiểu về các lệnh.

View / ViewModel Communication qua Commands

Mẫu lệnh đã được ghi chép đầy đủ và thường xuyên sử dụng mẫu thiết kế trong một vài thập kỷ. Trong mô hình này có hai tác nhân chính, người gọi và người nhận.

Người tham gia

  • Kẻ xâm lược là một đoạn mã có thể thực thi một số logic mệnh lệnh.

  • Thông thường, nó là một phần tử giao diện người dùng mà người dùng tương tác với, trong ngữ cảnh của khung giao diện người dùng.

  • Nó chỉ có thể là một đoạn mã logic khác ở đâu đó trong ứng dụng.

Người nhận

  • Bộ nhận là logic được thiết kế để thực hiện khi kẻ gọi tên bắn ra.

  • Trong ngữ cảnh của MVVM, bộ thu thường là một phương thức trong ViewModel của bạn cần được gọi.

Ở giữa hai thứ này, bạn có một lớp vật cản, có nghĩa là người gọi và người nhận không cần phải biết rõ ràng về nhau. Điều này thường được biểu diễn dưới dạng một giao diện trừu tượng tiếp xúc với người gọi và một triển khai cụ thể của giao diện đó có khả năng gọi người nhận.

Hãy xem một ví dụ đơn giản, trong đó bạn sẽ học các lệnh và cách sử dụng chúng để giao tiếp giữa View và ViewModel. Trong chương này, chúng ta sẽ tiếp tục với ví dụ tương tự từ chương trước.

Trong tệp StudentView.xaml, chúng tôi có một ListBox kết nối dữ liệu sinh viên từ một ViewModel. Bây giờ, hãy thêm một nút để xóa một sinh viên khỏi ListBox.

Điều quan trọng là làm việc với các lệnh trên nút rất dễ dàng vì chúng có thuộc tính lệnh để kết nối với một ICommand.

Vì vậy, chúng tôi có thể hiển thị một thuộc tính trên ViewModel của chúng tôi có một ICommand và liên kết với nó từ thuộc tính lệnh của nút như được hiển thị trong đoạn mã sau.

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

Hãy thêm một lớp mới trong dự án của bạn, lớp này sẽ triển khai giao diện ICommand. Sau đây là việc thực hiện giao diện 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(); 
         } 
      } 
   } 
}

Như bạn có thể thấy, đây là một triển khai ủy quyền đơn giản của ICommand, trong đó chúng ta có hai ủy quyền, một cho executeMethod và một cho canExecuteMethod có thể được chuyển vào khi xây dựng.

Trong cách triển khai ở trên, có hai hàm tạo được nạp chồng, một cho chỉ executeMethod và một cho cả executeMethod và tôi có thể canExecuteMethod.

Hãy thêm một thuộc tính kiểu MyICommand trong lớp StudentView Model. Bây giờ chúng ta cần tạo một thể hiện trong StudentViewModel. Chúng tôi sẽ sử dụng phương thức khởi tạo được nạp chồng của MyICommand có hai tham số.

public MyICommand DeleteCommand { get; set;} 

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

Bây giờ thêm việc triển khai các phương thức OnDelete và CanDelete.

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

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

Chúng tôi cũng cần thêm một Học viên được Chọn mới để người dùng có thể xóa Mục đã Chọn khỏi ListBox.

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

Sau đây là phần triển khai hoàn chỉnh của lớp 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; 
      }
   } 
}

Trong StudentView.xaml, chúng ta cần thêm thuộc tính SelectedItem trong ListBox sẽ liên kết với thuộc tính SelectStudent.

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

Sau đây là tệp xaml hoàn chỉnh.

<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>

Khi đoạn mã trên được biên dịch và thực thi, bạn sẽ thấy cửa sổ sau.

Bạn có thể thấy rằng nút xóa đã bị tắt. Nó sẽ được bật khi bạn chọn bất kỳ mục nào.

Khi bạn chọn bất kỳ mục nào và nhấn xóa. Bạn sẽ thấy rằng danh sách mục đã chọn bị xóa và nút xóa lại bị vô hiệu hóa.

Chúng tôi khuyên bạn nên thực hiện ví dụ trên theo cách từng bước để hiểu rõ hơn.

Khi xây dựng ứng dụng MVVM, bạn thường phân tách các màn hình thông tin phức tạp thành một tập hợp các chế độ xem mẹ và con, trong đó các chế độ xem con được chứa trong các chế độ xem chính trong các bảng điều khiển hoặc vùng chứa và tạo thành một hệ thống phân cấp sử dụng.

  • Sau khi phân tách các Chế độ xem phức tạp, điều đó không có nghĩa là mỗi và mọi phần nội dung con mà bạn tách thành tệp XAML của riêng nó nhất thiết phải là chế độ xem MVVM.

  • Phần nội dung chỉ cung cấp cấu trúc để hiển thị nội dung nào đó lên màn hình và không hỗ trợ bất kỳ đầu vào hoặc thao tác nào của người dùng đối với nội dung đó.

  • Nó có thể không cần một ViewModel riêng biệt, nhưng nó có thể chỉ là một đoạn XAML hiển thị dựa trên các thuộc tính được hiển thị bởi ViewModel cha mẹ.

  • Cuối cùng, nếu bạn có hệ thống phân cấp Chế độ xem và ViewModel, thì ViewModel mẹ có thể trở thành trung tâm giao tiếp để mỗi ViewModel con có thể được tách riêng khỏi ViewModel con khác và khỏi ViewModel mẹ nhiều nhất có thể.

Hãy xem một ví dụ trong đó chúng ta sẽ xác định một hệ thống phân cấp đơn giản giữa các khung nhìn khác nhau. Tạo một dự án Ứng dụng WPF mớiMVVMHierarchiesDemo

Step 1 - Thêm ba thư mục (Model, ViewModel và Views) vào dự án của bạn.

Step 2 - Thêm các lớp Khách hàng và Đơn hàng trong thư mục Model, CustomerListView và OrderView trong thư mục Views, và CustomerListViewModel và OrderViewModel trong thư mục ViewModel như trong hình sau.

Step 3- Thêm các khối văn bản trong cả CustomerListView và OrderView. Đây là tệp 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>

Sau đây là tệp 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>

Bây giờ chúng tôi cần một cái gì đó để lưu trữ các chế độ xem này và một nơi tốt cho việc đó trong MainWindow của chúng tôi vì nó là một ứng dụng đơn giản. Chúng tôi cần một điều khiển vùng chứa mà chúng tôi có thể đặt các chế độ xem của mình và chuyển đổi chúng theo cách điều hướng. Vì mục đích này, chúng tôi cần thêm ContentControl trong tệp MainWindow.xaml của mình và chúng tôi sẽ sử dụng thuộc tính nội dung của nó và liên kết nó với một tham chiếu ViewModel.

Bây giờ xác định các mẫu dữ liệu cho mỗi dạng xem trong từ điển tài nguyên. Sau đây là tệp MainWindow.xaml. Lưu ý cách mỗi mẫu dữ liệu ánh xạ một kiểu dữ liệu (kiểu ViewModel) đến một Dạng xem tương ứng.

<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>

Bất cứ khi nào mô hình chế độ xem hiện tại được đặt thành một phiên bản của CustomerListViewModel, nó sẽ hiển thị một CustomerListView với ViewModel được kết nối. Đó là một ViewModel đặt hàng, nó sẽ hiển thị OrderView, v.v.

Bây giờ chúng ta cần một ViewModel có thuộc tính CurrentViewModel và một số logic và lệnh để có thể chuyển tham chiếu hiện tại của ViewModel bên trong thuộc tính.

Hãy tạo một ViewModel cho MainWindow này được gọi là MainWindowViewModel. Chúng ta chỉ có thể tạo một phiên bản ViewModel của chúng ta từ XAML và sử dụng nó để đặt thuộc tính DataContext của cửa sổ. Đối với điều này, chúng ta cần tạo một lớp cơ sở để đóng gói việc triển khai INotifyPropertyChanged cho các ViewModels của chúng ta.

Ý tưởng chính đằng sau lớp này là đóng gói triển khai INotifyPropertyChanged và cung cấp các phương thức trợ giúp cho lớp dẫn xuất để chúng có thể dễ dàng kích hoạt các thông báo thích hợp. Sau đây là việc triển khai lớp 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 { }; 
   } 
}

Bây giờ đã đến lúc thực sự bắt đầu thực hiện một số chuyển đổi chế độ xem bằng thuộc tính CurrentViewModel của chúng tôi. Chúng tôi chỉ cần một số cách để thúc đẩy thiết lập của thuộc tính này. Và chúng tôi sẽ làm nó để người dùng cuối có thể ra lệnh truy cập danh sách khách hàng hoặc xem đơn đặt hàng. Đầu tiên hãy thêm một lớp mới vào dự án của bạn, lớp này sẽ triển khai giao diện ICommand. Sau đây là việc thực hiện giao diện 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 
   } 
}

Bây giờ chúng ta cần thiết lập một số điều hướng cấp cao nhất đến những thứ này tới ViewModels và logic cho việc chuyển đổi đó sẽ thuộc về MainWindowViewModel. Đối với điều này, chúng tôi sẽ sử dụng một phương thức được gọi là điều hướng lấy đích đến là chuỗi và trả về thuộc tính CurrentViewModel.

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

Để điều hướng các Chế độ xem khác nhau này, chúng ta cần thêm hai nút trong tệp MainWindow.xaml của mình. Sau đây là quá trình triển khai tệp XAML hoàn chỉnh.

<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>

Sau đây là cách triển khai MainWindowViewModel hoàn chỉnh.

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; 
         } 
      } 
   } 
}

Lấy ra tất cả các ViewModels của bạn từ lớp BindableBase. Khi đoạn mã trên được biên dịch và thực thi, bạn sẽ thấy kết quả sau.

Như bạn có thể thấy, chúng tôi chỉ thêm hai nút và một CurrentViewModel trên MainWindow của chúng tôi. Nếu bạn nhấp vào bất kỳ nút nào thì nó sẽ điều hướng đến Chế độ xem cụ thể đó. Hãy nhấp vào nút Khách hàng và bạn sẽ thấy rằng CustomerListView được hiển thị.

Chúng tôi khuyên bạn nên thực hiện ví dụ trên theo cách từng bước để hiểu rõ hơn.

Trong chương này, chúng ta sẽ tìm hiểu về các xác nhận. Chúng tôi cũng sẽ xem xét một cách rõ ràng để thực hiện xác thực với những gì liên kết WPF đã hỗ trợ nhưng buộc nó vào các thành phần MVVM.

Xác thực trong MVVM

  • Khi ứng dụng của bạn bắt đầu chấp nhận đầu vào dữ liệu từ người dùng cuối, bạn cần xem xét xác thực đầu vào đó.

  • Đảm bảo rằng nó phù hợp với yêu cầu tổng thể của bạn.

  • WPF có một số bản dựng và tính năng tuyệt vời trong hệ thống ràng buộc để xác thực đầu vào và bạn vẫn có thể tận dụng tất cả các tính năng đó khi thực hiện MVVM.

  • Hãy nhớ rằng logic hỗ trợ xác thực của bạn và xác định những quy tắc nào tồn tại cho những thuộc tính nào nên là một phần của Mô hình hoặc Mô hình xem, không phải chính Chế độ xem.

Bạn vẫn có thể sử dụng tất cả các cách thể hiện xác thực được hỗ trợ bởi liên kết dữ liệu WPF bao gồm:

  • Đặt ngoại lệ cho một thuộc tính.
  • Triển khai giao diện IDataErrorInfo.
  • Triển khai INotifyDataErrorInfo.
  • Sử dụng các quy tắc xác thực WPF.

Nói chung, INotifyDataErrorInfo được khuyến nghị và được giới thiệu với WPF .net 4.5 và nó hỗ trợ truy vấn đối tượng để tìm các lỗi liên quan đến thuộc tính và nó cũng sửa một số thiếu sót với tất cả các tùy chọn khác. Cụ thể, nó cho phép xác nhận không đồng bộ. Nó cho phép các thuộc tính có nhiều lỗi liên quan đến chúng.

Thêm xác thực

Hãy xem một ví dụ trong đó chúng tôi sẽ thêm hỗ trợ xác thực vào chế độ xem đầu vào của chúng tôi và trong ứng dụng lớn, bạn có thể sẽ cần một số vị trí này trong ứng dụng của mình. Đôi khi trên Chế độ xem, đôi khi trên ViewModels và đôi khi trên các đối tượng trợ giúp này có các trình bao bọc xung quanh các đối tượng mô hình.

Đó là một phương pháp hay để đưa hỗ trợ xác thực vào một lớp cơ sở chung mà sau đó bạn có thể kế thừa từ các tình huống khác nhau.

Lớp cơ sở sẽ hỗ trợ INotifyDataErrorInfo để xác thực đó được kích hoạt khi các thuộc tính thay đổi.

Tạo thêm một lớp mới có tên là ValidatableBindableBase. Vì chúng ta đã có một lớp cơ sở để xử lý thay đổi thuộc tính, hãy lấy lớp cơ sở từ nó và cũng triển khai giao diện INotifyDataErrorInfo.

Sau đây là việc triển khai lớp 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)); 
      } 
   } 
}

Bây giờ hãy thêm AddEditCustomerView và AddEditCustomerViewModel trong các thư mục tương ứng. Sau đây là mã của 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>

Sau đây là triển khai 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; 
      }  
   } 
}

Sau đây là việc triển khai lớp 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); } 
      } 
   } 
}

Khi đoạn mã trên được biên dịch và thực thi, bạn sẽ thấy cửa sổ sau.

Khi bạn nhấn nút Thêm khách hàng, bạn sẽ thấy giao diện sau. Khi người dùng để trống bất kỳ trường nào, trường đó sẽ được tô sáng và nút lưu sẽ bị tắt.

Trong chương này, chúng ta sẽ thảo luận ngắn gọn về việc tiêm phụ thuộc. Chúng tôi đã đề cập đến việc phân tách ràng buộc dữ liệu giữa các View và ViewModels với nhau cho phép chúng giao tiếp với nhau mà không cần biết rõ ràng điều gì đang xảy ra ở đầu kia của giao tiếp.

Bây giờ chúng ta cần một cái gì đó tương tự để tách ViewModel của chúng ta khỏi các dịch vụ khách hàng.

Trong những ngày đầu của lập trình hướng đối tượng, các nhà phát triển đã phải đối mặt với vấn đề tạo và truy xuất các thể hiện của các lớp trong ứng dụng. Nhiều giải pháp khác nhau đã được đề xuất cho vấn đề này.

Trong vài năm qua, tính năng tiêm phụ thuộc và đảo ngược kiểm soát (IoC) đã trở nên phổ biến trong giới phát triển và được ưu tiên hơn một số giải pháp cũ hơn như mẫu Singleton.

Chứa phụ thuộc / IoC

IoC và tiêm phụ thuộc là hai mẫu thiết kế có liên quan chặt chẽ với nhau và vùng chứa về cơ bản là một đoạn mã cơ sở hạ tầng thực hiện cả hai mẫu đó cho bạn.

  • Mẫu IoC là về việc ủy ​​quyền trách nhiệm xây dựng và mẫu chèn phụ thuộc là về việc cung cấp các phụ thuộc cho một đối tượng đã được xây dựng.

  • Cả hai đều có thể được coi là một cách tiếp cận hai giai đoạn để xây dựng. Khi bạn sử dụng một thùng chứa, thùng chứa có một số trách nhiệm như sau:

    • Nó xây dựng một đối tượng khi được hỏi.
    • Vùng chứa sẽ xác định đối tượng đó phụ thuộc vào cái gì.
    • Xây dựng các phụ thuộc đó.
    • Tiêm chúng vào vật thể đang được xây dựng.
    • Quy trình làm đệ quy.

Chúng ta hãy xem cách chúng ta có thể sử dụng phụ thuộc chèn để phá vỡ phân tách giữa ViewModels và các dịch vụ khách hàng. Chúng tôi sẽ kết nối biểu mẫu AddEditCustomerViewModel xử lý lưu bằng cách sử dụng phụ thuộc chèn liên quan đến điều đó.

Đầu tiên, chúng ta cần tạo một giao diện mới trong dự án của chúng ta trong thư mục Services. Nếu bạn không có thư mục dịch vụ trong dự án của mình thì hãy tạo trước tiên và thêm giao diện sau vào thư mục Dịch vụ.

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); 
   } 
}

Sau đây là việc triển khai 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(); 
      } 
   } 
}

Cách đơn giản để thực hiện xử lý Lưu là thêm một phiên bản mới của ICustomersRepository trong AddEditCustomerViewModel và nạp chồng phương thức khởi tạo AddEditCustomerViewModel và CustomerListViewModel.

private ICustomersRepository _repo; 

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

Cập nhật phương thức OnSave như được hiển thị trong đoạn mã sau.

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; 
}

Sau đây là AddEditCustomerViewModel hoàn chỉnh.

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; 
         }
      } 
   } 
}

Khi đoạn mã trên được biên dịch và thực thi, bạn sẽ thấy cùng một đầu ra nhưng bây giờ các ViewModels được phân tách lỏng lẻo hơn.

Khi bạn nhấn nút Thêm khách hàng, bạn sẽ thấy giao diện sau. Khi người dùng để trống bất kỳ trường nào, trường đó sẽ được tô sáng và nút lưu sẽ bị tắt.

Sự kiện là một cấu trúc lập trình phản ứng với sự thay đổi trạng thái, thông báo cho bất kỳ điểm cuối nào đã đăng ký thông báo. Chủ yếu, các sự kiện được sử dụng để thông báo cho người dùng nhập thông qua chuột và bàn phím, nhưng tính hữu ích của chúng không chỉ giới hạn ở đó. Bất cứ khi nào một thay đổi trạng thái được phát hiện, có lẽ khi một đối tượng đã được tải hoặc khởi tạo, một sự kiện có thể được kích hoạt để cảnh báo cho bất kỳ bên thứ ba nào quan tâm.

  • Trong ứng dụng WPF sử dụng mẫu thiết kế MVVM (Model-View-ViewModel), mô hình khung nhìn là thành phần chịu trách nhiệm xử lý trạng thái và logic trình bày của ứng dụng.

  • Tệp mã phía sau của chế độ xem không được chứa mã để xử lý các sự kiện được tạo ra từ bất kỳ phần tử Giao diện người dùng (UI) nào như Nút hoặc ComboBox cũng như không được chứa bất kỳ lôgic cụ thể nào của miền.

  • Lý tưởng nhất, mã phía sau của một Chế độ xem chỉ chứa một hàm tạo gọi phương thức InitializeComponent và có lẽ một số mã bổ sung để kiểm soát hoặc tương tác với lớp chế độ xem khó hoặc không hiệu quả để diễn đạt trong XAML, ví dụ như các hoạt ảnh phức tạp.

Hãy xem một ví dụ đơn giản về các sự kiện nhấp vào nút trong ứng dụng của chúng tôi. Sau đây là mã XAML của tệp MainWindow.xaml, trong đó bạn sẽ thấy hai nút.

<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>

Bạn có thể thấy rằng thuộc tính Click button không được sử dụng trong tệp XAML ở trên nhưng thuộc tính Command và CommandParameter được sử dụng để tải các View khác nhau khi nút được nhấn. Bây giờ bạn cần xác định việc triển khai các lệnh trong tệp MainWindowViewModel.cs nhưng không phải trong tệp Xem. Sau đây là cách triển khai MainWindowViewModel hoàn chỉnh.

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; 
         } 
      } 
   }
}

Lấy ra tất cả các ViewModels của bạn từ lớp BindableBase. Khi đoạn mã trên được biên dịch và thực thi, bạn sẽ thấy kết quả sau.

Như bạn có thể thấy, chúng tôi chỉ thêm hai nút và một CurrentViewModel trên MainWindow của chúng tôi. Bây giờ nếu bạn nhấp vào bất kỳ nút nào thì nó sẽ điều hướng đến Chế độ xem cụ thể đó. Hãy nhấp vào nút Khách hàng và bạn sẽ thấy rằng CustomerListView được hiển thị.

Chúng tôi khuyên bạn nên thực hiện ví dụ trên theo phương pháp từng bước để hiểu rõ hơn.

Ý tưởng đằng sau kiểm thử đơn vị là lấy các đoạn mã (đơn vị) rời rạc và viết các phương pháp kiểm tra sử dụng mã theo cách mong đợi, sau đó kiểm tra để xem liệu chúng có đạt được kết quả mong đợi hay không.

  • Bản thân là mã, các bài kiểm tra đơn vị được biên dịch giống như phần còn lại của dự án.

  • Chúng cũng được thực thi bởi phần mềm chạy thử nghiệm, phần mềm này có thể tăng tốc độ qua mỗi bài kiểm tra, có hiệu quả đưa ra các biểu tượng thích hoặc không thích để cho biết bài kiểm tra đã đạt hay không.

Hãy xem một ví dụ đã tạo trước đó. Sau đây là việc thực hiện Mô hình Sinh viên.

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)); 
         } 
      } 
   } 
}

Sau đây là việc triển khai 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>

Sau đây là triển khai 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; 
      } 
   } 
}

Sau đây là tệp 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>

Sau đây là triển khai MyICommand, thực hiện giao diện 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(); 
         } 
      } 
   }
}

Khi đoạn mã trên được biên dịch và thực thi, bạn sẽ thấy kết quả sau trên cửa sổ chính của mình.

Để viết một bài kiểm tra đơn vị cho ví dụ trên, hãy thêm một Dự án kiểm tra mới vào Giải pháp.

Thêm tham chiếu vào dự án bằng cách nhấp chuột phải vào Tham khảo.

Chọn dự án hiện có và nhấp vào Ok.

Bây giờ chúng ta hãy thêm một Bài kiểm tra đơn giản sẽ kiểm tra Số học sinh như được hiển thị trong đoạn mã sau.

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); 
      } 
   } 
}

Để thực hiện kiểm tra này, hãy chọn tùy chọn menu Kiểm tra → Chạy → Tất cả Kiểm tra.

Bạn có thể thấy trong Trình khám phá Kiểm tra rằng Bài kiểm tra đã Vượt qua, vì trong StudentViewModel, ba sinh viên được thêm vào. Thay đổi điều kiện đếm từ 3 thành 4 như thể hiện trong đoạn mã sau.

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);
      } 
   } 
}

Khi kế hoạch kiểm tra được thực hiện lại, bạn sẽ thấy rằng kiểm tra không thành công vì số học sinh không bằng 4.

Chúng tôi khuyên bạn nên thực hiện ví dụ trên theo phương pháp từng bước để hiểu rõ hơn.

Trong chương này, chúng ta sẽ thảo luận về các bộ công cụ MVVM hoặc các khuôn khổ có sẵn. Bạn cũng có thể sử dụng các khuôn khổ này để không phải viết một loạt mã lặp đi lặp lại để tự triển khai mẫu MVVM. Dưới đây là một số khuôn khổ phổ biến nhất -

  • Prism
  • MVVM Light
  • Caliburn Micro

Lăng kính

Prism cung cấp hướng dẫn dưới dạng mẫu và tài liệu giúp bạn dễ dàng thiết kế và xây dựng các ứng dụng Windows Presentation Foundation (WPF) phong phú, linh hoạt và dễ bảo trì. Các ứng dụng Internet phong phú (RIA) được xây dựng với trình cắm trình duyệt Microsoft Silverlight và các ứng dụng Windows.

  • Prism sử dụng các mẫu thiết kế thể hiện các nguyên tắc thiết kế kiến ​​trúc quan trọng, chẳng hạn như tách biệt các mối quan tâm và khớp nối lỏng lẻo.

  • Prism giúp bạn thiết kế và xây dựng các ứng dụng bằng cách sử dụng các thành phần liên kết lỏng lẻo có thể phát triển độc lập nhưng có thể được tích hợp dễ dàng và liền mạch vào ứng dụng tổng thể.

  • Các loại ứng dụng này được gọi là ứng dụng tổng hợp.

Prism có một số tính năng vượt trội. Sau đây là một số tính năng quan trọng của Prism.

Mẫu MVVM

Prism có hỗ trợ cho mẫu MVVM. Nó có một lớp Bindablebase tương tự như lớp được triển khai trong các chương trước.

Nó có một ViewModelLocator linh hoạt có các quy ước cho nó nhưng cho phép bạn ghi đè các quy ước đó và kết nối các View và ViewModels của bạn theo cách được kết hợp lỏng lẻo.

Môđun

Đó là khả năng chia nhỏ mã của bạn thành các thư viện lớp được ghép nối hoàn toàn lỏng lẻo trong các phần và kết hợp chúng lại với nhau trong thời gian chạy thành một tổng thể gắn kết cho người dùng cuối, trong khi mã vẫn được tách rời hoàn toàn.

Thành phần / Khu vực giao diện người dùng

Đó là khả năng cắm các khung nhìn vào vùng chứa mà không cần Chế độ xem đang thực hiện việc cắm, cần có tham chiếu rõ ràng đến chính vùng chứa giao diện người dùng.

dẫn đường

Prism có các tính năng điều hướng xếp lớp trên các vùng, như điều hướng tiến và lùi và ngăn xếp điều hướng cho phép các mô hình chế độ xem của bạn tham gia trực tiếp vào quá trình điều hướng.

Lệnh

Prism có các lệnh nên chúng có lệnh ủy quyền rất giống với lệnh MyICommand mà chúng ta đã sử dụng trong các chương trước ngoại trừ nó có thêm một số độ mạnh để bảo vệ bạn khỏi rò rỉ bộ nhớ.

Sự kiện Pub / Sub

Prism cũng có hỗ trợ cho các sự kiện Pub / Sub. Đây là những sự kiện được kết hợp lỏng lẻo trong đó nhà xuất bản và người đăng ký có thể có thời gian tồn tại khác nhau và không cần phải tham chiếu rõ ràng đến nhau để giao tiếp thông qua các sự kiện.

MVVM Light

MVVM Light được sản xuất bởi Laurent Bugnion và giúp bạn tách Chế độ xem khỏi Mô hình của mình, điều này tạo ra các ứng dụng sạch hơn, dễ bảo trì và mở rộng hơn.

  • Nó cũng tạo ra các ứng dụng có thể kiểm tra và cho phép bạn có một lớp giao diện người dùng mỏng hơn nhiều (khó kiểm tra tự động hơn).

  • Bộ công cụ này đặc biệt chú trọng vào việc mở và chỉnh sửa giao diện người dùng thành Blend, bao gồm cả việc tạo dữ liệu thời gian thiết kế để cho phép người dùng Blend "nhìn thấy thứ gì đó" khi họ làm việc với các điều khiển dữ liệu.

Caliburn Micro

Đây là một khung mã nguồn mở nhỏ khác giúp bạn triển khai mẫu MVVM và cũng hỗ trợ một số thứ khác.

  • Caliburn Micro là một khuôn khổ nhỏ nhưng mạnh mẽ, được thiết kế để xây dựng các ứng dụng trên tất cả các nền tảng XAML.

  • Với sự hỗ trợ mạnh mẽ cho MVVM và các mẫu giao diện người dùng đã được chứng minh khác, Caliburn Micro sẽ cho phép bạn xây dựng giải pháp của mình một cách nhanh chóng mà không cần phải hy sinh chất lượng mã hoặc khả năng kiểm tra.