.NET Chronicles

Temas relacionados con el desarrollo de aplicaciones con .NET

Octavio Telis

Monthly Archives: May 2012

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

Uso de la propiedad Visibility con valores booleanos en WPF

   

Hola que tal.

En esta ocasión quiero compartir con ustedes una manera para utilizar la propiedad Visibility con un valor bool. Te preguntarás “¿Y qué sentido tiene eso?”, bien, el detalles está en que en ocasiones se requiere que la visibilidad de un control de la interfaz de usuario responda a un valor de true o false.

En Windows Forms, la propiedad Visibility no existe en los controles de interfaz de usuario, sin embargo, existe la propiedad Visible que es del tipo bool. En WPF la propiedad Visibility es del tipo System.Windows.Visibility que es un enumerados con tres miembros a saber; Visible, Hidden y Collapsed. Visible establece que el control estará visible, Hidden establece que el control estará oculto y no visible, pero, conservando la relación de aspecto de donde se encuentra dibujado el control, esto es, que el espacio donde está el control permanece intacto, como si estuviera el control ahí, ocupando su espacio, pero sin mostrarse y Hidden, que por el contrario, oculta el control pero permite que el espacio que ocupa el control pueda ser contraído, liberando espacio visual, esto se puede ver más claramente con controles contenedores como el control StackPanel. En WPF, si quieres ocultar un control en base a un valor booleano no es posible de forma directa, por lo que la asignación de un valor booleano, procedente de una clase de datos o de otro control no es posible sin el uso de una estructura de decisión, por ejemplo, no se puede asignar directamente la propiedad IsChecked de un CheckBox a la propiedad Visibility de un control, para ocultarlo o mostrarlo según sea el valor de la propiedad IsChecked; true o false. Comúnmente, si quisiera mostrar el control desearía pasar un valor true y que se mostrara y si quisiera ocultarlo pasara un valor false y se ocultara, como es el caso de los controles  de Windows Forms.

