Validación de datos de entrada con enlace a datos en WPF

      

Hola, qué tal.

Ya que he venido hablando del enlace a datos en WPF en las publicaciones anteriores, bien convendría considerar el uso del Binding para validar datos, hemos tocado ya algunos puntos necesarios para escribir esta funcionalidad. En las publicaciones anteriores vimos ya como enlazar los datos, además, cómo convertir los datos, ahora, sería bueno que ya teniendo el enlace a datos, que de una buena vez se validaran las entradas de los datos.

Comúnmente, cuando se validan los datos de entrada en nuestras aplicaciones, llegamos a utilizar  los eventos LostFocus o KeyPress de los controles donde se escriben los datos por el usuario o también puede ser que se escriba código destinado para la validación de las entradas en el evento de algún botón destinado para aceptar o confirmar los datos. La finalidad de validar los datos es principalmente para evitar errores en tiempo de ejecución o datos inconsistentes en las bases de datos, dado que los datos son en la mayoría de los casos, introducidos como cadenas de caracteres, pues es mejor validar las entradas para estar seguros de que se van a poder convertir o transformar en los tipos de datos que utiliza nuestra aplicación para realizar sus tareas, o bien, que se puedan convertir las entradas en tipos de datos compatibles con los tipos de datos de una base de datos. La validación de entradas también tiene como propósito validar el formato de los datos introducidos por el usuario. En resumen, si se requiere que el usuario ingrese un número, validamos que lo que haya escrito en el control de entrada sea un número, o también, si se requiere un cadena de caracteres, por ejemplo, una dirección de correo electrónico, se valide que la cadena de caracteres de entrada tenga el formato correcto, congruente con una dirección de correo electrónico. Podemos pensar también en validar una fecha, aunque tenemos el control DatePicker que evita que el usuario se equivoque al introducir una fecha y no solo eso, también expone la funcionalidad para seleccionar una fecha en un calendario, pero, la selección es arbitraria y se podrían obtener entradas de fechas inconsistentes en nuestra aplicación, tal es el caso de requerir una fecha que deba ser posterior al día de hoy y que se introduzca una fecha del pasado o requerir fechas que no sean más antiguas que este siglo y que se introduzcan mal, etc.

Bien, para ilustrar el uso de la validación, primeramente crearemos una ventana con algunos campos, como la que se muestra a continuación:

<Window x:Class="EjemplosWPF.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:conv ="clr-namespace:EjemplosWPF"

        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>

        <conv:ConvertByteArrayToBitmapImage x:Key="ByteArrayToBitmapImage"/>

    </Window.Resources>

    <Grid>

        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"

                    Orientation="Horizontal">

            <StackPanel>

                <Image x:Name="imgFoto" Width="100" Height="100" Margin="5"

                    Source="{Binding Foto, UpdateSourceTrigger=PropertyChanged,

                            Mode=TwoWay,

                            Converter={StaticResource ByteArrayToBitmapImage}}"/>

                <Button x:Name="btnFoto" Width="100" Height="25"

                    Content="Agregar Foto" Margin="5" Click="btnFoto_Click"/>

            </StackPanel>

            <StackPanel Margin="5">

                <StackPanel Orientation="Horizontal" Height="30">

                    <TextBlock Text="Nombre:" Margin="5" Width="60"/>

                    <TextBox x:Name="txtNombre" Width="150" Margin="5"

                             Text="{Binding Nombre}"/>

                </StackPanel>

                <StackPanel Orientation="Horizontal" Height="30">

                    <TextBlock Text="Apellidos:" Margin="5" Width="60"/>

                    <TextBox x:Name="txtApellidos" Width="150" Margin="5"

                             Text="{Binding Apellidos}"/>

                </StackPanel>

                <StackPanel Orientation="Horizontal" Height="30">

                    <TextBlock Text="Correo E:" Margin="5" Width="60"/>

                    <TextBox x:Name="txtCorreoEletronico" Width="150" Margin="5"

                             Text="{Binding CorreoElectronico}"/>

                </StackPanel>

                <StackPanel Orientation="Horizontal" Height="30">

                    <TextBlock Text="Número:" Margin="5" Width="60"/>

                    <TextBox x:Name="txtNumero" Width="150" Margin="5"

                             Text="{Binding Numero}"/>

                </StackPanel>               

            </StackPanel>

        </StackPanel>

        <Button x:Name="btnAceptar" Content="Aceptar"

                HorizontalAlignment="Right" VerticalAlignment="Bottom"

                Margin="10" Width="75"/>

    </Grid>

