Octubre 03, 2013
aisen
¿Alguna vez han necesitado un Validation Summary en WPF tal como el que podemos usar en ASP.NET?, En WPF no existe tal control!!!, por lo cual nosotros tendremos que crearlo.
En este post veremos cómo crear un Validation Summary en WPF en lenguaje Visual Basic.
- Creación de interfaces.
Utilizaremos estas dos interfaces de .NET
- INotifyPropertyChanged
- IDataErrorInfo
Estas interfaces nos ayudarán a actualizar la validación, y ver cuando se produce algún error.
Crearemos un proyecto que validará lo siguiente: nombre, correo y fecha de nacimiento
Creamos dos carpetas para separar la lógica llamadas “UserControlValidationSummary” (en esta carpeta crearemos el user control de validación y sus clases) y “ValidationData” (en esta carpeta tendremos las clases con las propiedades a validar).
En la carpeta “UserControlValidationSummary” creamos lo siguiente:
- Una interface llamada “ItemsError”
- Una clase llamada “ValidationSummary”
- Un User Control llamado “ValidationSummaryErrors”
- Una clase llamada “ValidationSummaryValidator”
- Creación de clases.
En la carpeta “ValidationData” crearemos una clase llamada “ClassValidation”.
Además creamos una interfaz para la utilización de la validación la cual se verá como la imagen.
Tendra dos textbox (nombre, correo) y un datepicker.
En la carpeta “UserControlValidationSummary” creamos la clase ItemsError, esta interface tendrá un método para establecer el elemento
Namespace UserControlValidationSummary
Public Interface ItemsError
''' <summary>
''' Setea el DependencyObject del elemento
''' </summary>
''' <param name="element"></param>
''' <remarks></remarks>
Sub SetElement(element As DependencyObject)
End Interface
End Namespace
Acto seguido creamos la clase ValidationSummary, esta clase implementará las interfaces de .NET. Es una clase que se multiherencia ya que la utilizaremos en todas nuestras clases de validaciones que tengamos en nuestro sistema.
Imports System.ComponentModel
Namespace UserControlValidationSummary
Public MustInherit Class ValidationSummary
'Implementamos estas interfaces de .NET
Implements INotifyPropertyChanged
Implements IDataErrorInfo
#Region "Eventos"
''' <summary>
''' Funcion que verifica las propiedades
''' </summary>
''' <param name="pPropertyName">Nombre de la propiedad</param>
''' <returns></returns>
''' <remarks></remarks>
Public MustOverride Function CheckProperties(ByVal pPropertyName As String) As String
''' <summary>
''' Evento que se llama cuando una propiedad cambia
''' </summary>
''' <param name="sender">Evento que lo lanza</param>
''' <param name="e">Evento que cambia la propiedad</param>
''' <remarks></remarks>
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
#End Region
''' <summary>
''' Metodo que contiene todos los errores
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property [Error]() As String Implements System.ComponentModel.IDataErrorInfo.Error
Get
Return Me.CheckProperties(String.Empty)
End Get
End Property
''' <summary>
''' Metodo que verifica la propiedad llama a la funcion CheckProperties
''' </summary>
''' <param name="pPropertyName">Nombre de la propiedad</param>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property Item(ByVal pPropertyName As String) As String Implements System.ComponentModel.IDataErrorInfo.Item
Get
Return Me.CheckProperties(pPropertyName)
End Get
End Property
''' <summary>
''' Metodo que se llama cuando seteamos la propiedad
''' </summary>
''' <param name="pPropertyName">Nombre de la propiedad</param>
''' <remarks></remarks>
Public Sub RaisePropertyChanged(ByVal pPropertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(pPropertyName))
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Error"))
End Sub
End Class
End Namespace
También es necesario crear una clase llamada ValidationSummaryErrors, Este es un User Control donde mostraremos los errores. En la sección del xaml de nuestro User Control colocamos lo siguiente.
<UserControl x:Class="UserControlValidationSummary.ValidationSummaryErrors"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Resources>
<DataTemplate x:Key="ErrorViewerItemTemplate" DataType="string" >
<StackPanel Orientation="Horizontal">
<Ellipse Fill="Red" Width="10" Height="7" VerticalAlignment="Center"
HorizontalAlignment="Left" Margin="550,0,0,0" />
<TextBlock Text="{Binding}" FontSize="12" FontStyle="Italic" Foreground="red" Padding="2"
HorizontalAlignment="Left" VerticalAlignment="Center" Margin="10,0,0,0" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<ItemsControl x:Name="itemsValidate" ItemTemplate="{StaticResource ErrorViewerItemTemplate}"></ItemsControl>
</UserControl>
En el code behind del user control añadiremos y eliminamos los errores que se vayan produciendo para que el User Control muestre los errores actuales.
Imports System.Collections.ObjectModel
Namespace UserControlValidationSummary
Partial Public Class ValidationSummaryErrors
Inherits UserControl
'Implementamos nuestra interface
Implements ItemsError
'Creamos una lista de DependencyObject
Private _elements As New List(Of DependencyObject)()
'Creamos una lista de ObservableCollection
Private _errorMessages As New ObservableCollection(Of String)()
''' <summary>
''' Inicializamos la clase
''' </summary>
''' <remarks></remarks>
Public Sub New()
InitializeComponent()
AddHandler Me.Loaded, New RoutedEventHandler(AddressOf ErrorViewer_Loaded)
End Sub
''' <summary>
''' Cargamos el user control con los errores
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub ErrorViewer_Loaded(sender As Object, e As RoutedEventArgs)
itemsValidate.ItemsSource = _errorMessages
End Sub
''' <summary>
''' Crea o elimina el error en el user control
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub Element_ValidationError(sender As Object, e As ValidationErrorEventArgs)
If e.Action = ValidationErrorEventAction.Added AndAlso Not _errorMessages.Contains(e.[Error].ErrorContent.ToString()) Then
_errorMessages.Add(e.[Error].ErrorContent.ToString())
ElseIf e.Action = ValidationErrorEventAction.Removed AndAlso _errorMessages.Contains(e.[Error].ErrorContent.ToString()) Then
_errorMessages.Remove(e.[Error].ErrorContent.ToString())
End If
End Sub
''' <summary>
''' Seteamos el error, utilizamos nuestra interface
''' </summary>
''' <param name="element"></param>
''' <remarks></remarks>
Public Sub SetElement(element As DependencyObject) Implements ItemsError.SetElement
If Not _elements.Contains(element) Then
_elements.Add(element)
Validation.AddErrorHandler(element, AddressOf Element_ValidationError)
End If
End Sub
End Class
End Namespace
La última clase de este folder sería ValidationSummaryValidator, esta clase es para crear la notificación en los errores, la vamos a utilizar cuando creamos los estilos para los controles que tengamos que validar.
Namespace UserControlValidationSummary
Public NotInheritable Class ValidationSummaryValidator
''' <summary>
''' Creamos un DependencyProperty para la decoracion del error
''' </summary>
''' <remarks></remarks>
Public Shared AdornerSiteProperty As DependencyProperty = DependencyProperty.RegisterAttached("AdornerSite", GetType(DependencyObject), GetType(ValidationSummaryValidator), New PropertyMetadata(Nothing, AddressOf OnAdornerSiteChanged))
''' <summary>
''' Obtiene la decoracion del error
''' </summary>
''' <param name="element"></param>
''' <returns></returns>
''' <remarks></remarks>
<AttachedPropertyBrowsableForType(GetType(DependencyObject))> _
Public Shared Function GetAdornerSite(element As DependencyObject) As DependencyObject
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
Return TryCast(element.GetValue(AdornerSiteProperty), DependencyObject)
End Function
''' <summary>
''' Setea la decoracion del error
''' </summary>
''' <param name="element"></param>
''' <param name="value"></param>
''' <remarks></remarks>
Public Shared Sub SetAdornerSite(element As DependencyObject, value As DependencyObject)
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
element.SetValue(AdornerSiteProperty, value)
End Sub
''' <summary>
''' Cuando la propiedad cambia se setea la decoracion
''' </summary>
''' <param name="d"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Shared Sub OnAdornerSiteChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim errorViewer As ItemsError = TryCast(e.NewValue, ItemsError)
If errorViewer IsNot Nothing Then
errorViewer.SetElement(d)
End If
End Sub
End Class
End Namespace
- Utilización de nuestros controles.
En la carpeta “ValidationData” crearemos la clase ClassValidation. esta será nuestra clase de validación en donde crearemos nuestras propiedades a validar y donde pondremos la lógica que van a tener nuestras propiedades.
Esta clase hereda de la clase ValidationSummary. Solo pondré una propiedad como referencia las demás las adjunto en el recurso del proyecto.
Imports System.Text.RegularExpressions
Imports ValidationSummary.UserControlValidationSummary
Namespace ValidationData
Public Class ClassValidation : Inherits ValidationSummary.UserControlValidationSummary.ValidationSummary
#Region "Declaraciones"
Public _nombre As String = String.Empty
#End Region
#Region "Propiedades"
Public Property Nombre() As String
Get
Return _nombre
End Get
Set(value As String)
_nombre = value
RaisePropertyChanged("Nombre")
End Set
End Property
#End Region
#Region "Metodos"
''' <summary>
''' Verificamos las propiedades, y colocamos nuestra logica a aplicar
''' </summary>
''' <param name="pPropertyName">Nombre de la propiedad</param>
''' <returns></returns>
''' <remarks></remarks>
Public Overrides Function CheckProperties(pPropertyName As String) As String
Dim vlResult As String = String.Empty
'Validamos la propiedad "Nombre"
If String.IsNullOrEmpty(pPropertyName) OrElse String.Compare(pPropertyName, "Nombre", True) = 0 Then
If _nombre = Nothing Then
vlResult = "El nombre es requerido"
End If
End If
Return vlResult
End Function
#End Region
End Class
End Namespace
En el markup de nuestro window crearemos los textbox, datepicker y demás elementos, además creamos los estilos para nuestros textbox y datepicker para visualización de los errores.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:PropertiesValidate="clr-namespace:ValidationSummary.ValidationData"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ValidationCore="clr-namespace:ValidationSummary.UserControlValidationSummary"
Title="Validacion WPF" Height="250" Width="500" Loaded="Window_Loaded_1">
<Window.Resources>
<PropertiesValidate:ClassValidation x:Key="validation" />
<Style TargetType="{x:Type TextBox}" >
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,2,40,2" />
<Setter Property="ValidationCore:ValidationSummaryValidator.AdornerSite" Value="{Binding ElementName=validationSummary}"></Setter>
<Setter Property="Validation.ErrorTemplate" >
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="true">
<Border Background="Red" DockPanel.Dock="right" Margin="5,0,0,0" Width="20" Height="20" CornerRadius="10"
ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
<TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white">
</TextBlock>
</Border>
<AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource validation}}">
<Label Content="Escriba su nombre: " HorizontalAlignment="Left" Margin="30,43,0,0" VerticalAlignment="Top"/>
<Label Content="Escriba su correo: " HorizontalAlignment="Left" Margin="30,74,0,0" VerticalAlignment="Top"/>
<Label Content="Seleccione su fecha de nacimiento: " HorizontalAlignment="Left" Margin="30,115,0,0" VerticalAlignment="Top"/>
<TextBox Name="txtNombre" HorizontalAlignment="Left" Height="23" Margin="204,43,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="206"
Validation.Error="Validation_Error" Text="{Binding Path=Nombre, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=LostFocus}" />
<TextBox Name="txtCorreo" HorizontalAlignment="Left" Height="23" Margin="204,77,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="206"
Validation.Error="Validation_Error" Text="{Binding Path=Correo, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=LostFocus}" />
<DatePicker Name="dtpFecha" HorizontalAlignment="Left" Margin="308,117,0,0" VerticalAlignment="Top"
Validation.Error="Validation_Error" SelectedDate="{Binding Path=FechaNacimiemto, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=LostFocus}" />
<Button Content="Enviar" HorizontalAlignment="Left" Margin="335,174,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click_1"/>
</Grid>
</Window>
En el code behind creamos un método que manejará los ítems de errores actuales para así controlar la respuesta de nuestro sistema.
Class MainWindow
Dim vgNoOfErrorsOnScreen As Integer = 0
Private Sub Validation_Error(sender As Object, e As ValidationErrorEventArgs)
If e.Action = ValidationErrorEventAction.Added Then
vgNoOfErrorsOnScreen += 1
Else
vgNoOfErrorsOnScreen -= 1
End If
End Sub
Private Sub Window_Loaded_1(sender As Object, e As RoutedEventArgs)
Me.dtpFecha.SelectedDate = DateTime.Now
End Sub
Private Sub Button_Click_1(sender As Object, e As RoutedEventArgs)
If vgNoOfErrorsOnScreen <= 0 Then
MessageBox.Show("Bienvenido al sistema")
Else
MessageBox.Show("Existen errores favor verificar")
End If
End Sub
End Class
Si deseas probar el código listado, puedes encontrar un zip con la solución completa para Visual Studio 2012.
Espero les sea de ayuda.