MVVM - การตรวจสอบ

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

namespace MVVMHierarchiesDemo { 

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

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

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

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

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

<UserControl x:Class = "MVVMHierarchiesDemo.Views.AddEditCustomerView"
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <Grid.RowDefinitions> 
         <RowDefinition Height = "Auto" /> 
         <RowDefinition Height = "Auto" />
      </Grid.RowDefinitions>
		
      <Grid x:Name = "grid1" 
         HorizontalAlignment = "Left" 
         DataContext = "{Binding Customer}" 
         Margin = "10,10,0,0" 
         VerticalAlignment = "Top">
			
         <Grid.ColumnDefinitions> 
            <ColumnDefinition Width = "Auto" /> 
            <ColumnDefinition Width = "Auto" /> 
         </Grid.ColumnDefinitions>
		
         <Grid.RowDefinitions> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
         </Grid.RowDefinitions>
		
         <Label Content = "First Name:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "0" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "firstNameTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "0" 
            Text = "{Binding FirstName, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Last Name:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "1" 
            VerticalAlignment = "Center" /> 
			
         <TextBox x:Name = "lastNameTextBox"
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "1" 
            Text = "{Binding LastName, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Email:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "2" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "emailTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "2" 
            Text = "{Binding Email, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Phone:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "3" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "phoneTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "3" 
            Text = "{Binding Phone, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
      </Grid> 

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

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

using MVVMHierarchiesDemo.Model;

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

namespace MVVMHierarchiesDemo.ViewModel { 

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

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

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

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

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

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

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

namespace MVVMHierarchiesDemo.Model { 

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

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

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