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

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>