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); }
}
}
}
เมื่อโค้ดด้านบนถูกคอมไพล์และรันคุณจะเห็นหน้าต่างต่อไปนี้
เมื่อคุณกดปุ่มเพิ่มลูกค้าคุณจะเห็นมุมมองต่อไปนี้ เมื่อผู้ใช้เว้นช่องว่างไว้ช่องนั้นจะถูกไฮไลต์และปุ่มบันทึกจะปิดใช้งาน