</Window>

 

Podemos observar en el código XAML de arriba, que se está incluyendo la declaración del recurso para la conversión de mapas de bits (para uso de converter ver: http://bit.ly/Lu8Eft) que se está usando en el enlace a datos de la propiedad Source del control imgFoto, además ya se tienen los enlaces a datos en los controles TextBox (para uso de enlaces a datos ver: http://bit.ly/JmGKq8). Además hay un botón, el botón btnAceptar, que para este ejemplo no tendrá ninguna funcionalidad adicional que estar ahí, es solo para ejemplificar un elemento que podría tener funcionalidad para alguna tarea específica con los datos, como algún cálculo o para guardar información en una base de datos. El código de la ventana inicialmente está como sigue:

using System;

using Microsoft.Win32;

using System.IO;

using System.Globalization;

using System.ComponentModel;

using System.Windows.Media.Imaging;

using System.Windows;

using System.Windows.Media;

using System.Windows.Data;

 

namespace EjemplosWPF

{

    public partial class MainWindow : Window

    {

        Empleado empleado;

        public MainWindow()

        {

            InitializeComponent();

            empleado = new Empleado();

            DataContext = empleado;

        }

 

        private void btnFoto_Click(object sender, RoutedEventArgs e)

        {

            if (imgFoto.Source == null)

            {

                OpenFileDialog openFile = new OpenFileDialog();

                BitmapImage b = new BitmapImage();

                openFile.Title = "Seleccione la Imagen a Mostrar";

                openFile.Filter = "Todos(*.*)|*.*|Imagenes|*.jpg;*.gif;*.png;*.bmp";

                if (openFile.ShowDialog() == true)

                {

                    if (new FileInfo(openFile.FileName).Length > 131072)

                    {

                        MessageBox.Show(

                            "El tamaño máximo permitido de la imagen es de 128 KB",

                            "Mensaje de Sistema",

                        MessageBoxButton.OK,

                        MessageBoxImage.Warning,

                        MessageBoxResult.OK);

                        return;

                    }

 

                    b.BeginInit();

                    b.UriSource = new Uri(openFile.FileName);

                    b.EndInit();

                    imgFoto.Stretch = Stretch.Fill;

                    imgFoto.Source = b;

 

                    btnFoto.Content = "Quitar Foto";

                }

            }

            else

            {

                imgFoto.Source = null;

                btnFoto.Content = "Agregar Foto";

            }

        }

    }

}

 

Podemos observar que al inicio de la clase se incluye la declaración de la variable empleado que es del tipo Empleado, en el constructor de la ventana incluimos también la instanciación de la clase Empleado en el objeto empleado y la asignación de la propiedad DataContext de la ventana con el objeto empleado, que es el objeto de origen de enlace a datos que usará la ventana para mostrar y obtener información del usuario. La declaración inicial de la clase Empleado es la siguiente:

public class Empleado : INotifyPropertyChanged

{

    byte[] foto;

    string nombre;

    string apellidos;

    string correoElectronico;

    int numero;

 

    public event PropertyChangedEventHandler PropertyChanged;

 

    public Empleado()

    {

        InitializeVars();

    }

 

    private void InitializeVars()

    {

        foto = null;

        nombre = "";

        apellidos = "";

        correoElectronico = "";

        numero = 0;

    }

 

    public byte[] Foto

    {

        get

        {

            return foto;

        }

        set

        {

            foto = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs("Foto"));

        }

    }

 

    public string Nombre

    {

        get

        {

            return nombre;

        }

        set

        {

            nombre = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs("Nombre"));

        }

    }

 

    public string Apellidos

    {

        get

        {

            return apellidos;

        }

        set

        {

            apellidos = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs("Apellidos"));

        }

    }

 

    public string CorreoElectronico

    {

        get

        {

            return correoElectronico;

        }

        set

        {

            correoElectronico = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs("CorreoElectronico"));

        }

    }

 

    public int Numero

    {

        get

        {

            return numero;

        }

        set

        {

            numero = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs("Numero"));

        }

    }

}

 

