Tag Archives: Silverlight

WebBrowser + Mango = IE9

El otro día me llegó al correo una pregunta que, como de costumbre, no supe responder. Me preguntaban si podíamos esperar que WebBrowser utilizara IE9 como navegador en Mango.

Lo primero que me sugería la lógica es que sí. WebBrowser había estado utilizando IE7 hasta ahora y, puesto que Mango trae como una de sus principales novedades IE9 como navegador, lo normal era esperar que fuera así.

Rebuscando por MSDN (ese sitio donde está toda la información pero es casi imposible encontrar nada), encontré un pequeño artículo sobre los cambios e incompatibilidades de Mango en el que, explícitamente, citaban a WebBrowser y decían lo siguiente (traducción de cosecha propia):

El control WebBrowser para Windows Phone se ha actualizado para soportar Internet Explorer 9. Las aplicaciones que usen este control deberían re-testearse para asegurar su compatibilidad con Internet Explorer 9.

Parecía suficiente, pero ya que tengo instalado Mango en el teléfono, decidí probarlo por mí mismo. Creé un proyecto en el que simplemente incrusté un WebBrowser y una caja de texto para meter una dirección. El resultado lo podéis en la siguientes imágenes, primero navegando a http://user-agent-string.info/parse para conseguir información sobre el User Agent String que está enviando el control WebBrowser.

MangoBrowser_7-31-2011_13.10.33.888

Y, en esta otra, navegando al test ACID3, que aunque no valga de mucho por ser muy poco exhaustivo, sí nos sirve para diferenciar entre IE7 e IE9.

MangoBrowser_ACID3_7-31-2011_13.14.50.684

En definitiva, WebBrowser en Mango SÍ funcionará sobre Internet Explorer 9.

Silverlight–FallbackValue y TargetNullValue

En estos días me encuentro inmerso en un proyecto con Silverlight. Uno de los últimos problemas que me he encontrado involucraba el siguiente escenario:

  1. Control cuya visibilidad está enlazado a una propiedad de un objeto (en este caso, un campo del Content de un NavigationFrame)
  2. Este frame, en un primer momento, tiene null en esta propiedad Content, hasta que navega.

El problema es que esta navegación se producía un instante después de que el control se creara, por lo que durante ese instante, el binding fallaba y la propiedad de visibilidad se establecía a Visible, que supongo que es el valor por defecto. Después de darle muchas vueltas encontré estas dos propiedades interesantes para este tipo de escenarios.

FallbackValue

Nos sirve para establecer un valor para el binding, cuando el binding no es capaz de resolverse y obtener un valor. Por ejemplo, en mi escenario, hasta que el NavigationFrame no navegaba por primera vez a alguna pantalla, su Content era null. Al estar mi binding enlazado a una de las propiedades del objeto que esperaba encontrar en Content, y ser éste un null, internamente el Binding estaba fallando con NullReferenceException.

Para esta situación de fallo, FallbackValue es perfecta. Básicamente estás diciéndole al binding: “si no eres capaz de calcular un valor, ponme éste directamente”.

TargetNullValue

Esta otra propiedad nos ayuda en otro escenario típico: el valor al que estamos enlazando, es null. Si por ejemplo la visibilidad de nuestro control dependiera de una propiedad string con valores “VISIBLE” y “NOVISIBLE”, podríamos crear un sencillo conversor que se encargara de devolver Visibility.Visible para el primer valor, y Visibility.Collapsed para el segundo.

¿Qué pasaría si la propiedad string contuviera un null? En tal caso, podríamos o bien modificar nuestro conversor para devolver Visibility.Collapsed también con un valor null. Pero, ¿y si no hemos creado ningún conversor? ¿No es un poco tedioso tener que crear uno sólo para gestionar los valores null?

Para eso precisamente existe TargetNullValue, que nos permite indicarle al Binding qué valor por defecto queremos que se asigne a la propiedad bindeada, en caso de que el origen del binding (source) tenga un valor null.

 

Espero que le resulte útil a alguien. A mí ya me hizo perder una hora y pico…

 

Bibliografía

MSDN: BindingBase.FallbackValue

MSDN: BindingBase.TargetNullValue

Silverlight: Binding de textboxs a propiedades nullables

No 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.

Atributo InternalsVisibleTo

El atributo InternalsVisibleTo puede resultarnos muy útil en situaciones en las que tengamos la necesidad de acceder, desde un ensamblado, a los miembros Internal de las clases de otro ensamblado.

Un ejemplo que me he encontrado últimamente de su uso es en el testeo de aplicaciones Silverlight. Por desgracia, Silverlight no tiene ningún framework de testeo que sea capaz de generar accessors, probablemente porque Reflection no funciona para campos privados en Silverlight.

