WPF UniformGrid vs Grid con IsSharedSizeScope = True

Lo que voy a publicar hoy es un pequeño truco que seguramente todos conozcáis, pero que sin duda puede ayudar a ahorrar mucho tiempo de diseño en vuestras preciosas y coherentes aplicaciones. Decir que los controles destinados al Layout de una interfaz de usuario en WPF son espectaculares sobra. Hay una gran cantidad de Panels que se ajustan a cualquier situción que estemos buscando. Pero a mí siempre me llaman la atención las cosas sencillas. Qué duda hay de que el Panel Grid de WPF es el más versátil de todos. Nos permite hacer cualquier cosa. Podemos definir tantas filas y columnas en él como queramos. Vamos, una pasada de control. Pues bien, uno de los “truquillos” de Grid es establecer el valor de la AttachedProperty Grid.IsSharedSizeScope a True. (De momento no hablaré de las Attached Properties, pero quizás me anime a hacerlo en un post posterior puesto que he implementado algunos trucos bastante interesantes con este mecanismo.) Dicha propiedad permite que las columnas o filas presentes en el Grid compartan entre ellas información de su anchura o altura. ¿Para qué puede ser útil? Se recurre mucho a esta técnica, por ejemplo, en el caso de querer hacer que los Button pertenecientes a un mismo grupo o categoría tengan el mismo tamaño (equivalente al máximo de entre todos). ¿Para qué se recurre a esto? Aunque todo es cuestión de diseño, hay estudios que demuestran que en una pareja de botones “Imprimir – Mandar por correo electrónico” por ejemplo, ambos deberían tener el mismo ancho para no parecer incoherentes. Siempre se puede usar un ancho fijo para ambos, pero no es una solución elegante si nuestra aplicación va a ser traducida a diversos idiomas donde las palabras, obviamente, no miden lo mismo. Para conseguir, pues, este efecto de homogeneidad se puede hacer uso de un par de columnas (ColumnDefinition), colocar en cada una de ellas un botón, establecer la propiedad SharedSizeGroup de cada ColumnDefinition a un valor común (por ejemplo “Grupo”) y el ingrediente final: la mencionada Grid.IsSharedSizeScope = True en el Grid contenedor. Veamos el XAML de dicho escenario:

 1: <Grid Grid.IsSharedSizeScope="True">
 2:     <Grid.ColumnDefinitions>
 3:         <ColumnDefinition SharedSizeGroup="Group"/>
 4:         <ColumnDefinition SharedSizeGroup="Group"/>
 5:     Grid.ColumnDefinitions>
 6:         <Button Content="Imprimir" Grid.Column="0" Margin="0,0,4,0"/>
 7:     <Button Content="Enviar por correo electrónico" Grid.Column="1" Margin="4,0,0,0"/>
 8: </Grid>

El resultado es el que esperábamos. Ambos botones tienen el mismo ancho y si en un futuro queremos cambiar el idioma o, incluso, el texto original, los botones seguirán siendo igual de anchos. Varias decenas de veces he repetido este mecanismo para diferentes situaciones, hasta que un día me percaté de que tanta “verborrea” de xaml se podría simplificar mucho haciendo uso de mi gran amigo: El sencillo UniformGrid. Con este Grid simplificado solo hay que tener en cuenta cuántas columnas y filas queremos u olvidarse por completo de definir nada (en cuyo caso UniformGrid creará dinámicamente tantas columnas como filas para que cada elemento hijo tenga cabida y se organice de una manera homogénea). Por lo tanto, para hacer una equivalencia de nuestro ejemplo pero con UniformGrid sólo necesitaremos establecer el número de filas que queremos. En nuestro caso será uno. UniformGrid se encargará pues de crear tantas columnas del mismo ancho como hijos contenga. De este modo tan sencillo y olvidándonos por completo de propiedades especiales y mágicas que necesitan trabajar juntas hemos conseguido el mismo efecto de homogeneidad en nuestros botones. Veamos el XAML necesario:

 1: <UniformGrid Rows="1">
 2:     <Button Content="Imprimir" Margin="0,0,4,0"/>
 3:     <Button Content="Enviar por correo electrónico" Margin="4,0,0,0"/>
 4: </UniformGrid> 

