Hero

Cómo crear un ValidationSummary en WPF

Octubre 03, 2013

aisen
Microsoft
.Net
Programación
VB
WPF

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

  1. 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”
  1. 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.

validation

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

Proyecto

Espero les sea de ayuda.

Recibe consejos y oportunidades de trabajo 100% remotas y en dólares de weKnow Inc.