Para sustituir esta carencia, en algunos casos podremos marcar la visibilidad de miembros que queramos testear como Internal en lugar de Private y, haciendo uso de InternalsVisibleTo, acceder a ellos desde los correspondientes tests. ¿Cómo lo lograríamos? Son unos pocos pasos:

Primer paso: obtener la clave pública

Necesitamos conocer la clave pública de firmado del ensamblado al que queremos conceder el privilegio de poder acceder a los miembros internal. Para ello, en una consola de Visual Studio, deberemos introducir el siguiente comando:

sn -Tp c:UsersjavierprojectsMyProjectDllToGrantAccessToInternals.dll

De la salida que nos genera este comando, nos la copiamos para hacer el siguiente paso.

Segundo paso: definir el atributo

Ahora que ya conocemos la PublicKey del ensamblado (no confudir con la PublicKeyToken que aparece en los qualified names), necesitamos definir el atributo en el fichero AssemblyInfo del proyecto. En dicho fichero deberemos añadir algo como lo siguiente:

[assembly: InternalsVisibleTo("DllToGrantAccessToInternals.dll, PublicKey=0024000004…)]

 

¿Sencillo no? Con esto ya podríamos ver los miembros Internal del ensamblado en que hemos definido el atributo, desde el ensamblado DllToGrantAccessToInternals.

Silverlight: Watermarked control

Mi experiencia con Silverlight no está siendo todo lo placentera que me gustaría. Me estoy encontrado permanentemente en la necesidad de cambiar las cosas que presume por defecto esta tecnología, para adecuarla a mis necesidades.

Una de ellas ha sido la creación de un control Textbox con marca de agua. Buscando un poco por Internet el primer enlace redirige al blog de Tim Heuer, donde tenemos un ejemplo de implementación de este control.

El problema se plantea cuando se quiere utilizar este control para usarlo como control de password. No nos ofrece la posibilidad de enmascarar los caracteres. La solución podría ser algo como esto (no recuerdo si se me ocurrió a mí, lo leí por ahí o adapté algo que hubiera encontrado):

/// <summary>
/// Changes the visual state when the control changes its text.
/// </summary>
/// <param name="sender">Sender of the event.</param>
/// <param name="e">Event arguments.</param>>
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
    if (this.IsPasswordBox == true)
    {
        TextBox textBox = sender as TextBox;
        if (textBox != null)
        {
            string newText = textBox.Text;

            // If we don't have text, clean the plain text back up.
            if (string.IsNullOrEmpty(newText) == true)
            {
                this.Plaintext = string.Empty;
                return;
            }

            // If the new text is shorter than the previous text, we simply cut the previous text
            if (newText.Length < this.Plaintext.Length)
            {
                this.Plaintext = this.Plaintext.Substring(0, newText.Length);
                return;
            }

            // We have an string longer than the previous one. We can have a new character in any place
            // inside the text, so we go along the string to find the new characters (probably only one).
            // This new character will not be the "hidden" character
            StringBuilder newBackup = new StringBuilder();

            // We will use the offset because every time we find a character in the new text that is clear,
            // it is a new character. For example, if the new character is in the 5º position, and the next
            // 6º character is hidden, it would be in the 5º position of the old text. So we use the offset
            // to count in which place the character is in the old text.
            int offset = 0;
            for (int i = 0; i < newText.Length; i++)
            {
                if (newText[i] == 'u25CF')
                {
                    // We add this character from the original plain text to the new one
                    newBackup.Append(this.Plaintext[i + offset]);
                }
                else
                {
                    // We add this character from the new text, because it's one of the new characters
                    newBackup.Append(newText[i]);

                    // We must update the offset
                    offset--;

                    // We must change this character in the new text for the hidden character.
                    newText = newText.Substring(0, i) + 'u25CF' + newText.Substring(i + 1, newText.Length - i - 1);
                }
            }

            // Set the masked text in the control
            textBox.Text = newText;

            // Move the caret to the end of the text
            textBox.SelectionStart = textBox.Text.Length;

            // Update the backup plain text
            this.Plaintext = newBackup.ToString();
        }
    }

    this.ChangeVisualState(true);
}

Por último, quedaría la validación del control. Por desgracia para los que no estamos muy informados sobre cómo funciona Silverlight, el ejemplo de Tim no incluía nada relacionado con la validación del control. Es decir, la validación en sí se realiza con mecanismos ajenos al control, como los Data Annotations. El problema es que, una vez validado el control y encontrado que no es correcta su información, es necesario que pase a un estado “No Válido” y que muestre alguna forma de error.

Después de darle un poco de vueltas y echarle un vistazo a estilos de otros controles por defecto de Silverlight, ésta es la solución que me está funcionando a las mil maravillas (esta vez, sí, cosecha propia 100%).

En primer lugar, el estilo para el control. Me voy a limitar a mostrar las partes directamente relacionadas con la validación, puesto que al final del artículo adjunto tanto el estilo del control como el código del mismo.

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup x:Name="ValidationStates">
        <VisualState x:Name="Valid"/>
        <VisualState x:Name="InvalidUnfocused">
            <Storyboard>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0">
                        <DiscreteObjectKeyFrame.Value>
                            <Visibility>Visible</Visibility>
                        </DiscreteObjectKeyFrame.Value>
                    </DiscreteObjectKeyFrame>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>
        <VisualState x:Name="InvalidFocused">
            <Storyboard>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0">
                        <DiscreteObjectKeyFrame.Value>
                            <Visibility>Visible</Visibility>
                        </DiscreteObjectKeyFrame.Value>
                    </DiscreteObjectKeyFrame>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsOpen">
                    <DiscreteObjectKeyFrame KeyTime="0">
                        <DiscreteObjectKeyFrame.Value>
                            <System:Boolean>True</System:Boolean>
                        </DiscreteObjectKeyFrame.Value>
                    </DiscreteObjectKeyFrame>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="ValidationErrorElement"
        Visibility="Collapsed"
        BorderBrush="#FFDB000C"
        BorderThickness="1"
        >
    <ToolTipService.ToolTip>
        <ToolTip x:Name="validationTooltip" Template="{StaticResource CommonValidationToolTipTemplate}"
            DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Placement="Right"
            PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}">
            <ToolTip.Triggers>
                <EventTrigger RoutedEvent="Canvas.Loaded">
                    <BeginStoryboard>
                        <Storyboard>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsHitTestVisible">
                                <DiscreteObjectKeyFrame KeyTime="0">
                                    <DiscreteObjectKeyFrame.Value>
                                        <System:Boolean>true</System:Boolean>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </ToolTip.Triggers>
        </ToolTip>
    </ToolTipService.ToolTip>
    <Grid Height="12" HorizontalAlignment="Right"
          Margin="1,-4,-4,0" VerticalAlignment="Top"
          Width="12" Background="Transparent">
        <Path Fill="#FFDC000C" Margin="1,3,0,0" Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z"/>
        <Path Fill="#ffffff" Margin="1,3,0,0" Data="M 0,0 L2,0 L 8,6 L8,8"/>
    </Grid>