Para propósitos del ejemplo, esta clase se puede declarar enseguida de la clase de la ventana, dentro del mismo namespace del ejemplo.

La declaración de esta clase ya está conformada para responder a los cambios en los controles con los que se enlazará, como vimos en publicaciones anteriores (para ver por qué se declaró así esta clase ver: http://bit.ly/JmGKq8).

Ahora procedemos a identificar las validaciones que podrían suscitarse en la ejecución de la aplicación.

Consideraremos dos aspectos en los textos, uno es que si es requerido debe contener una cadena de caracteres, o bien, no estar vacío. En nuestro caso, consideremos que todos los datos son requeridos. Además, si consideramos que los datos podrían ir a una base de datos, también debemos tener en cuenta la longitud de la cadena, lo cual consideraremos también.

En el caso del correo electrónico, tendremos en cuenta el formato del texto, validando que sea un correo electrónico con formato válido.

En el caso del número de empleado, no utilizaremos un converter, la clase Binding implementa la conversión internamente para estos tipos de datos, pero, el enlace a datos no funcionará si el usuario introduce texto que no pueda ser convertido en número, aun así, no será necesario validarlo, ya que el enlace a datos lanzará una excepción y esa excepción será la que controlaremos con la clase Binding.

Para la foto, estamos validando la longitud del archivo, entonces, agregaremos una validación para esta condición.

Con esto cubrimos la validación básica de nuestros datos para este ejemplo.

Existen dos técnicas para validar la información en los enlaces a datos, una es utilizando la clase ValidationRule, es una clase abstracta que sirve para derivar clases con funcionalidad específica de validación y la otra es implementando la interfaz IDataErrorInfo en la definición de nuestra clase Empleado. En esta ocasión cubriré la segunda técnica, en lo personal es la que prefiero porque es más simple en la implementación, la información para las validaciones se concentran en la clase que es dónde se definen los datos y tiene estrecha relación con estos. La desventaja de usar IDataErrorInfo es que hay que declarar el bloque de validaciones para cada clase, si hay validaciones que podrían ser reutilizadas, se tendrán que escribir de nuevo de clase en clase. La técnica utilizando ValidationRule requiere un poco más de código, pero al final puede reutilizarse ya que dejan las clases derivadas disponibles para seguir usandolas, pero, al realizar la de declaración de los enlaces, siempre requerirá más código que al utilizar IDataErrorInfo, además, con el uso de IDataErrorInfo, tenemos mecanismos que nos permiten saber si la clase tiene datos válidos o no, ya que está en comunión la clase y sus datos, y con el uso de ValidationRule no es posible, esto es porque el uso de ValidationRule está orientado a la reutilización de las clases derivadas, funciona de forma independiente a nuestra clase y no proporciona un mecanismo simple para identificar el estado de nuestra clase, esto es, si es tiene datos válidos o no.

Pues bien, veamos cómo es la técnica de IDataErrorInfo.

Primero, agreguemos en la declaración de la clase Empleado, la declaración de la implementación de IDataErrorInfo:

public class Empleado : INotifyPropertyChanged, IDataErrorInfo

 

A continuación, implementemos los miembros de la interfaz de forma implícita declarando los siguientes miembros en nuestra clase Empleado:

public string Error

{

    get

    {

        return null;

    }

}

 

public string this[string propertyName]

{

    get

    {

        return IsValid(propertyName);

    }

}

 

Tenemos la propiedad de solo lectura: Error, que regresa un null. Esta propiedad puede ser implementada para obtener el mensaje de error con la descripción de lo que le pasa al objeto empleado. También tenemos un indizador, el cual obtiene el mensaje de error correspondiente a la propiedad con el nombre especificado en propertyName. En esta última declaración podemos notar el uso del método IsValid, este es le método que contiene las validaciones y devuelve una cadena de caracteres con el resultado de la validación, esto es, si la validación de la propiedad resulta negativa se obtiene el texto con la descripción del error, de lo contrario se obtiene una cadena vacía o null. La declaración del método IsValid será la siguiente:

private string IsValid(string propertyName)

{

    switch (propertyName)

    {

        case "Foto":

                if(Foto != null && 131072 < Foto.Length)

                    return "El campo Foto no debe " +

                        "ser mayor a 128 Kbytes";

            break;

        case "Nombre":

            if (string.IsNullOrWhiteSpace(Nombre))

                return "El campo Nombre es requerido";

            else if (80 < Nombre.Length)

                return "El campo Nombre no debe " +

                    "contener más de 150 caracteres";

            break;

        case "Apellidos":

            if (string.IsNullOrWhiteSpace(Apellidos))

                return "El campo Apellidos es requerido";

            else if (150 < Apellidos.Length)

                return "El campo Apellidos no debe " +

                    "contener más de 150 caracteres";

            break;

        case "CorreoElectronico":

            if (string.IsNullOrWhiteSpace(CorreoElectronico))

                return "El campo Correo Electrónico es requerido";

            else if (100 < CorreoElectronico.Length)

                return "El campo Correo Electrónico no debe "

                    + "contener más de 100 caracteres";

            else

            {

                Regex regEx;

                regEx = new Regex(@"^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*\." +

                    @"(\w{2}|(com|net|org|edu|int|mil|gov|arpa|" +

                    @"biz|aero|name|coop|info|pro|museum))$");

                if(!regEx.IsMatch(CorreoElectronico))

                    return "El campo Correo Electrónico no " +

                        "tiene un formato correcto";

            }

            break;

        case "Numero":

            if (Numero <= 0)

                return "El campo Número debe ser mayor que cero";

            break;

    }

    return null;

}

 

Podemos notar que utilizamos una estructura switch en conjunto con el parámetro propertyName del método. Las validaciones no son nada especial o que no hayamos hecho antes, simplemente estamos creando un bloque de código con las validaciones correspondientes a cada propiedad.  Podemos validar los datos contra cualquier condición que sea necesaria, particular o general, dándole a nuestro objeto cierta inteligencia para evitar errores en el manejo de los datos, esta es una de las ventajas más apreciables de esta técnica. Cabe mencionar que estoy utilizando la clase Regex, por lo que debemos incluir la siguiente declaración al principio del archivo de clase, en el bloque de declaraciones using:

using System.Text.RegularExpressions;

 

El método IsValid que se muestra más arriba, devuelve un string y tiene alcance private, por lo que no podrá ser expuesto a través de las instancias de la clase Empleado, tendremos que declarar un método que pueda devolver un valor booleano que nos deje saber en qué estado se encuentra nuestro objeto, es decir, si es válido o no. Para esto declaramos el siguiente método:

public bool IsValid()

{

    return string.IsNullOrEmpty(IsValid("Foto"))

        && string.IsNullOrEmpty(IsValid("Nombre"))

        && string.IsNullOrEmpty(IsValid("Apellidos"))

        && string.IsNullOrEmpty(IsValid("CorreoElectronico"))

        && string.IsNullOrEmpty(IsValid("Numero"));

}

 

Este método invoca al método privado IsValid para determinar si hay alguna propiedad que no esté cumpliendo con la validación.

Continuando, debemos ahora modificar las declaraciones de los enlaces a datos, asignaremos algunas propiedades más de la clase Binding para que puedan manejarse las validaciones por completo. Primeramente, incluir la propiedad UpdateSourceTrigger asignada con el valor PropertyChanged, la cual está relacionada directamente con la interfaz INotifyPropertyChanged implementada en nuestra clase. La otra propiedad que incluiremos, está relacionada directamente con la interfaz IDataErrorInfo, esta es ValidatesOnDataError, la cual es del tipo bool y que tiene como valor predeterminado el valor false por lo que le asignaremos el valor true para habilitar la validación cuando sucedan errores con los datos, de esta manera utilizará la implementación de la de la interfaz IDataErrorInfo para realizar las validaciones que nosotros definimos. Otra propiedad que utilizaremos en casos específicos es ValidatesOnExceptions que es del tipo bool y que tiene como valor predeterminado el valor false, la asignaremos el valor true para habilitar la validación de excepciones provocados por el enlace a datos. La propiedad ValidatesOnExceptions se utiliza para obtener información de excepciones que ocurren en la conversión de tipos que realiza Binding en los enlaces a datos, en nuestro caso específico sería al validar el número de empleado, ya que la entrada será por texto, el usuario podría pasar valores que no son números. Aunque la validación funciona de todas maneras sin esta última propiedad, la ventaja de usarla es que provee información de la excepción al usuario.

Para proveer información al usuario del error de validación, incluiremos la declaración de un estilo para los controles TextBox, este estilo pasará el primer valor del error contenido en el la colección Errors de la propiedad Validation del control en caso de que la propiedad HasError de Validation sea true. La descripción del error la mostraremos en el ToolTip del control. La declaración de este estilo estará en la sección Window.Resourses de nuestra ventana. El estilo es el siguiente:

<Style x:Key="textBoxInError" TargetType="TextBox">

    <Style.Triggers>

        <Trigger Property="Validation.HasError" Value="true">

            <Setter Property="ToolTip"

                Value="{Binding RelativeSource={x:Static RelativeSource.Self},

                Path=(Validation.Errors)[0].ErrorContent}"/>

        </Trigger>

    </Style.Triggers>