Para ejemplificar esta situación y cómo lo resolveríamos, vamos a utilizar el ejemplo de la publicación anterior (http://bit.ly/Lu8Eft) pero modificando un poco la interfaz de usuario, la cual quedaría de la siguiente manera:

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

            <CheckBox x:Name=”chkMostrarImagen” Content=”Mostrar Imagen”

                      Margin=”5″ Checked=”chkMostrarImagen_Checked”

                      Unchecked=”chkMostrarImagen_Checked

                      IsChecked=”True”/>

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

                   Source=”{Binding Image, UpdateSourceTrigger=PropertyChanged,

                            Mode=TwoWay,

                            Converter={StaticResource ByteArrayToBitmapImage}}”/>

            <Button x:Name=”btnFoto” Width=”100″ Height=”25″

                    Content=”Agregar Foto” Margin=”5″ Click=”btnFoto_Click”/>

        </StackPanel>

    </Grid>

</Window>

 

Lo único que cambió es que agregué un control CheckBox denominado chkMostrarImagen con la propiedad IsChecked asignada a True. La funcionalidad que se busca debe hacerse utilizando el evento Checked del control chkMostrarImagen, que se lanza cuando la casilla se ha marcado y por otra parte, en el evento Unchecked que se lanza cuando la casilla se ha desmarcado, ambos apuntan al mismo controlador de evento, para no duplicar el esfuerzo, ya que la acción de marcar y desmarcar realizan la misma tarea que es evaluar el valor de la propiedad IsChecked del control CheckBox y de acuerdo al valor de esta propiedad, mostrar u ocultar el control imgFoto. De antemano, podemos percatarnos de que implementar la funcionalidad que se requiere implica escribir código adicional para la interfaz de usuario. El código del controlador de evento para Checked y Unchecked quedaría como sigue:

private void chkMostrarImagen_Checked(object sender, RoutedEventArgs e)

{

    if (imgFoto == null)

        return;

    if (chkMostrarImagen.IsChecked.Value)

        imgFoto.Visibility = System.Windows.Visibility.Visible;

    else

        imgFoto.Visibility = System.Windows.Visibility.Collapsed;

}

 

Hay que comentar algo importante al respecto de este código, al inicio he tenido que agregar una validación para verificar que el control imgFoto no es null, ya que por el árbol visual de elementos de la interfaz de usuario, al inicio de la ejecución, el primero que se crea es el control chkMostrarImagen y al crearse se le asigna la propiedad IsChecked con true porque así lo declaramos inicialmente lo que provoca que se lance el evento Checked cuando aun no se ha creado el control imgFoto. Podríamos evitar esta validación si nosotros enlazamos el evento posteriormente a que se hayan creado todos los elementos de la interfaz de usuario, lo cual es posible realizar en el constructor de la ventana, inmediatamente después del método InitializeComponent, para esto, debemos eliminar la declaración de los eventos Checked y Uncheked de la declaración del control CheckBox. ¿Que cómo sería?, bueno, pues veamos:

La declaración del CheckBox quedaría así:

<CheckBox x:Name=”chkMostrarImagen” Content=”Mostrar Imagen”

            Margin=”5″ IsChecked=”True”/>

 

El constructor en la clase de la ventana quedaría así:

public MainWindow()

{

    InitializeComponent();

    chkMostrarImagen.Checked +=

        new RoutedEventHandler(chkMostrarImagen_Checked);

    chkMostrarImagen.Unchecked +=

        new RoutedEventHandler(chkMostrarImagen_Checked);

    foto = new Foto();

    DataContext = foto;

}

 

El manejador del evento quedaría ahora así:

private void chkMostrarImagen_Checked(object sender, RoutedEventArgs e)

{

    if (chkMostrarImagen.IsChecked.Value)

        imgFoto.Visibility = System.Windows.Visibility.Visible;

    else

        imgFoto.Visibility = System.Windows.Visibility.Collapsed;

}

 

En fin, hemos tenido que escribir más código aún, pero funciona. Aunque, ¿no sería mejor que el propio control pudiera hacer todo esto y que yo no escribiera más código que el necesario? La respuesta por supuesto que debe ser afirmativa, pues es parte de lo que trata este artículo, sin embargo, ¿cómo podríamos hacer esto posible?, bien, la factibilidad recae en usar la clase Binding y crear una clase de conversión (conocidas como Converters) implementando la interfaz IValueConverter. Yo sé que al final dirán, “¿no nos íbamos a librar de escribir el código’”, pues no, pero es verdad que ganamos código que no volveremos a escribir mas que una vez y para siempre, para la siguiente vez que lo utilicen no volverán a escribirlo.

Empecemos por crear el converter, que quedará como lo siguiente:

[ValueConversion(typeof(bool), typeof(Visibility))]

public class ConvertBooleanToVisibilityCollapsed : IValueConverter

{

    public object Convert(object value, Type targetType,

        object parameter, CultureInfo culture)

    {

        bool? res = value as bool?;

        if (res != null && res.HasValue)

        {

            if (res.Value)

                return Visibility.Visible;

            else

                return Visibility.Collapsed;

        }

        return value;

    }

    public object ConvertBack(object value, Type targetType,

        object parameter, CultureInfo culture)

    {

        Visibility res = (Visibility value;

        return res == Visibility.Visible ? true : false;

    }

}

 

La mecánica para la definición de una clase converter se encuentra explicada con más detalle con el ejemplo de la publicación anterior (http://bit.ly/Lu8Eft).  El código es similar a lo que hacemos en el método controlador del evento Checked que mostré más arriba, solo que esta vez lo hacemos sobre el valor de origen dentro del método Convert. Ya que el valor de la propiedad IsChecked del control CheckBox es del tipo bool anulable, entonces realizamos la conversión y evaluamos como lo hicimos en el método controlador del evento Checked antes mencionado. Para el valor de vuelta, realizaremos la conversión inversa, vemos que si el valor de la propiedad es Visibility.Visible regresa true y si es lo contrario es false.

Para efectos del ejemplo, declaramos la clase del converter en el mismo archivo de clase de la ventana dentro del namespace del ejemplo.

Ya que tengo mi clase converter, procedemos a las modificaciones pertinentes.

Primero, dejemos el constructor como estaba originalmente, esto es, sin los enlaces del controlador de eventos, quedando así:

public MainWindow()

{

    InitializeComponent();

    foto = new Foto();

    DataContext = foto;

}

 

Seguido de esto, pues… quitemos la declaración del método manejador, denominado chkMostrarImagen_Checked, ya no lo necesitamos más asi que bye.

Tercero, pues la mágia, debemos incluir la declaración del recurso vinculado a nuestra clase recién creada, quedando el bloque de recursos de la ventana de esta manera:

<Window.Resources>

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

    <conv:ConvertBooleanToVisibilityCollapsed x:Key=”BooleanToVisibilityCollapsed”/>

</Window.Resources>

 

El nombre que utilicé, es uno alusivo a la acción que realiza el converter. Ahora tenemos dos, como se puede observar.

Por último, modifiquemos el código del control imgFoto, para que interactúe con el CheckBox. Debemos declarar un nuevo binding, pero con una particularidad más, utilizaremos la propiedad ElementName. Como explicaba en una publicación anterior al hablar del enlace a datos (http://bit.ly/JmGKq8), se pueden enlazar las propiedades entre controles, en este caso, enlazaremos la propiedad IsChecked del control chkMostrarImagen con la propiedad Visibility del control imgFoto. El control chkMostrarImagen será el objeto origen del enlace y el control imgFoto será el objeto destino del enlace. Bien teniendo en mente esta consideración, debemos indicar al objeto Binding del enlace que se está enlazando la propiedad de destino de enlace con una propiedad de origen de enlace de un elemento de la interfaz de usuario, por esta razón incluiremos la propiedad ElementName a la que se le asigna el nombre del control que será el objeto de origen del enlace. Después de este preámbulo, veamos como queda la declaración:

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

        Source=”{Binding Image, UpdateSourceTrigger=PropertyChanged,

                Mode=TwoWay,

                Converter={StaticResource ByteArrayToBitmapImage}}”

        Visibility=”{Binding IsChecked, ElementName=chkMostrarImagen,

                Converter={StaticResource BooleanToVisibilityCollapsed}}”/>

 

Básicamente, enlazar las propiedades entre controles es muy similar a la manera en que se hace con los objetos propios, la particularidad es el uso de la propiedad ElementName. Cabe mencionar que si las propiedades de origen de enlace son del mismo tipo que las propiedades de destino de enlace; no hay necesidad de incluir un converter. Quiero ejemplificar esto agregando un enlace a datos a la propiedad Visibility del control btnFoto que se enlazará a la propiedad Visibility del control imgFoto, donde el control btnFoto será el objeto de destino de enlace y el control imgFoto será el objeto de origen de enlace. Una vez hechas las adecuaciones, quedará como a continuación se muestra:

<Button x:Name=”btnFoto” Width=”100″ Height=”25″

        Content=”Agregar Foto” Margin=”5″ Click=”btnFoto_Click”

        Visibility=”{Binding Visibility, ElementName=imgFoto}”/>

 

La funcionalidad esperada será que se oculten ambos controles al cambiar el valor de la propiedad IsChecked del control chkMostrarImagen a false y que se muestren al cambiar el valor de la propiedad IsChecked a true con solo utilizar un converter y el enlace a datos. Para ocultar el botón btnFoto no hay más que hacer que el enlace a datos de su propiedad Visibility a la propiedad Visibility del control imgFoto y se comportará como este último, esto es, que si es visible el control imgFoto, también será visible el control btnFoto y si se oculta el control imgFoto, también se ocultará el control btnFoto. Al final ya todo el código XAML de la ventana se verá de esta manera:

<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”/>

        <conv:ConvertBooleanToVisibilityCollapsed

               x:Key=”BooleanToVisibilityCollapsed”/>

    </Window.Resources>

    <Grid>

        <StackPanel HorizontalAlignment=”Center” VerticalAlignment=”Center”>

            <CheckBox x:Name=”chkMostrarImagen” Content=”Mostrar Imagen”

                        Margin=”5″ IsChecked=”True”/>

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

                    Source=”{Binding Image, UpdateSourceTrigger=PropertyChanged,

                            Mode=TwoWay,

                            Converter={StaticResource ByteArrayToBitmapImage}}”

                    Visibility=”{Binding IsChecked, ElementName=chkMostrarImagen,

                    Converter={StaticResource BooleanToVisibilityCollapsed}}”/>                  

            <Button x:Name=”btnFoto” Width=”100″ Height=”25″

                    Content=”Agregar Foto” Margin=”5″ Click=”btnFoto_Click”

                    Visibility=”{Binding Visibility, ElementName=imgFoto}”/>

        </StackPanel>

    </Grid>

</Window>

 

El código de la clase de la ventana, bueno, aunque lo manoseamos un poco, quedará como estaba en un principio. Lo único que agregamos fue la clase del converter y la declaración de los enlaces a datos.

Es una práctica muy común todavía la de escribir código para ocultar y mostrar los controles dentro de la clase de la ventana, y si había que ocultar o mostrar varios controles, había que escribir una línea para cada uno. Ahora con el enlace a datos entre controles, podemos propagar el valor de la propiedad de un control a todos los que deseemos con muy pocas líneas de código lo que resulta en un código más simple en la clase de la ventana.

Esto ha sido todo por esta ocasión y nos seguiremos leyendo después.

Octavio Telis

Conversión de Valores con Enlace a Datos

    

Hola que tal.

En el diseño de aplicaciones con WPF tendremos muchas ventajas en cuanto a la interfaz de usuario se refiere, dado que es un modelo muy flexible, nos permite dar formato y estilo a la apariencia de la interfaz de usuario, logrando aplicaciones más agradables, vistosas y que mejoran por mucho la experiencia de usuario.

Si bien la apariencia de la interfaz de usuario puede ser mejorada de gran manera, hay una parte importante que no podemos perder de vista, y me refiero a la manera en que se muestra la información al usuario cuando se utiliza el enlace a datos. La interfaz de usuario puede ser vistosa y tener un diseño espectacular, pero no está completa si no tenemos un mecanismo autónomo que permita dar formato de salida a los datos enlazados a nuestras clases dentro de la aplicación.

Con WPF y el .Net Framework, podemos simplificar mucho esta tarea, ya que contamos con elementos de programación que nos son útiles para mostrar los datos de manera dinámica y con una conversión automática en los enlaces a datos.

El .Net Framework expone dos tipos para esta tarea, uno es la clase Binding que está íntimamente ligada a WPF y que se puede utilizar con una gran cantidad de propiedades de dependencia contenidas en los controles de la interfaz de usuario y el otro es la interfaz IValueConverter que interactúa de manera íntima con la propiedad Converter de la clase Binding. Al hablar de una interfaz, comúnmente, estaremos considerando la construcción de una clase que la implemente, y esta no es la excepción. Crearemos clases con la funcionalidad de cambiar de un tipo a otro según lo que nos convenga para mostrar la información de nuestra aplicación al usuario. IValueConverter contiene dos métodos que serán implementados en nuestras clases, el primero es Convert, que expondrá la funcionalidad para convertir un tipo de origen en un tipo de destino distinto al primero, teniendo como parámetros el valor de origen generado por el enlace a datos (object), un tipo de destino del enlace a datos (Type), parámetros del enlace (object) y la información cultural de la aplicación en ejecución (CultureInfo).  En nuestro caso, no tendremos que asignar los valores de los parámetros de entrada, ya que esto lo hará el enlace a datos donde se utilice la propiedad Converter. El segundo método es ConvertBack, que expondrá la funcionalidad para convertir un dato que se propaga del enlace de destino, esto es, cuando un valor se cambia desde la interfaz de usuario, este método transformará el valor de destino a un dato válido para el origen. Tiene los mismos parámetros que el método Convert.

Para ejemplificar el uso de la conversión de datos, utilizaré el código de la publicación anterior (http://bit.ly/JmGKq8). En este ejemplo, definí una clase para mostrar el uso de Binding, la clase es la siguiente:

public class Foto : INotifyPropertyChanged

{

    BitmapImage image;

    public Foto()

    {

        image = null;

    }

 

    public BitmapImage Image

    {

        get

        {

            return image;

        }

        set

        {

            image = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs(“Image”));

        }

    }

 

    public event PropertyChangedEventHandler PropertyChanged;

}

 

Viendo a detalle la clase, veremos que la propiedad Image no resultará de mucha utilidad al usarla para manejar datos provenientes de una base de datos, ya que la mayoría de las base de datos no contienen un tipo de dato compatible directamente con BitmapImage, pero por el contrario, si tienen tipos de datos compatibles con un arreglo de bytes. Las imágenes pueden ser transformadas en un arreglo de bytes por proceder de archivos binarios, esto nos lleva a mejorar la clase cambiando el tipo de dato de la propiedad Image de BitmapImage por el tipo byte[]. Quedando la clase como sigue:

public class Foto : INotifyPropertyChanged

{

    byte[]image;

    public Foto()

    {

        image = null;

        PropertyChanged = delegate { };

    }

 

    public byte[]Image

    {

        get

        {

            return image;

        }

        set

        {

            image = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs(“Image”));

        }

    }

 

    public event PropertyChangedEventHandler PropertyChanged;

}

 

Este cambio repercute en la aplicación, ya que un control Image no puede recibir directamente un arreglo de bytes en la propiedad Source, deberemos quitar el enlace a datos de la propiedad Source en el código XAML y además, deberemos inicializar el evento PropertyChanged para que la clase pueda ser utilizada sin un enlace a datos, ya que al estar enlazada, Binding inicializaba este evento. Por otra parte, tenemos que modificar el método del botón btnFoto que estaba de esta manera:

 

private void btnFoto_Click(object sender, RoutedEventArgs e)

{

    if (foto.Image == 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;

            foto.Image = b;

 

            btnFoto.Content = “Quitar Foto”;

        }

    }

    else

    {

        foto.Image = null;

        btnFoto.Content = “Agregar Foto”;

    }

 

}

 

Y ahora quedaría algo más o menos así:

private void btnFoto_Click(object sender, RoutedEventArgs e)

{

    if (foto.Image == 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;

 

            string path = b.UriSource.OriginalString;

            FileStream sr = new FileStream(path, FileMode.Open, FileAccess.Read);

            byte[] bytes = new byte[sr.Length];

            sr.Read(bytes, 0, bytes.Length);

            foto.Image = bytes;

 

            btnFoto.Content = “Quitar Foto”;

        }

    }

    else

    {

        foto.Image = null;

        imgFoto.Source = null;

        btnFoto.Content = “Agregar Foto”;

    }

}

 

El objetivo era optimizar la aplicación y con este cambio hemos complicado las cosas, hemos escrito más código y hemos vuelto a la práctica antigua, teniendo que prescindir del enlace de datos que era una de las ventajas que habíamos logrado. Sin embargo, podemos notar que tenemos la funcionalidad de la conversión, implícita en el código del botón btnFoto. Ahora, si tuviéramos un mecanismo que pudiera realizar esa conversión y que además permitiera el uso de Binding, el código se simplificaría y ganaríamos funcionalidad reutilizable en otras situaciones semejantes. Pues bien, aquí es donde IValueConverter cobra sentido.

Para poder tomar ventaja de IValueConverter, deberemos crear una clase que implemente esta interfaz, y así, poder utilizarla con la clase Binding de los enlaces a datos de nuestra interfaz de usuario. La clase quedaría como sigue:

[ValueConversion(typeof(byte[]), typeof(BitmapImage))]

public class ConvertByteArrayToBitmapImage : IValueConverter

{

    public object Convert(object value, Type targetType,

        object parameter, CultureInfo culture)

    {

        try

        {

            byte[] res;

            res = value as byte[];

            BitmapImage b = new BitmapImage();

 

            MemoryStream ms = new MemoryStream(res);

 

            b.BeginInit();

            b.CacheOption = BitmapCacheOption.OnLoad;

            b.StreamSource = ms;

            b.EndInit();

            return b;

        }

        catch

        {

            return value;

        }

    }

    public object ConvertBack(object value, Type targetType,

        object parameter, CultureInfo culture)

    {

        try

        {

            byte[] res;

            string path = (value as BitmapImage).UriSource.OriginalString;

            FileStream sr = new FileStream(path, FileMode.Open, FileAccess.Read);

            res = new byte[sr.Length];

            sr.Read(res, 0, res.Length);

            return res;

        }

        catch

        {

            return value;

        }

    }

}

 

Como podemos observar,  la clase contiene los dos métodos que mencioné anteriormente, el método Convert que tiene el código correspondiente para convertir un arreglo de bytes en un BitmapImage y por otra parte el método ConvertBack que tiene el código necesario para convertir un BitmapImage en un arreglo de bytes. Dato curioso es el atributo que antecede a la declaración de la clase, del tipo ValueConversion, este atributo solo está disponible para WPF y nos permite especificar qué tipos de datos serán utilizados en el convertidor, el primer parámetro del constructor de esta clase es el tipo de origen y el segundo parámetro es el tipo de destino. En el enlace a datos, recordemos que se asigna a la propiedad Path de Binding el nombre de la propiedad de origen del enlace, el tipo de dato de la propiedad de enlace de origen será el que se ponga en el primer parámetro, y por ende, en el segundo parámetro se pondrá el tipo de dato de la propiedad de destino de enlace. Así pues, el primer parámetro será del tipo byte[] y el segundo del tipo BitmapImage. El nombre de la clase es alusivo a la funcionalidad que va a exponer, por lo que es recomendable utilizar la palabra Convert al inicio y de qué tipo a qué tipo va a hacerse la conversión.

Para efectos del ejemplo, esta clase la pueden escribir en el mismo archivo de la clase de la ventana, dentro del mismo namespace a continuación de la clase Foto.

Para poder utilizar esta clase en el enlace a datos, debemos realizar algunos cambios a nuestra interfaz de usuario realizando lo siguiente:

1.- Declarar un espacio de nombres XML al en la etiqueta Window de nuestra ventana, de la siguiente manera:

<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″>

 

El espacio de nombres que declaré fue xmlns:conv y hace referencia  al espacio de nombres de nuestro ejemplo.

 

2.- Agregar una sección de recursos para la ventana, en la cual declararemos un recurso que incluya la definición de la clase para utilizarlo en el enlace a datos, quedando como sigue:

 

<Window.Resources>

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

</Window.Resources>

 

El recurso utiliza el espacio de nombres declarado en el punto 1 y debe ser identificado con una llave x:Key, el nombre de la llave es alusivo a la clase. Cabe mencionar, que este recurso estará disponible para toda la ventana si se desea convertir valores en más de un enlace a datos.

 

3.- Modificar la declaración del enlace a datos en la propiedad Source del control imgFoto de la siguiente manera:

 

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

       Source=”{Binding Image, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay,

                Converter={StaticResource ByteArrayToBitmapImage}}”/>

 

Podemos notar que se han agregado dos propiedades más de la clase Binding, una es la propiedad Mode, Mode es del tipo BindingMode, que es un enumerador que define de qué modo se hará el enlace a datos, esta propiedad está asignada al valor Default de manera predeterminada. En las propiedades de dependencia que pueden ser modificadas por el usuario, de controles como el TextBox, CheckBox, etc., tienen como valor predeterminado TwoWay que define un enlace de datos de dos sentidos, esto es, el enlace se establece a la propiedad de origen de enlace cuando se modifica la propiedad de destino de enlace y viceversa, esto permite el uso de UpdateSourceTrigger. En controles como el Image, el TextBlock, etc., el valor de la propiedad Mode está establecido en OneWay, que solo permite el enlace en un solo sentido, esto es, de la propiedad de origen de enlace a la propiedad de destino de enlace. Por lo anterior, hemos asignado la propiedad Mode a TwoWay, para poder establecer un enlace a datos en los dos sentidos, la razón se explica más adelante.

 

La otra propiedad que hemos incluido de Binding es Converter, la cual hemos asignado con la referencia a un recurso de la ventana (StaticResource) el cual se declaró en la sección de recursos de la misma. El resultado final es el siguiente:

 

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

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

                   Source=”{Binding Image, UpdateSourceTrigger=PropertyChanged,

                            Mode=TwoWay,

                            Converter={StaticResource ByteArrayToBitmapImage}}”/>

            <Button x:Name=”btnFoto” Width=”100″ Height=”25″

                    Content=”Agregar Foto” Margin=”5″ Click=”btnFoto_Click”/>

        </StackPanel>

    </Grid>

</Window>

 

EL código del botón, puede permanecer como en el ejemplo anterior, sin embargo, para dejar el código independiente de la clase y dejar todo en función de la interfaz de usuario, modificamos el código del enlace a datos incluyendo la propiedad Mode, como se expuso anteriormente y dejando el código del botón btnFoto 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)

        {

            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”;

    }

}

 