</Border>

A destacar especialmente que hay dos partes claramente diferenciadas en el estilo:

  • La parte en la que se definen el grupo de estados “ValidationStates”, formado por los estados “Valid”, “InvalidUnfocused” y “InvalidFocused”. Con estos tres cubriríamos todas las posibilidades relacionadas con el estado del control y su validación.
  • La otra parte relevante del estilo es la definición de un elemento “ValidationErrorElement”, que junto al tooltip nos permite marcar, en este caso en rojo, el control que ha fallado y mostrar un mensaje con información del error.

La segunda parte de este truco consiste en definir las transacciones correspondientes entre los estados del control así como dispararlas ante eventos concretos que acontezcan en el control. En este caso, es necesario definir una propiedad “isErroneous” para indicar cuándo el control ha pasado al estado erróneo. Esta propiedad la controlaremos conectándonos al evento BindingValidationError, tal como se muestra en el siguiente extracto del código del control:

/// <summary>
/// Indicates if the control is in an erroneous state.
/// </summary>
private bool isErroneous;

/// <summary>
/// Initializes a new instance of the <see cref="WatermarkedTextBox"/> class.
/// </summary>
public WatermarkedTextBox()
{
    this.BindingValidationError += new EventHandler<ValidationErrorEventArgs>(WatermarkedTextBox_BindingValidationError);
}

El código que vamos a ligar al evento es tan sencillo como lo que podemos ver a continuación:

/// <summary>
/// Configures the properties of the control to indicate it has an error.
/// </summary>
/// <param name="sender">Sender of the event.</param>
/// <param name="e">Event arguments.</param>
private void WatermarkedTextBox_BindingValidationError(object sender, ValidationErrorEventArgs e)
{
    this.isErroneous = e.Action == ValidationErrorEventAction.Added ? true : false;
    this.ChangeVisualState(true);
}