</Style>

 

Ya que tenemos este estilo, procederemos a realizar los cambios correspondientes en nuestro código XAML de la ventana, quedando como a continuación se muestra:

<Window x:Class="EjemplosWPF.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:conv ="clr-namespace:EjemplosWPF"

        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>

        <conv:ConvertByteArrayToBitmapImage x:Key="ByteArrayToBitmapImage"/>

        <Style x:Key="textBoxInError" TargetType="TextBox">

            <Style.Triggers>

                <Trigger Property="Validation.HasError" Value="true">

                    <Setter Property="ToolTip"

                        Value="{Binding RelativeSource={

                        x:Static RelativeSource.Self},

                        Path=(Validation.Errors)[0].ErrorContent}"/>

                </Trigger>

            </Style.Triggers>

        </Style>

        <Style x:Key="imageInError" TargetType="Image">

            <Style.Triggers>

                <Trigger Property="Validation.HasError" Value="true">

                    <Setter Property="ToolTip"

                        Value="{Binding RelativeSource={

                        x:Static RelativeSource.Self},

                        Path=(Validation.Errors)[0].ErrorContent}"/>

                </Trigger>

            </Style.Triggers>

        </Style>

    </Window.Resources>

    <Grid>

        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"

                    Orientation="Horizontal">

            <StackPanel>

                <Image x:Name="imgFoto" Width="100" Height="100" Margin="5"

                       Style="{StaticResource imageInError}"

                       Source="{Binding Foto, Mode=TwoWay,

                            UpdateSourceTrigger=PropertyChanged,                            

                            ValidatesOnDataErrors=True,

                            ValidatesOnExceptions=True,

                            Converter={StaticResource ByteArrayToBitmapImage}}"/>

                <Button x:Name="btnFoto" Width="100" Height="25"

                    Content="Agregar Foto" Margin="5" Click="btnFoto_Click"/>

            </StackPanel>

            <StackPanel Margin="5">

                <StackPanel Orientation="Horizontal" Height="30">

                    <TextBlock Text="Nombre:" Margin="5" Width="60"/>

                    <TextBox x:Name="txtNombre" Width="150" Margin="5"

                            Style="{StaticResource textBoxInError}"

                            Text="{Binding Nombre,

                                   UpdateSourceTrigger=PropertyChanged,

                                   ValidatesOnDataErrors=True}"/>

                </StackPanel>

                <StackPanel Orientation="Horizontal" Height="30">

                    <TextBlock Text="Apellidos:" Margin="5" Width="60"/>

                    <TextBox x:Name="txtApellidos" Width="150" Margin="5"

                             Style="{StaticResource textBoxInError}"

                             Text="{Binding Apellidos,

                                   UpdateSourceTrigger=PropertyChanged,

                                   ValidatesOnDataErrors=True}"/>

                </StackPanel>

                <StackPanel Orientation="Horizontal" Height="30">

                    <TextBlock Text="Correo E:" Margin="5" Width="60"/>

                    <TextBox x:Name="txtCorreoEletronico" Width="150" Margin="5"

                             Style="{StaticResource textBoxInError}"

                             Text="{Binding CorreoElectronico,

                                   UpdateSourceTrigger=PropertyChanged,

                                   ValidatesOnDataErrors=True}"/>

                </StackPanel>

                <StackPanel Orientation="Horizontal" Height="30">

                    <TextBlock Text="Número:" Margin="5" Width="60" />

                    <TextBox x:Name="txtNumero" Width="150" Margin="5"

                             Style="{StaticResource textBoxInError}"

                             Text="{Binding Numero,

                                   UpdateSourceTrigger=PropertyChanged,

                                   ValidatesOnDataErrors=True,

                                   ValidatesOnExceptions=True}"/>

                </StackPanel>               

            </StackPanel>

        </StackPanel>

        <Button x:Name="btnAceptar" Content="Aceptar"

                HorizontalAlignment="Right" VerticalAlignment="Bottom"

                Margin="10" Width="75"/>

    </Grid>

