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:
-
Bindear los datos del campo con el identificador de la identidad.
-
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.
-
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:
- <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:
- public class MyIDConverter : IValueConverter
- {
- public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
- {
-
- List<ComboBoxItem> list = new List<ComboBoxItem>();
- foreach (B b in B.All())
- {
- ComboBoxItem comboItem = new ComboBoxItem();
- comboItem.Content = b.Name;
- comboItem.Tag = b;
- list.Add(comboItem);
- }
- return list;
-
- }
-
- public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
- {
- return null;
- }
- }
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:
- <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:
- <DataTemplate>
- <ComboBox>
- <ComboBox.ItemsSource>
- <Binding Converter="{StaticResource ResourceKey=MyIDConverter}">
- </Binding>
- </ComboBox.ItemsSource>
- <ComboBox.SelectedIndex>
- <MultiBinding Converter="{StaticResource ResourceKey=MyIDtoIDConverter}" UpdateSourceTrigger="PropertyChanged">
- <MultiBinding.Bindings>
- <Binding Path="BID" Mode="TwoWay"></Binding>
- <Binding RelativeSource="{RelativeSource Mode=Self}" Path="."></Binding>
- </MultiBinding.Bindings>
- </MultiBinding>
- </ComboBox.SelectedIndex>
- </ComboBox>
- </DataTemplate>
Y ahora la implementación del IMultiValueConverter
- public class MyIDtoIDConverter : IMultiValueConverter
- {
- private ComboBox combo;
- public MyIDtoIDConverter()
- {
- combo = null;
- }
-
- public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
- {
- this.combo = values[1] as ComboBox;
- ComboBoxItem comboItem = null;
- int index = 0;
- foreach (ComboBoxItem item in combo.Items)
- {
- OperationType operationType = item.Tag as OperationType;
- if (operationType.Id == values[0] as int?)
- {
- comboItem = item;
- combo.SelectedIndex = index;
- }
- index++;
- }
-
- return combo.SelectedIndex;
- }
-
- public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
- {
- ComboBoxItem comboItem = this.combo.Items[(int)value] as ComboBoxItem;
- B b = comboItem.Tag as B;
- return new object[] { b.ID };
- }
- }
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.