Mucho más sencillo y legible, ¿verdad? Y como siempre digo, esto sólo es un pequeño ejemplo, pero puede haber una gran cantidad de situaciones en las que se requiera el uso de cualquiera de estas técnicas para conseguir distintos resultados. Una de ellas, por ejemplo, es la necesidad de compartir anchuras entre diferentes items de una lista, los cuales han sido generados por un DataTemplate que contiene Grids y varias columnas. Pero quizás os lo explique en otro post. Espero no haber aburrido demasiado a nadie y gracias por leerme. Saludos

Multi-Conversor en WPF con IMultiValueConverter

A raíz de un post del blog de Javier Torrecilla y una posterior conversación en Twitter, he decidido escribir mi primera entrada.

Hablaré un poco sobre cómo implementar la interfaz  System.Windows.Data.IMultiValueConverter y propondré un ejemplo, a mi parecer, divertido.

La interfaz IMultiValueConverter contiene dos métodos, Convert y ConvertBack, al igual que en IValueConverter. A diferencia de como ocurre en ésta, Convert recibe en su primer parámetro un array de object. Es por ello que ya no estamos ante un Converter normal y corriente que transforma un objeto en otro, sino que tenemos la posibilidad de pasarle a dicha función tantos objetos como queramos, los cuales serán posteriormente utilizados para crear el valor resultado.

Veamos el ejemplo:

Código del multi-conversor:

 1: using System.Windows.Data;
 2: using System;
 3: using System.Globalization;
 4: namespace IMultiValueConverterExample
 5: {
 6:    public class ChromosomeToBabyConverter : IMultiValueConverter
 7:    {
 8:       public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
 9:       {
 10:          string first = (string)values[0];
 11:          string second = (string)values[1];
 12:          if (first == second && first == "X")
 13:          {
 14:             return "Girl";
 15:          }
 16:          else if (first == "X" && second == "Y" || first == "Y" && second == "X")
 17:          {
 18:             return "Boy";
 19:          }
 20:          else
 21:          {
 22:             return "Angel";
 23:          }
 24:       }
 25:  
 26:       public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
 27:       {
 28:          if ((string)value == “Boy”)
 29:          {
 30:             return new string[] { “X”, “Y” };
 31:          }
 32:          else if ((string)value == “Girl”)
 33:          {
 34:             return new string[] { “X”, “X” };
 35:          }
 36:          else
 37:          {
 38:             return new string[] { “Y”, “Y” };
 39:          }
 40:       }
 41:    }
 42: }

 
Y ahora el marcado XAML con una sencillísima interfaz que contiene tres TextBox. (Los dos primeros permiten escribir el tipo de cromosoma y el tercero muestra el sexo del bebé)

 1: <Window x:Class="IMultiValueConverterExample.MainWindow"
 2:    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3:    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4:    Title="IMultiValueConverterExample"
 5:    xmlns:local="clr-namespace:IMultiValueConverterExample">
 6:    <StackPanel>
 7:       <StackPanel.Resources>
 8:          <local:ChromosomeToBabyConverter x:Key="converter"/>
 9:       </StackPanel.Resources>
 10:       <TextBox x:Name="first"/>
 11:       <TextBox x:Name="second"/>
 12:       <TextBox>
 13:          <TextBox.Text>
 14:             <MultiBinding Converter="{StaticResource converter}">
 15:                <Binding ElementName="first" Path="Text"/>
 16:                <Binding ElementName="second" Path="Text"/>
 17:             </MultiBinding>
 18:          </TextBox.Text>
 19:       </TextBox>
 20:    </StackPanel>
 21: </Window>

 
Espero que en éste, mi primer post, me haya explicado correctamente y con ello la gente se anime al uso de esta técnica.
¡Saludos!