La clase foto quedaría entonces de la siguiente manera:

 

public class Foto : INotifyPropertyChanged

{

    byte[] image;

    public Foto()

    {

        image = null;

    }

 

    public byte[] Image

    {

        get

        {

            return image;

        }

        set

        {

            image = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs(“Image”));

        }

    }

    public event PropertyChangedEventHandler PropertyChanged;

}

 

El objetivo del enlace a datos es, principalmente, establecer un vínculo directo con las clases de datos y la interface de usuario, además de que permite centralizar las tareas de conversión en un solo lugar permitiendo la reutilización, con esto evitamos tareas sinuosas y exhaustivas al mostrar información al usuario. Otra de las bondades del enlace a datos es la capacidad de validar datos de manera estrecha con la clase de datos, pero esto lo veremos con detalle en otra ocasión. También seguiremos con el tema de la conversión en algunos ejemplos más adelante, relacionados al enlace a datos entre elementos de la interfaz de usuario.

 

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

 

Octavio Telis.

Mostrar archivos de imagen en WPF utilizando enlace a datos.

Holal Qué tal.

Continuando con el ejemplo del post anterior (http://bit.ly/oh0m9k), referente al manejo de imágenes en WPF, ejemplificaremos el manejo de las imágenes de la misma manera pero, esta vez, utilizando enlace a datos y la clase Binding.

Antes, tenemos que hablar un poco de lo que es el enlace a datos con WPF. Trataré de ser breve y de no ahondar tanto para llegar más rápido a lo que nos ocupa.

El enlace a datos en objetos de la interfaz de usuario de WPF está determinado a través de la clase Binding, expuesta en la mayoría de las ocasiones, dentro del código de XAML definiendolo directamente en la asignación del valor de la propiedad que se quiere enlazar.

Debemos tener en cuenta que para realizar el enlace de datos, se debe contar con las condiciones necesarias para que esto sea posible, empezando por identificar las propiedades que podemos enlazar, las cuales deben ser propiedades de dependencia y que no sean de solo lectura. Otra consideración importante, es que las propiedades se enlazan a valores establecidos en objetos con la finalidad de mostrar los datos que contienen sus propiedades, en la interfaz de usuario.

Comúnmente se asignan los valores de los objetos directamente a las propiedades de los controles contenidos en la interfaz de usuario desde el código, teniendo complicaciones al momento de implementar la actualización de la información presentada en la interfaz de usuario de manera dinámica cuando los valores en las propiedades de nuestros objetos cambian durante la ejecución del código, derivando en la definición de más código que incluyen condiciones en la ejecución y eventos que pudieran estar de más con el uso del enlace a datos utilizando Binding.

La clase Binding ofrece mecanismos prácticos para que la información que se muestra al usuario sea la misma que contienen las propiedades de nuestros objetos.

Para iniciarnos en el uso de Binding debemos tener presente que, para que la información que contienen las propiedades de nuestros objetos se muestren correctamente en la interfaz de usuario utilizando enlace a datos en WPF y XAML, deben existir cuatro elementos necesarios para que funcione de la mejor manera. Primeramente debemos definir el Destino del enlace, el cual será un control de la interfaz de usuario, dicho de una mejor manera, un objeto de dependencia. Seguido de esto, identificaremos en el objeto de dependencia, la propiedad de dependencia que será la encargada de mostrar el dato. Con esto tenemos definido ya el destino, ahora bien, debemos identificar el objeto de origen, el cual expondrá  los datos y por último, la propiedad en este objeto que será la que proveerá el dato a la interfaz de usuario.

Con esto, hemos definido los cuatro elementos necesarios para realizar el enlace a datos utilizando Binding en WPF. En resumen, los cuatro elementos son un objeto de destino del enlace, una propiedad de destino del enlace, un objeto de origen del enlace y una propiedad de origen del enlace. Recordemos que la propiedad de destino de enlace debe ser una propiedad de dependencia.

Como podrán observar, la propiedad de origen de destino no es necesariamente una propiedad de dependencia y los objetos de origen de enlace no están restringidos a ser únicamente los personalizados, también, los propios elementos de la interfaz de usuario pueden ser objetos de origen de enlace. Como veremos en otra publicación en otra ocasión.

Para ejemplificar, dentro de nuestro ejemplo tenemos declarado un control Image denominado imgFoto, este control mostrará una imagen como dato enlazado. La declaración XAML será algo como esto:

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

        Source=”{Binding Path=Image}” />

 

Podemos notar ciertas características en la definición del enlace a datos, primeramente veremos que la propiedad Source se declara como habitualmente se hace, sin embargo, dentro de la acotación definida por las comillas se agrega la declaración del enlace entre llaves ({}),  se pone la palabra Binding que hace referencia a la clase del mismo nombre y en seguida la asignación de Path con el nombre de la propiedad de origen del enlace, misma que está definida en el objeto de origen del enlace.

La clase Binding expone una propiedad denominada Path, esta propiedad es la que establece el enalce con la propiedad de origen. Es una propiedad predeterminada, a menos que se requiera la declaración explicita se podrá omitir, y lo que se escriba inmediatamente enseguida de Binding será tomado como el nombre de la propiedad de origen del enlace y será asignado a la propiedad Path. Teniendo en cuenta esto, la declaración puede quedar como sigue:

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

        Source=”{Binding Image}” />

 

De este modo el código de nuestra ventana quedará conformado de manera inicial como lo siguiente:

<Window x:Class=”EjemplosWPF.MainWindow”

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

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

        Title=”MainWindow” Height=”350″ Width=”525″>

    <Grid>

        <StackPanel HorizontalAlignment=”Center” VerticalAlignment=”Center”>

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

                    Source=”{Binding Image}” />

            <Button x:Name=”btnFoto” Width=”100″ Height=”25″

                    Content=”Agregar Foto” Margin=”5″ Click=”btnFoto_Click”/>

        </StackPanel>

    </Grid>

</Window>

 

Debemos declarar ahora la clase que define al objeto de origen del enlace, será una clase simple y estará definida como sigue:

public class Foto

{

    BitmapImage image;

    public Foto()

    {

        image = null;

    }

 

    public BitmapImage Image

    {

        get

        {

            return image;

 

        }

        set

        {

            image = value;

       }

 

    }

}

 

Para efectos del ejemplo, esta clase la pueden crear enseguida de la clase de la ventana, dentro del mismo Namespace.

En la ventana, el código quedaría de la siguiente manera:

public partial class MainWindow : Window

{

    Foto foto;

    public MainWindow()

    {

        InitializeComponent();

        foto = new Foto();

        DataContext = foto;

    }

 

    private void btnFoto_Click(object sender, RoutedEventArgs e)

    {           

        if (foto.Image == 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;

                foto.Image = b;

 

                btnFoto.Content = “Quitar Foto”;

            }

        }

        else

        {

            foto.Image = null;

            btnFoto.Content = “Agregar Foto”;

        }

           

    }

}

 