</Window>

 

Con esto, la ventana a quedado completa, sin embargo, queremos sacar provecho de la validación y queremos que el usuario no continúe si los datos no son correctos, haremos las modificaciones correspondientes en el código de la clase de la ventana.

Primeramente, agregamos un manejador para el evento PropertyChanged de nuestra clase, en el cual, dependiendo del estado de nuestra clase, se habilitará o deshabilitará el botón btnAceptar, evitando que se utilice si los datos del objeto empleado no son válidos. El método sería como lo siguiente:

private void empleado_PropertyChanged(object sender, PropertyChangedEventArgs e)

{

    btnAceptar.IsEnabled = empleado.IsValid();

}

 

Notamos aquí que estamos utilizando el método público IsValid para determinar si los datos de nuestra clase son válidos y de esta manera, con el valor devuelto, se habilita o deshabilita el botón btnAceptar.

Ahora bien, debemos agregar el método controlador del evento PropertyChanged al objeto empleado, lo cual haremos inmediatamente después de haberlo instanciado y esto será en el constructor de la ventana, quedando como sigue:

public MainWindow()

{

    InitializeComponent();

    empleado = new Empleado();

    empleado.PropertyChanged +=

        new PropertyChangedEventHandler(empleado_PropertyChanged);

    DataContext = empleado;

}

 