Controlando la acción que acompaña como argumento al evento, podremos saber si la función ha saltado porque el control ha entrado o ha salido del estado erróneo. Toda la lógica, y por tanto lo relevante, queda relegado al método ChangeVisualState. Este método controlará las transacciones entre los distintos estados en los que puede estar el control. Un extracto de este método, específico para la parte de validación de errores, en el siguiente fragmento:

/// <summary>
/// Change to the correct visual state for the textbox.
/// </summary>
/// <param name="useTransitions">
/// true to use transitions when updating the visual state, false to
/// snap directly to the new visual state.
/// </param>
private void ChangeVisualState(bool useTransitions)
{

    // Update the ValidationStates group
    if (this.hasFocus && this.isErroneous)
    {
        VisualStateHelper.GoToState(this, useTransitions, VisualStateHelper.InvalidFocused, VisualStateHelper.InvalidUnfocused);
    }
    else if (this.isErroneous)
    {
        VisualStateHelper.GoToState(this, useTransitions, VisualStateHelper.InvalidUnfocused);
    }
    else
    {
        VisualStateHelper.GoToState(this, useTransitions, VisualStateHelper.StateValid);
    }
}

Cómo se puede ver, dependiendo de si el control tiene foco o no, se pasa a los distintos estados inválidos en caso de existir errores. Caso de no haberlos, se pasa a un estado de validez que desactiva los adornos de estilo informativos.

El resultado es un control que se comporta igual que los textbox por defecto de Silverlight, a pesar de ser totalmente personalizado. En cualquier caso, cambiar el aspecto que mostrará al entrar en estado erróneo es tan sencillo como modificar el estilo.

Ejemplo

Descargas:

PD: Perdonad los comentarios en inglés en el código, pero es para un proyecto de la empresa en la que trabajo y la pereza me impide ponerme a traducirlo todo :)

Desarrollando con Silverlight: Data binding

Unos cuantos apuntes rápidos sobre Silverlight y Data binding, más a título personal (para no olvidarme de estos recursos y estas conclusiones) que con ánimo de salvarle la vida a nadie (no he conseguido ni arreglar mis propios problemas…).

En primer lugar, este artículo en MSDN sobre Data Binding. Algunas de las conclusiones que he podido sacar son:

  • Usar la propiedad DataContext de los objetos para enlazarlos con objetos del CLR (sources). Permite herencia hacia abajo y también se hereda de los controles padre, por lo que si no queremos que un objeto tenga el mismo valor que su padre, habrá que sobreescribirla.
  • La propiedad ElementName nos vale para enlazar a otros controles XAML en lugar de a un objeto del CLR
  • La propiedad RelativeSource para servir para enlazar a elementos en un Control Template.
  • Es necesario implementar INotifyPropertyChanged en los objetos que queramos que funcionen como sources, si queremos tener TwoWay en el binding. Si lo queremos hacer con una colección, tendrá que implementar INotifyCollectionChanged. En cualquier caso, la colección ObservableCollection<T> ya lo hace, con lo que es una buena opción para no reinventar la rueda.
  • Si queremos que nos aparezcan esos cartelitos rojos tan chulos con los mensajes de error que hayamos definido (aquí entra en juego los Data Annotations, estupendos para poder hacer validaciones más finas), tenemos que marcar a true las propiedades ValidatesOnException y NotifyOnValidationError del binding, en el fragmento XAML.

El punto en que me encuentro ahora es que tengo una validación correcta, pero al estar usando un control con su propio manejo de los estados, no es capaz de mostrar los errores (más bien, imagino que pasar a un estado “erróneo” que, por defecto en SL, tendrá asociada una transición que hace aparecer la caja con el error y demás artificios visuales).

Enlaces sobre los que trabajar:

Tan pronto resuelva el problema, publicaré puntualmente.

Debugger in Silverlight

Este fin de semana perdí una cantidad considerable de tiempo porque no me funcionaba el debugger en Silverlight. Básicamente, me salían esos simpáticos mensajes que aparece en Visual Studio cuando un breakpoint no se va a ejecutar:

The break point currently will not be hit , no symbols have been loaded to this document

¿Por qué? ¡¿Por qué?! Me preguntaba yo, intentando depurar una aplicación que estaba comportándose de forma incorrecta. Pues bien, la solución es tan sencilla, de esas que por suerte no se olvidan. Basta con activarlo dirigiéndose a las propiedades del proyecto ASP.NET que estará hosteando nuestro control Silverlight.

En las propiedades, bajo la pestaña Web, en la parte inferior de la pantalla se puede ver un menú como el de la siguiente imagen. Basta activar el checkbox correspondiente a Silverlight para que nuestro depurador favorito funcione de nuevo correctamente.

SilverlightDebugger

Espero que esto le sirva a alguien para perder menos tiempo del que perdí yo.