Como podemos observar, no es muy diferente al código del ejemplo anterior, sin embargo podemos recalcar algunos cambios sustanciales.

Primero, se ha incluido la declaración de la variable foto, que es del tipo Foto, mismo que está definido por la clase que se muestra más arriba.

Segundo, en el constructor de la clase de la ventana, se agregó la instancia de la variable foto y se asigna la propiedad DataContext de la misma ventana. Aquí quiero hacer un paréntesis y hacer una observación sobre DataContext. Al asignar el objeto foto como DataContext del formulario, hará que en todas las declaraciones de Binding se asuma que los valores de Path son propiedades de origen de enlace contenidas en el objeto foto. Si existieran más controles con la definición de Binding, además del control imgFoto, los valores asignados a las propiedades Path de estos, estarían en el entendido de ser propiedades de origen de enlace definidas en el objeto foto. Si por el contrario, se asignara directamente a la propiedad DataContext del control imgFoto, solo se aplicarían los enlaces de datos a este control en exclusivo. Veremos un ejemplo de esto en la siguiente publicación.

Tercero; las validaciones cambiaron, de validar la propiedad Source del control imgFoto a validar la propiedad Image del objeto foto.

Cuarto; ahora en vez de asignar la propiedad Source del control imgFoto, se asigna la propiedad Image del objeto foto, que será la propiedad de origen del enlace, como podemos ver en el código XAML, la propiedad Source del control imgFoto está asignada con un Binding cuya propiedad Path, es la propiedad Image del objeto foto, de esta manera por ser del mismo tipo de valor que recibe la propiedad Source, se debe mostrar al momento en el control imgFoto.