También, como ya hemos incluido la validación del tamaño de la imagen en la clase Empleado, podemos prescindir de la validación que hacíamos al cargar el archivo de imagen, modificando el código del botón btnFoto, quedando como sigue:

private void btnFoto_Click(object sender, RoutedEventArgs e)

{

    if (imgFoto.Source == null)

    {

        OpenFileDialog openFile = new OpenFileDialog();

        BitmapImage b = new BitmapImage();

        openFile.Title = "Seleccione la Imagen a Mostrar";

        openFile.Filter = "Todos(*.*)|*.*|Imagenes|*.jpg;*.gif;*.png;*.bmp";

        if (openFile.ShowDialog() == true)

        {

            b.BeginInit();

            b.UriSource = new Uri(openFile.FileName);

            b.EndInit();

            imgFoto.Stretch = Stretch.Fill;

            imgFoto.Source = b;

 

            btnFoto.Content = "Quitar Foto";

        }

    }

    else

    {

        imgFoto.Source = null;

        btnFoto.Content = "Agregar Foto";

    }

}

 

Con esto, hemos quitado unas líneas más de código y concentramos la validación de datos en el lugar dónde sucede, en el enlace a datos. El código final de la clase de la ventana es el siguiente:

public partial class MainWindow : Window

{

    Empleado empleado;

