Silverlight: Binding de textboxs a propiedades nullables
13 Aug 2010No descubro nada si digo que Silverlight tiene un magnífico sistema de binding, con el que nos podemos ahorrar muchísimo “code behind”. Sin embargo, me he encontrado un extraño comportamiento cuando se combina con propiedades nulables, como serían int?. Mi escenario era el siguiente:
- Un textbox bindeado a una propiedad int? en una entidad que era el data context de mi control
- Un botón que, al pulsarlo, lanzaba de forma manual el checkeo de los bindings de todos los textboxs, incluido el anterior (método UpdateSource del binding)
- Todos los métodos set de las propiedades son correctamente invocados, menos el de mi propiedad nulable.
¿Por qué? Además, como corolario, todos aquellos que no estaban correctamente formateados, se marcaban con el correspondiente borde rojo, incluido el que aparentemente no había llegado a checkearse. Y por si esto no fuera suficiente misterio, el mensaje que acompañaba al borde rojo, aparentemente había salido de ninguna parte; yo no lo había definido.
MISTERIO DESENTREAÑADO
Después de buscar bastante por Internet, llegué a esta página en la que explicaban el porqué de la situación. Al parecer, los bindings de Silverlight no se actualizan cuando no son capaces de deducir si el valor del control, es “casteable” a la propiedad que se está intentando asignar.
En mi caso, el textbox, al estar vacío, provocaba que el binding no supiera convertir un string.Empty a un int?, algo lógico por otra parte. De ahí que el método set de la propiedad nunca llegara a llamarse. La última pieza del misterio, el mensaje salido de la nada, era responsabilidad del propio binding, avisando de que no es capaz de hacer la conversión.
La solución
Ahora que ya sabemos el porqué, lo mejor es saber cómo resolverlo. Es fácil, necesitamos que “algo” convierta los valores que el binding no va a ser capaz de resolver, a valores que sí sean asignables a nuestras propiedades nulables. Por ejemplo, que cada string.Empty o puñado de caracteres vacíos, se conviertan en un null, algo con lo que el binding sí va a poder manejarse y setear en nuestra propiedades. En definitiva, necesitamos un Converter. Un ejemplo de uno totalmente funcional lo tenéis a continuación:
/// <summary> /// Class that converts a value from an UI control to a nullable property value. /// </summary> /// <remarks>This class intents to resolve a "gap" in the Silverlight binding model. /// Currently, the binding model is not able to invoke the setter of a nullable property, if /// the value inside the control is empty. /// Silverlight binding mechanism always avoids to invoke a setter if it is not sure how to /// represent the type of the property to which is binded. For example, in a textbox with an empty /// value for Text property, it will not set this value to a int? property in the underlying model. /// </remarks> public class NullableValueConverter : IValueConverter { #region IValueConverter Members /// <summary> /// Converts a value from the underlying entity into its UI control representation. /// </summary> /// <param name="value">Value to convert.</param> /// <param name="targetType">Target type.</param> /// <param name="parameter">Conversion parameter.</param> /// <param name="culture">Culture to use in the conversion.</param> /// <returns>Object converted.</returns> public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return value; } /// <summary> /// Converts a value from a UI control to its representation in the underlying entity. /// </summary> /// <param name="value">Value to convert.</param> /// <param name="targetType">Target type.</param> /// <param name="parameter">Conversion parameter.</param> /// <param name="culture">Culture to use in the conversion.</param> /// <returns>Object converted.</returns> public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string castedValue = (string)value; if (string.IsNullOrEmpty(castedValue) == true || castedValue.Trim().Length == 0) { return null; } return value; } #endregion }
Y ya lo único que nos faltaría sería utilizarlo en nuestro código XAML, en dos pasos. El primero, definirlo como recurso dentro del XAML en que vamos a usarlo:
<UserControl.Resources> <controls:NullableValueConverter x:Key="NullableValueConverter"/> </UserControl.Resources>
Y el segundo, asociándolo al binding que hemos establecido entre el control y la propiedad nulable.
Text="{Binding MyNullableProperty, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, Converter={StaticResource NullableValueConverter}}"
Y esto es todo. Espero que os sea de utilidad.