English 中文(简体)
MVVM - Validations
  • 时间:2024-11-03

MVVM – Vapdations


Previous Page Next Page  

In this chapter, we will learn about vapdations. We will also look at a clean way to do vapdation with what WPF bindings already support but tying it into MVVM components.

Vapdation in MVVM

    When your apppcation starts accepting data input from end users you need to consider vapdating that input.

    Make sure it conforms to your overall requirements.

    WPF has some great builds and features in the binding system for vapdating input and you can still leverage all those features when doing MVVM.

    Keep in mind that the logic that supports your vapdation and defines what rules exist for what properties should be part of the Model or the ViewModel, not the View itself.

You can still use all the ways of expressing vapdation that are supported by WPF data binding including −

    Throwing exceptions on a property is set.

    Implementing the IDataErrorInfo interface.

    Implementing INotifyDataErrorInfo.

    Use WPF vapdation rules.

In general, INotifyDataErrorInfo is recommended and was introduced to WPF .net 4.5 and it supports querying the object for errors associated with properties and it also fixes a couple of deficiencies with all the other options. Specifically, it allows asynchronous vapdation. It allows properties to have more than one error associated with them.

Adding Vapdation

Let’s take a look at an example in which we will add vapdation support to our input view, and in large apppcation you will probably need this a number of places in your apppcation. Sometimes on Views, sometimes on ViewModels and sometimes on these helper objects there are wrappers around model objects.

It’s a good practice for putting the vapdation support in a common base class that you can then inherit from different scenarios.

The base class will support INotifyDataErrorInfo so that that vapdation gets triggered when properties change.

Create add a new class called VapdatableBindableBase. Since we already have a base class for a property change handpng, let’s derive the base class from it and also implement the INotifyDataErrorInfo interface.

Following is the implementation of VapdatableBindableBase class.

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 { 

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

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

      pubpc System.Collections.IEnumerable GetErrors(string propertyName) {
		
         if (_errors.ContainsKey(propertyName)) 
            return _errors[propertyName]; 
         else 
            return null; 
      }
      
      pubpc 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);
         VapdateProperty(propertyName, val);
      }
		
      private void VapdateProperty<T>(string propertyName, T value) {
         var results = new List<VapdationResult>();
			
         //VapdationContext context = new VapdationContext(this); 
         //context.MemberName = propertyName;
         //Vapdator.TryVapdateProperty(value, context, results);

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

Now add AddEditCustomerView and AddEditCustomerViewModel in respective folders. Following is the code of 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-compatibipty/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" 
         HorizontalApgnment = "Left" 
         DataContext = "{Binding Customer}" 
         Margin = "10,10,0,0" 
         VerticalApgnment = "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" 
            HorizontalApgnment = "Left" 
            Margin = "3" 
            Grid.Row = "0" 
            VerticalApgnment = "Center" />
			
         <TextBox x:Name = "firstNameTextBox" 
            Grid.Column = "1" 
            HorizontalApgnment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "0" 
            Text = "{Binding FirstName, VapdatesOnNotifyDataErrors = True}"
            VerticalApgnment = "Center" 
            Width = "120" />
			
         <Label Content = "Last Name:" 
            Grid.Column = "0" 
            HorizontalApgnment = "Left" 
            Margin = "3" 
            Grid.Row = "1" 
            VerticalApgnment = "Center" /> 
			
         <TextBox x:Name = "lastNameTextBox"
            Grid.Column = "1" 
            HorizontalApgnment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "1" 
            Text = "{Binding LastName, VapdatesOnNotifyDataErrors = True}"
            VerticalApgnment = "Center" 
            Width = "120" />
			
         <Label Content = "Email:" 
            Grid.Column = "0" 
            HorizontalApgnment = "Left" 
            Margin = "3" 
            Grid.Row = "2" 
            VerticalApgnment = "Center" />
			
         <TextBox x:Name = "emailTextBox" 
            Grid.Column = "1" 
            HorizontalApgnment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "2" 
            Text = "{Binding Email, VapdatesOnNotifyDataErrors = True}"
            VerticalApgnment = "Center" 
            Width = "120" />
			
         <Label Content = "Phone:" 
            Grid.Column = "0" 
            HorizontalApgnment = "Left" 
            Margin = "3" 
            Grid.Row = "3" 
            VerticalApgnment = "Center" />
			
         <TextBox x:Name = "phoneTextBox" 
            Grid.Column = "1" 
            HorizontalApgnment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "3" 
            Text = "{Binding Phone, VapdatesOnNotifyDataErrors = True}"
            VerticalApgnment = "Center" 
            Width = "120" />
			
      </Grid> 

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

Following is the AddEditCustomerViewModel implementation.

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 { 
	
      pubpc AddEditCustomerViewModel() {
         CancelCommand = new MyIcommand(OnCancel); 
         SaveCommand = new MyIcommand(OnSave, CanSave); 
      } 
		
      private bool _EditMode; 
		
      pubpc bool EditMode { 
         get { return _EditMode; } 
         set { SetProperty(ref _EditMode, value);} 
      }
		
      private SimpleEditableCustomer _Customer;
		
      pubpc SimpleEditableCustomer Customer { 
         get { return _Customer; } 
         set { SetProperty(ref _Customer, value);} 
      }

      private Customer _editingCustomer = null;
		
      pubpc 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(); 
      }

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

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

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

Following is the implementation of SimpleEditableCustomer class.

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

namespace MVVMHierarchiesDemo.Model { 

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

When the above code is compiled and executed, you will see the following window.

MVVM Vapdations MainWindow1

When you press the Add Customer button you will see the following view. When the user leaves any field empty, then it will become highpghted and the save button will become disabled.

MVVM Vapdations MainWindow2 Advertisements