    public MainWindow()

    {

        InitializeComponent();

        empleado = new Empleado();

        empleado.PropertyChanged +=

            new PropertyChangedEventHandler(empleado_PropertyChanged);

        DataContext = empleado;

    }

 

    private void empleado_PropertyChanged(object sender,

        PropertyChangedEventArgs e)

    {

        btnAceptar.IsEnabled = empleado.IsValid();

    }

 

    private void btnFoto_Click(object sender, RoutedEventArgs e)

    {

        if (imgFoto.Source == null)

        {

            OpenFileDialog openFile = new OpenFileDialog();

            BitmapImage b = new BitmapImage();

            openFile.Title = "Seleccione la Imagen a Mostrar";

            openFile.Filter = "Todos(*.*)|*.*|Imagenes|*.jpg;*.gif;*.png;*.bmp";

            if (openFile.ShowDialog() == true)

            {

                b.BeginInit();

                b.UriSource = new Uri(openFile.FileName);

                b.EndInit();

                imgFoto.Stretch = Stretch.Fill;

                imgFoto.Source = b;

 

                btnFoto.Content = "Quitar Foto";

            }

        }

        else

        {

            imgFoto.Source = null;

            btnFoto.Content = "Agregar Foto";

        }

    }

}

 

Podemos ver un código más simple y limpio en la interfaz de usuario, orientado exclusivamente al manejo de la interfaz de usuario. El código final de la clase Empleado es el siguiente:

public class Empleado : INotifyPropertyChanged, IDataErrorInfo

{

    byte[] foto;

    string nombre;

    string apellidos;

    string correoElectronico;

    int numero;

    public event PropertyChangedEventHandler PropertyChanged;

 

    public Empleado()

    {

        InitializeVars();

    }

 

    private void InitializeVars()

    {

        foto = null;

        nombre = "";

        apellidos = "";

        correoElectronico = "";

        numero = 0;

    }

    private string IsValid(string propertyName)

    {

        switch (propertyName)

        {

            case "Foto":

                if (Foto != null && 131072 < Foto.Length)

                    return "El campo Foto no debe " +

                        "ser mayor a 128 Kbytes";

                break;

            case "Nombre":

                if (string.IsNullOrWhiteSpace(Nombre))

                    return "El campo Nombre es requerido";

                else if (80 < Nombre.Length)

                    return "El campo Nombre no debe " +

                        "contener más de 150 caracteres";

                break;

            case "Apellidos":

                if (string.IsNullOrWhiteSpace(Apellidos))

                    return "El campo Apellidos es requerido";

                else if (150 < Apellidos.Length)

                    return "El campo Apellidos no debe " +

                        "contener más de 150 caracteres";

                break;

            case "CorreoElectronico":

                if (string.IsNullOrWhiteSpace(CorreoElectronico))

                    return "El campo CorreoElectronico es requerido";

                else if (100 < CorreoElectronico.Length)

                    return "El campo CorreoElectronico no debe "

                        + "contener más de 100 caracteres";

                else

                {

                    Regex regEx;

                    regEx = new Regex(@"^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*\." +

                        @"(\w{2}|(com|net|org|edu|int|mil|gov|arpa|" +

                        @"biz|aero|name|coop|info|pro|museum))$");

                    if (!regEx.IsMatch(CorreoElectronico))

                        return "El campo CorreoElectronico no " +

                            "tiene un formato correcto";

                }

                break;

            case "Numero":

                if (Numero <= 0)

                    return "El campo Numero debe ser mayor que cero";

                break;

        }

        return null;

    }

