127.0.0.1: Hogar dulce hogar
Mostrando entradas con la etiqueta binding. Mostrar todas las entradas
Mostrando entradas con la etiqueta binding. Mostrar todas las entradas

viernes, 8 de abril de 2011

[WPF/Silverlight] Binding de objetos relacionados en un ComboBox

 

Escenario: Tengo dos entidades relacionadas. La entidad A se relaciona con la entidad B a través de un identificador. Y lo que quiero es bindear eso, pero de modo que yo disponga en la interfaz de un combobox –o cualquier tipo de selección- que permita seleccionar todos los elementos. Entonces tenemos las siguientes tareas:

  1. Bindear los datos del campo con el identificador de la identidad.
  2. Pero yo no quiero ver un número. Yo quiero ver el nombre del objeto que relaciono. Por lo tanto, tendré un combobox que rellenar y bindear con respecto al identificador anteriormente citado.
  3. Y claro, para hacerlo bueno, bonito y barato, nada de hacerlo en el código. Todo debe ir en el XAML.

El primer enfoque puede ir dirigido a un binding normal. Es decir, obviamente tenemos que tener en cuenta el ID de la entidad A, puesto que ese será el valor elegido a mostrar. Y como quiero rellenar todo el combobox con el resto de entidades B, usaremos un Converter:

  1. <ComboBox ItemsSource="{Binding Converter={StaticResource ResourceKey=Converter}}" />

De este modo ya podemos ver todos los valores de la entidad B. Y además, en el converter podemos declarar cómo queremos mostrar cada Item:

  1. public class MyIDConverter : IValueConverter
  2.     {
  3.         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  4.         {
  5.  
  6.             List<ComboBoxItem> list = new List<ComboBoxItem>();            
  7.             foreach (B b in B.All())
  8.             {
  9.                 ComboBoxItem comboItem = new ComboBoxItem();
  10.                 comboItem.Content = b.Name;
  11.                 comboItem.Tag = b;
  12.                 list.Add(comboItem);
  13.             }
  14.             return list;
  15.  
  16.         }
  17.  
  18.         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  19.         {
  20.             return null;
  21.         }
  22.     }

Ahora lo que falta es enlazar el ID de A para que el combobox tenga como seleccionado el item de B correspondiente. Dentro del primero enfoque, sería usar otro converter para ello, pero tenemos un problema: necesito obtener el combobox para indicarle SelectedItem. De acuerdo, podemos declarar un parámetro en el Converter y le pasaremos el propio combobox del siguiente modo:

  1. <ComboBox ItemsSource="{Binding Converter={StaticResource ResourceKey=Converter}, ConverterParameter={RelativeSource Mode=Self}}" />

Pero esto directamente no compila. ¿Por qué? Porque los parámetros que se pasan al Converter deben ser constantes y no objetos variables. Es decir, le puedo pasar binding de propiedades como objetos ya que las considera “constantes”, pero el propio objeto comboBox (que al bindearlo se convierte en un puntero this) va cambiando. Una solución a esto es añadir manualmente dicho combobox como un DependencyProperty propio de la clase. Y otra solución es escribir el código necesario para que esto funcione en el codebehind. Pero recuerdo que el objetivo de esto es usar únicamente el XAML

¿Solución? El segundo enfoque. Simplemente consiste en añadir, además del primer Converter para rellenar el comboBox, otro que se dedique a bindear el contenido del combobox con el identificador de la entidad A. Se implementa la interfaz IMultiValueConverter que permite bindear a la vez varios elementos sobre una misma propiedad. De este modo, bindearemos la propiedad identificador de B que aparece en A como relación junto con el propio ComboBox. Y lo bindearemos al SelectedIndex de ComboBox, para que cargue el objeto B apropiado y además, que al seleccionar otro, modifique el identificador que corresponda.

Veamos el código:

  1. <DataTemplate>
  2.                             <ComboBox>
  3.                                 <ComboBox.ItemsSource>
  4.                                     <Binding Converter="{StaticResource ResourceKey=MyIDConverter}">
  5.                                     </Binding>
  6.                                 </ComboBox.ItemsSource>
  7.                                 <ComboBox.SelectedIndex>
  8.                                     <MultiBinding Converter="{StaticResource ResourceKey=MyIDtoIDConverter}" UpdateSourceTrigger="PropertyChanged">
  9.                                         <MultiBinding.Bindings>
  10.                                             <Binding Path="BID" Mode="TwoWay"></Binding>
  11.                                             <Binding RelativeSource="{RelativeSource Mode=Self}" Path="."></Binding>
  12.                                         </MultiBinding.Bindings>
  13.                                     </MultiBinding>
  14.                                 </ComboBox.SelectedIndex>
  15.                             </ComboBox>
  16.                         </DataTemplate>

