Validación en WPF


Como ya les había mencionado en el artículo anterior de aquí en adelante vamos a ver la maravillosa propiedad Binding de WPF (Windows Presentation Foundation). En este artículo se supone que el lector tendrá conocimiento básico de cómo hacer un Bind entre una propiedad y un control. Uno de los mayores dolores en WPF es la validación de datos. Un error frecuente en la validación es el siguiente.

Como pueden ver en la imagen, la validación de los campos se hace desde el principio de la aplicación haciendo que se vea “feo”. Qué se puede hacer para que lo visto en la imagen no ocurra. Aquí se da una propuesta para solucionar este error, pero en este artículo expondré la forma que yo he utilizado.

Manos a la obra!

Ya les he mostrado la Vista (View) del proyecto, pero, antes de entrar a ver el código xaml miremos nuestro Modelo (Model) y nuestra VistaModelo (ViewModel).

Nota: he usado el patrón MVVM para realizar este proyecto aunque no he seguido todos los reglamentos.

Modelo

public class UsuarioModel : INotifyPropertyChanged, IDataErrorInfo
{

	private string correo;
	public string Correo
	{
		get
		{
			return correo;
		}
		set
		{
			if ( correo == value ) return;

			correo = value;
			RaisePropertyChanged ( "Correo" );
		}
	}

	private string contraseña;
	public string Contraseña
	{
		get
		{
			return contraseña;
		}
		set
		{
			if ( contraseña == value ) return;

			contraseña = value;
			RaisePropertyChanged ( "Contraseña" );
		}
	}
}

Como vemos, nuestro modelo hereda de las interfaces que nos ayudan a saber cuando un valor es cambiado (INotifyPropertyChanged) y la que nos permite mostrar información de error personalizada en nuestra interfaz (IDataErrorInfo). El código del primero no lo voy a poner aquí pues es muy sencillo pero el del segundo es el que nos importa, así que aquí va:

private bool validarCorreo = false;
private bool validarContraseña = false;
public string Error
{
	get { throw new NotImplementedException (); }
}
public string this[string columnName]
{
	get
	{
		string result = null;

		if ( columnName.Equals ( "Correo" ) )
		{
			if ( validarCorreo )
			{
				if ( String.IsNullOrEmpty ( Correo ) )
				{
					result = "El correo no puede estar en blanco";
				}

				if ( Correo != null && !Regex.IsMatch ( Correo, "^[\\w-]+(\\.[\\w-]+)*@([a-z0-9-]+(\\.[a-z0-9-]+)*?\\.[a-z]{2,6}|(\\d{1,3}\\.){3}\\d{1,3})(:\\d{4})?$", RegexOptions.IgnoreCase ) && result == null )
				{
					result = "Digite un correo válido";
				}
			}
			else
			{
				validarCorreo = !validarCorreo;
			}
		}

		else if ( columnName.Equals ( "Contraseña" ) )
		{
			if ( validarContraseña )
			{
				if ( String.IsNullOrEmpty ( Contraseña ) )
				{
					result = "La contraseña no puede estar en blanco";
				}
			}
			else
			{
				validarContraseña = !validarContraseña;
			}
		}

		return result;
	}
}

Es bastante sencillo y muy común el planteamiento que tengo para los errores, el único cambio que he hecho es el de agregar dos variables bool las cuales no permitirán que valide las variables al iniciarse la Ventana.

Para nuestro ViewModel vamos a hacer algo simple, aunque vamos a usar algo que es muy necesario en el patrón MVVM y son los comandos (Command)

VistaModelo

public class UsuarioViewModel
{
	public UsuarioViewModel ()
	{
		Usuario = new UsuarioModel ();
	}

	public UsuarioModel UsuarioLogeado { get; set; }
	public UsuarioModel Usuario { get; set; }

	RelayCommand command;
	public ICommand Ingresar
	{
		get
		{
			if ( command == null )
			{
				command = new RelayCommand ( ingresarCommand, canIngresar );
			}
			return command;
		}
	}

	private void ingresarCommand ( object parameter )
	{
		UsuarioLogeado = new UsuarioModel ();
		UsuarioModel usuario = parameter as UsuarioModel;

		UsuarioLogeado.Contraseña = usuario.Contraseña;
		UsuarioLogeado.Correo = usuario.Correo;
	}

	private bool canIngresar ( object parameter )
	{
		UsuarioModel usuario = parameter as UsuarioModel;
		if ( usuario != null )
		{
			if ( !string.IsNullOrEmpty(usuario.Contraseña) && !string.IsNullOrEmpty(usuario.Correo) && Regex.IsMatch(usuario.Correo, "^[\\w-]+(\\.[\\w-]+)*@([a-z0-9-]+(\\.[a-z0-9-]+)*?\\.[a-z]{2,6}|(\\d{1,3}\\.){3}\\d{1,3})(:\\d{4})?$", RegexOptions.IgnoreCase))
			{
				return true;
			}
		}
		return false;
	}
}

Hemos creado una propiedad llamada Usuario del tipo UsuarioModel además un comando llamado Ingresar el cual es un RelayCommand. Cabe notar que el paramtero que le llega a ingresarCommand y a canIngresar vienen de un Multi-Convertidor de Valores.

Por último ha llegado la hora de ver el código xaml de nuestra ventana.