Quinto; ahora se anula la propiedad Image del objeto foto en lugar de anular la propiedad Source del control imgFoto, que tendrá el mismo comportamiento que en el punto cuarto, quitando la imagen del control imgFoto.

Pues bien, aclarando esto, construimos el proyecto y ejecutamos… y… después de ver que al agregar la foto, no se muestra… nos desesperamos y caemos en la cuenta de que el Binding no sirve para nada… No, no es ninguna falla. El Binding para que sea dinámico debe estar notificado por un desencadenador (trigger), que actualizará la interfaz de usuario al momento en que cambia la propiedad. Para lograr esto debemos modificar un poco el código, solo en la clase Foto y en el XAML de la ventana, el código de la ventana se queda como está.

En la declaración de XAML, tenemos lo siguiente:

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

        Source=”{Binding Image}” />

 

Debemos modificar esta declaración incluyendo la propiedad UpdateSourceTrigger asignándola con el valor PropertyChanged. Quedando como sigue:

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

        Source=”{Binding Image, UpdateSourceTrigger=PropertyChanged}” />

 

Podemos observar que la propiedad se define explícitamente a diferencia de la propiedad Path, que ya habíamos comentado, y también vemos que las propiedades de Binding se incluyen en la declaración separadas por comas. Pero aun así, no es suficiente, ya que el valor PropertyChanged hace alusión a un evento que se desencadena en el objeto de origen del enlace, y debe incluirse en este como una implementación de la interfaz INotifyPropertyChanged que es parte del namespace System.ComponentModel. Así que haremos las modificaciones correspondientes. Primeramente al inicio de nuestro archivo de clase incluimos la declaración:

using System.ComponentModel;

 

Una vez incluida esta declaración, modificaremos la clase Foto para que quede de la siguiente manera:

public class Foto : INotifyPropertyChanged

{

    BitmapImage image;

    public Foto()

    {

        image = null;

    }

 

    public BitmapImage Image

    {

        get

        {

            return image;

 

        }

        set

        {

            image = value;

            PropertyChanged(this,

                new PropertyChangedEventArgs(“Image”));

        }

 

    }

 

    public event PropertyChangedEventHandler PropertyChanged;

}

 

Podemos observar que incluimos en la declaración de la clase las adecuaciones para implementar la interfaz INotifyPropertyChanged, además, se declara el evento PropertyChanged que de manera implícita implementa el miembro PropertyChanged de la interfaz INotifyPropertyChanged. Ahora bien, en la propiedad Image de la clase, en el descriptor de acceso set se lanza el evento PropertyChanged, como parte de los argumentos del evento se incluye como parámetro el nombre de la propiedad como un string. Así, toda vez que se asigne un valor a la propiedad Image del objeto foto,  se lanzará el evento PropertyChanged e informará del cambio del valor de la propiedad a Binding para que el contenido de las propiedades enlazadas se actualicen.

Ahora sí, hechas las modificaciones pertinentes, ejecutemos el código para ver complacientes que todo funciona como se esperaba que lo hiciera.