Y ahora la implementación del IMultiValueConverter

  1. public class MyIDtoIDConverter : IMultiValueConverter
  2. {
  3.     private ComboBox combo;
  4.     public MyIDtoIDConverter()
  5.     {
  6.         combo = null;
  7.     }
  8.  
  9.     public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  10.     {
  11.         this.combo = values[1] as ComboBox;
  12.         ComboBoxItem comboItem = null;
  13.         int index = 0;
  14.         foreach (ComboBoxItem item in combo.Items)
  15.         {
  16.             OperationType operationType = item.Tag as OperationType;
  17.             if (operationType.Id == values[0] as int?)
  18.             {
  19.                 comboItem = item;
  20.                 combo.SelectedIndex = index;
  21.             }
  22.             index++;
  23.         }
  24.  
  25.         return combo.SelectedIndex;
  26.     }
  27.  
  28.     public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
  29.     {
  30.         ComboBoxItem comboItem = this.combo.Items[(int)value] as ComboBoxItem;
  31.         B b = comboItem.Tag as B;
  32.         return new object[] { b.ID };
  33.     }
  34. }

 

El Converter almacena el comboBox para que después al hacer el ConvertBack, tenga la referencia del estado de los items. De este modo, tenemos las entidades bindeadas con las propiedades de los elementos de WPF y además manteniendo sus relaciones.

lunes, 27 de diciembre de 2010

[Silverlight] Convirtiendo tipos para DataTemplates

Una de las armas más poderosas de WPF y Silverlight son los DataTemplates, que te permiten especificar un template y después mediante el DataBinding puedes desplegar todos los datos de forma automática sin necesidad de rellenarlos a “pelo” insertando fila por fila.

Pero en algunas ocasiones se necesitará contemplar elementos que deben aparecer o no en función de algunos valores de lógica. Por ejemplo, en mi lógica dispongo de una clase Item que tiene una propiedad booleana llamada Borrowed. Cuando esta propiedad está a true, debe aparecer un botón, y cuando no lo está, no debe aparecer nada. En Silverlight la propiedad Visibility permite especificar la visibilidad de un elemento cualquiera, pero esta propiedad se ajusta a través de un enumerado que indica si es visible ( Visible ) o no (Collapsed). Como medida sucia podría incluir dentro de mi lógica una propiedad que tuviera ese valor, pero entonces estaría causando una dependencia entre la lógica y la interfaz de la aplicación y como todos sabemos, eso nunca es bueno. Para solventar este tipo de cosas usaremos un Converter junto con el Binding. Vayamos por partes.

Usaremos el converter para transformar el valor de una propiedad en otro. En nuestro caso, transformaremos el valor booleano de Borrowed por el valor Visible de la interfaz. Para ello crearemos una clase que implemente la interfaz IValueConverter. Esta interfaz consta de dos métodos, Convert y ConvertBack. En nuestro caso vamos a implementar únicamente el Convert, ya que el ConverBack se usará en sentido inverso (de interfaz a lógica).

   1: public class BorrowedToVisibilityConverter : System.Windows.Data.IValueConverter
   2:     {
   3:         #region IValueConverter Members
   4:  
   5:         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
   6:         {
   7:             Visibility isVisible = Visibility.Collapsed;
   8:             if ((value == null))
   9:                 return isVisible;
  10:             if (((bool)value))
  11:             {
  12:                 isVisible = Visibility.Visible;
  13:             }
  14:             return isVisible;
  15:         }
  16:  
  17:         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  18:         {
  19:             throw new NotImplementedException();
  20:         }
  21:  
  22:         #endregion


Dentro de Convert interesa el parámetro value (lo que vamos a usar para convertir). El resto de parámetros no interesa en este caso, pero no es necesario explicarlos. Como se puede apreciar el código es sencillo, si el valor es positivo se devuelve Visibility.Visible y en cualquier otro caso se devuelve Visibiliy.Collapsed. Ahora teniendo esto ya implementado, tenemos que asociarlo a nuestro binding y por ello, a partir de este momento nos dedicamos ya con el fichero XAML correspondiente.

Ante de asociarlo, tenemos que mapear este conversor como un recurso. Para ello simplemente le asignamos una clave de referencia y especificamos el nombre del conversor:

   1: <UserControl.Resources>
   2:          <converter:BorrowedToVisibilityConverter x:Key="BorrowedToVisibility" />
   3: </UserControl.Resources>

Y por último lo asociamos a nuestro binding. En este caso irá en un botón en el su propiedad Visibility dependerá del valor de la propiedad Borrowed del binding asociado al template. Es aquí cuando ya debemos especificar el conversor de forma explícita:

   1: <Button Content="Ok!" Height="72" Width="160"  Visibility="{Binding Borrowed, Converter={StaticResource BorrowedToVisibility} }" HorizontalAlignment="Right" Click="Button_Click" Tag="{Binding Id}" Background="White" BorderBrush="Black" Foreground="Black" />

Y más en detalle:

   1: Visibility="{Binding Borrowed, Converter={StaticResource BorrowedToVisibility} }" 

Asociamos la propiedad del objeto y le indicamos que tenemos un conversor, incluido como recurso estático y su clave de referencia.