<Window x:Class="Validacion.UsuarioView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Validacion"
        Title="Usuario" Height="240" Width="300">

	<Window.Resources>
        <local:UsuarioViewModel x:Key="UsuarioViewModel"/>
        <local:UsuarioConverter x:Key="Convertidor"/>
        <Style TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="true">
                    <Setter Property="ToolTip"
                Value="{Binding RelativeSource={RelativeSource Self},
                       Path=(Validation.Errors)[0].ErrorContent}"/>
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style x:Key="BienvenidoStyle" TargetType="TextBlock">
            <Style.Triggers>
                <DataTrigger Binding="{Binding UsuarioLogeado.Correo}" Value="{x:Null}">
                    <Setter Property="Visibility" Value="Hidden"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

    <StackPanel DataContext="{Binding Source={StaticResource UsuarioViewModel}}">
        <StackPanel Orientation="Horizontal" Width="160" Margin="0,20,0,0">
            <TextBlock Text="Correo:" Width="40" Height="20" Margin="0,5,0,0"/>
            <TextBox Name="Correo" Width="100" Height="25" Margin="10,0,0,0"
                     Text="{Binding Path=Usuario.Correo, ValidatesOnDataErrors=True, ValidatesOnExceptions=True,
                            UpdateSourceTrigger=PropertyChanged}"/>
        </StackPanel>
        <TextBlock Text="{Binding ElementName=Correo, Path=(Validation.Errors)[0].ErrorContent}"
                   Foreground="Red" Width="190" TextAlignment="Center"/>
        <StackPanel Orientation="Horizontal" Width="180" Margin="0,30,0,0">
            <TextBlock Text="Contraseña:" Width="60" Height="20" Margin="0,5,0,0"/>
            <TextBox Name="Contraseña" Width="100" Height="25" Margin="10,0,0,0"
                     Text="{Binding Path=Usuario.Contraseña, ValidatesOnDataErrors=True, ValidatesOnExceptions=True,
                            UpdateSourceTrigger=PropertyChanged}"/>
            <!--Se usa un TextBlock solo como un ejemplo, con respecto a como tratar un campo contraseña usando
                MVVM se recomienda una lectura corta en el siguiente enlace:
                http://stackoverflow.com/questions/1483892/wpf-binding-to-the-passwordbox-in-mvvm-working-solution-->
        </StackPanel>
        <TextBlock Text="{Binding ElementName=Contraseña, Path=(Validation.Errors)[0].ErrorContent}"
                   Foreground="Red" Width="210" TextAlignment="Center"/>

        <Button Name="Aceptar" Width="100" Margin="0,15,0,0" Command="{Binding Ingresar}">
            <Button.CommandParameter>
                <MultiBinding Converter="{StaticResource Convertidor}" UpdateSourceTrigger="PropertyChanged">
                    <Binding Path="Text" ElementName="Correo"/>
                    <Binding Path="Text" ElementName="Contraseña"/>
                </MultiBinding>
            </Button.CommandParameter>
            Ingresar
        </Button>
        <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
            <TextBlock FontWeight="Bold" Style="{StaticResource BienvenidoStyle}" Text="Bienvenido:" Margin="30,0,0,0"/>
            <TextBlock FontWeight="Bold" Text="{Binding Path=UsuarioLogeado.Correo}" Margin="40,0,0,0"/>
        </StackPanel>
    </StackPanel>
</Window>

Quiero comentarles que he utilizado 3 formas diferentes de validación:

  1. Mostrar un ToolTip con el texto de error cuando se produce uno (líneas 10 a 18).
  2. Mostrar un TextBlock con el texto de error y en color Rojo (líneas 35 y 46).
  3. Inhabilitar el botón usando el canIngresar antes mencionado (esto lo hace automáticamente).

Continuemos con unas pequeñas explicaciones del código.

Línea 28: <StackPanel DataContext=”{Binding Source={StaticResource UsuarioViewModel}}”>

Esta parte nos indica que usaremos un recurso estático que se llama UsuarioViewModel y que hace referencia a nuestra clase ya antes vista.

Línea 32: Text=”{Binding Path=Usuario.Correo…}”>

Aquí hacemos un Bind al campo Correo de la propiedad Usuario. Recordar que en este caso nuestra propieda Usuario es del tipo UsuarioModel.

Línea 35: <TextBlock Text=”{Binding ElementName=Correo, Path=(Validation.Errors)[0].ErrorContent}”…>

Esta línea es muy especial pues en ella hacemos un Bind a un control llamado Correo -nuesto TextBox- usando el ElementName y luego usando el Path (Ruta) hacemos que tome el valor del mensaje de error que proporciona el control.

Línea 49 – 57: El botón

Hacemos un Bind al nombre de nuestro comando (Ingresar) y luego le pasamos como parámetros los valores contenido en el TextBox Correo y Contraseña haciendo uso de nuestro Multi-Convertidor genialmente llamado ‘Convertidor’.

Ya para terminar veamos como queda nuestro proyecto al correrlo. Y claro que no se me olvida dejarles el proyecto para que lo descarguen Validacion.zip. Recuerden cambiar de .odt a .zip.

Que lo disfruten!  Y… A practicar!!

Anuncios

3 Respuestas a “Validación en WPF

  1. sale error al ejecutar me muestra la sig. descripción del error Error 2 La propiedad ‘Content’ está establecida más de una vez. C:\Documents and Settings\ADS Systems\Escritorio\proyectosLV\Validacion\Validacion\View\UsuarioView.xaml 28 5 Validacion

    • Hola
      Le has hecho alguna modificación al código xaml? pues yo lo estoy probando y no me ha salido ese problema. De pronto has definido 2 Content al Button que ahí esta.
      Espero tu respuesta.
      Saludos

  2. Pingback: Wpf | TagHall

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s