Este ejemplo es un adelante a lo que sigue, que es cómo manejar las imágenes como datos que pueden salvarse como parte de un registro en una base de datos. El próximo articulo estará dirigido a esto en el que incluiré todo lo necesario para manejar la imagen con bases de datos.

Espero que este artículo sea de utilidad y nos vemos en la próxima.

Octavio Telis.

.NET Chronicles
  • Eventos del Teclado en WPF July 22, 2015
      Hola ¿qué tal? En esta ocasión como como continuidad a lo que previamente había escrito sobre los eventos del teclado en Windows Forms, haré un artículo sobre el uso de los eventos del teclado en WPF, así es en Windows Presentation Foundation. No es desconocido por muchos que el nuevo estándar de desarrollo de […]
  • Programación Orientada a Objetos (Introducción) May 14, 2014
    Hola qué tal… Aquí les dejo este video sobre la programación orientada a objetos, a manera de preámbulo a los siguientes videos, con las bases de la programación en C# y otra línea con algunas utilerías. Saludos… Octavio Telis
  • Capítulo piloto del la versión en video de .NET Chronicles May 6, 2014
    Hola qué tal??? Pues en esta ocasión estoy compartiendo con ustedes la liga de un video piloto, con el que pretendo comunicar un poco más sobre las tecnologías .NET y los lenguajes de Programación. En esta ocasión será con la presentación de C#, en un capitulo titulado “Te presento a C#”, espero que sea de […]
  • Validación de datos de entrada con enlace a datos en WPF May 27, 2012
    Tweet 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, […]
  • Uso de la propiedad Visibility con valores booleanos en WPF May 25, 2012
    Tweet    Hola que tal. En esta ocasión quiero compartir con ustedes una manera para utilizar la propiedad Visibility con un valor bool. Te preguntarás “¿Y qué sentido tiene eso?”, bien, el detalles está en que en ocasiones se requiere que la visibilidad de un control de la interfaz de usuario responda a un valor […]
  • Conversión de Valores con Enlace a Datos May 24, 2012
    Tweet     Hola que tal. En el diseño de aplicaciones con WPF tendremos muchas ventajas en cuanto a la interfaz de usuario se refiere, dado que es un modelo muy flexible, nos permite dar formato y estilo a la apariencia de la interfaz de usuario, logrando aplicaciones más agradables, vistosas y que mejoran por mucho […]
  • Mostrar archivos de imagen en WPF utilizando enlace a datos. May 23, 2012
    Tweet Holal Qué tal. Continuando con el ejemplo del post anterior (http://bit.ly/oh0m9k), referente al manejo de imágenes en WPF, ejemplificaremos el manejo de las imágenes de la misma manera pero, esta vez, utilizando enlace a datos y la clase Binding. Antes, tenemos que hablar un poco de lo que es el enlace a datos con […]
  • Mostrar archivos de imagen en WPF August 26, 2011
    Hola Qué Tal… En esta ocasión quiero comenzar una serie de artículos sobre el manejo de archivos de imágenes con .NET. En esta primera parte voy a tratar la manera de cargar un archivo de imagen en un contenedor de imagen, en este caso usaré WPF y el control Image que viene incluido en el […]
  • Comparar dos DataTables según sus DataRows August 23, 2011
    Hola que tal. En ocasiones es necesario comparar el contenido de dos DataTable para determinar qué registros (DataRow) están en una y en otra no. Supongamos tenemos dos DataTable; dt1 y dt2, ambas con el mismo esquema. La tabla dt2 contiene más registros que la taba dt1, por lo que deseamos saber qué registros de […]
  • Arquitectura – Definición de un Data Access Component (con un ejemplo) Parte 3 May 14, 2010
    Hola Qué Tal? En esta ocasión, no he dejado pasar tanto tiempo para terminar la trilogía del uso de Data Access Component con un ejemplo. Bien, pues en este artículo veremos el uso del componente ya creado, cómo extenderemos la funcionalidad del componente y cómo lo aplicamos en la interfaz de usuario. Primeramente, debemos crear […]