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.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>