    public bool IsValid()

    {

        return string.IsNullOrEmpty(IsValid("Foto"))

            && string.IsNullOrEmpty(IsValid("Nombre"))

            && string.IsNullOrEmpty(IsValid("Apellidos"))

            && string.IsNullOrEmpty(IsValid("CorreoElectronico"))

            && string.IsNullOrEmpty(IsValid("Numero"));

    }

 

    public string Error

    {

        get

        {

            return null;

        }

    }

    public string this[string propertyName]

    {

        get

        {

            return IsValid(propertyName);

        }

    }

 

    public byte[] Foto

    {

        get

        {

            return foto;

        }

        set

        {

            foto = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs("Foto"));

        }

    }

    public string Nombre

    {

        get

        {

            return nombre;

        }

        set

        {

            nombre = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs("Nombre"));

        }

    }

    public string Apellidos

    {

        get

        {

            return apellidos;

        }

        set

        {

            apellidos = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs("Apellidos"));

        }

    }

    public string CorreoElectronico

    {

        get

        {

            return correoElectronico;

        }

        set

        {

            correoElectronico = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs("CorreoElectronico"));

        }

    }

    public int Numero

    {

        get

        {

            return numero;

        }

        set

        {

            numero = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs("Numero"));

        }

    }

}

 

Con esto queda terminado el ejemplo dejando al aire un cuestionamiento sobre la validación de los datos. De acuerdo a las buenas prácticas de arquitectura de software que nos dicen que la validación de los datos debe hacerse en la interfaz de usuario y no en los componentes de datos la pregunta que surge es ¿estamos haciendo malas prácticas de arquitectura con esta técnica de validación?, la respuesta es NO, y es por una razón muy simple pero no tan obvia, la validación no se hace en el objeto empleado, se hace en la interfaz de usuario, en específico, en el enlace a datos con los mecanismos que ofrece para esta tarea la clase Binding, lo único que estamos haciendo con esta técnica, y es parte de una buena práctica, es describir los datos de nuestra clase y a su vez, exponer las condiciones que deben cumplir estos datos para tener un objeto con datos válidos. En contraste, una mala práctica sería realizar las validaciones directamente en las propiedades de la clase Empleado. Así pues, quedémonos tranquilos, que la realidad es que la validación de los datos se sigue haciendo en la interfaz de usuario con información que provee nuestra clase Empleado a través del objeto empleado.

Lo único que restaría es conectar el objeto empleado a una base de datos, pero esa es otra historia.

Espero que te sea de utilidad y nos estamos leyendo en la próxima.

Octavio Telis

Published Sun, May 27 2012 12:10 by Octavio Telis Aynés
Filed under: , , ,

Comments

# re: Validación de datos de entrada con enlace a datos en WPF

Thursday, December 27, 2012 11:54 AM by Daniel

Octavio, gracias por este artículo porque de verdad que ha sido de provecho. Gracias por el tiempo que te has tomado. Saludos.

Leave a Comment

(required) 
(required) 
(optional)
(required) 
If you can't read this number refresh your screen
Enter the numbers above:  
Powered by Community Server (Commercial Edition), by Telligent Systems