[Windows Phone 7.5] Creando nuestros propios Behaviors

Hola a todos!

En el último artículo sobre Expression Blend (puedes verlo aquí) hablamos sobre animaciones y como usar los behaviors incluidos en Silverlight para ejecutarlas. Pudimos ver como existen diferentes tipos de behaviors, pero no vimos como poder crear los nuestros propios para poder usarlos en nuestras aplicaciones.

Behavior<T>

Dentro del ensamblado System.Windows.Interactivity tenemos una clase llamada Behavior<T> de la cual podemos heredar para crear un behavior personalizado. La estructura mínima es la siguiente:

public class CustomBehavior : Behavior<Grid>
{
    protected override void OnAttached()
    {
        base.OnAttached();
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
    }
}

Con el código anterior indicamos que queremos crear un Behavior llamado CustomBehavior que podrá ser usado en elementos de tipo Grid. El método OnAttached se ejecutará cuando el behavior se añada al elemento Grid y el OnDetaching cuando se descargue del elemento. De esta forma podremos ejecutar el código que deseemos y tendremos una forma de limpiar las referencias, eventos, y todo lo necesario cuando el Behavior ya no sea necesario.

Un ejemplo práctico: Extendiendo el control WebBrowser

El control WebBrowser incluido en el SDK de Windows Phone es muy versátil para mostrar contenido HTML, incluso contiene un método llamado NavigateToString que nos permite mostrar en el control una cadena de texto con formato HTML. El problema que tiene es que no existe una propiedad que podamos usar desde XAML directamente para mostrar el contenido de una propiedad, siempre necesitamos usar Code Behind para atacar a este método, algo parecido a esto:

<Grid x:Name="LayoutRoot" Background="Transparent">       
    <phone:WebBrowser Name="webControl"></phone:WebBrowser>
</Grid>
public MainPage()
{
    InitializeComponent();

    webControl.NavigateToString("<BODY>Hola!</BODY>");
}

Esto es muy engorroso si estamos usando un patrón MVVM, además de romper la regla de no usar Code Behind, tenemos que detectar los cambios en nuestro ViewModel y de alguna forma invocar código en nuestro code behind o tener una referencia al control directamente en nuestra ViewModel, lo cual es aun peor.

¿Como lo resolvemos? Usando un Behavior!

Lo primero que vamos a hacer es crear una nueva clase llamada NavigateToStringBehavior que herede de Behavior, pasando como tipo el control WebBrowser. Dentro de nuestro behavior tenemos una propiedad llamada AssociatedObject, que nos devuelve el objeto al cual hemos asociado nuestro behavior, con todas las propiedades del tipo indicado:

public class NavigateToStringBehavior : Behavior<Microsoft.Phone.Controls.WebBrowser>
{
    /// <summary>
    /// Execute when attached to a parent object.
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Loaded += AssociatedObject_Loaded;
    }

    /// <summary>
    /// Execute our behavior actions!
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
    {
    }

    /// <summary>
    /// Execute when detached from a parent object
    /// </summary>
    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.Loaded -= AssociatedObject_Loaded;
    }
}

En este caso usamos el método OnAttached para manejar el evento Loaded de nuestro WebBrowser, mediante la propiedad AssociatedObject y el método OnDetaching para eliminar el manejador creado de forma que no queden dependencias y se pueda liberar correctamente toda la memoria al destruir el WebBrowser.

Ahora necesitamos algún mecanismo que nos permita indicarle a nuestro behavior cual es el contenido que deseamos mostrar, además queremos que este contenido pueda ser indicado mediante un Binding a una propiedad de nuestra ViewModel, la solución: Añadir una DependencyProperty a nuestro behavior:

private static readonly DependencyProperty HtmlContentProperty = DependencyProperty.Register("HtmlContent", typeof(string),
                                                                                                typeof(NavigateToStringBehavior), 
                                                                                                null);
public string HtmlContent 
{
    get { return (string)this.GetValue(HtmlContentProperty); }
    set { this.SetValue(HtmlContentProperty, value); }
}

Para ello creamos una propiedad pública en nuestra clase NavigateToStringBehavior de tipo string llamada HtmlContent y a continuación una propiedad de tipo DependencyProperty que será la encargada de almacenar nuestro valor.

Ahora solo nos queda añadir código al manejador de eventos AssociatedObject_Loaded para que obtenga el valor de la propiedad HtmlContentProperty y llame al método NavigateToString del WebBrowser asociado:

/// <summary>
/// Execute our behavior actions!
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
    AssociatedObject.NavigateToString(HtmlContent);
}

El código simplemente llama al método NavigateToString pasando como argumento la propiedad HtmlContent, que a su vez obtendrá el valor de la DependencyProperty HtmlContentProperty evaluando la expresión de enlace a datos (binding) si existe.

Usando el fruto de nuestro trabajo

Ahora vamos a crear una clase llamada HtmlViewModel en nuestra aplicación que imitará a una ViewModel real y expondrá dos propiedades de tipo string:

public class HtmlViewModel
{
    public string Html1 { get; set; }

    public string Html2 { get; set; }

    public HtmlViewModel()
    {
        Html1 = @"<BODY bgcolor='#AAAAFF' text='#003300'>
                    <strong>Hola!</strong>
                  </BODY>";

        Html2 = @"<BODY bgcolor='#FFAAAA' text='#003300'>
                    <strong>Adios!</strong>
                  </BODY>";
    }
}

En estas dos propiedades hemos introducido código HTML para imitar el posible HTML que podríamos tener guardado de un Rss o una página web.

Vamos a abrir Expression Blend (Haciendo click derecho en nuestra página MainPage.xaml y escogiendo la opción “Open in Expression Blend”).

He creado un layout con dos WebBrowsers en la página:

image

 

Lo primero que necesitamos hacer es indicarle a nuestra página que debe usar la clase HtmlViewModel como su DataContext. Para ello seleccionamos en la pestaña de objetos nuestra PhoneApplicationPage y en las propiedades buscamos el DataContext, donde encontraremos el botón New, al presionarlo abrirá una ventana para seleccionar nuestra fuente de datos:

image

Después de seleccionar la  clase HtmlViewModel presionamos OK. Veremos una lista de las propiedades que expone nuestra clase, Html1 y Html2, ahora seleccionamos el primer WebBrowser y vamos a la pestaña Assets y a los Behaviors, donde veremos que está incluido nuestro NavigateToStringBehavior:

image

Solo tenemos que seleccionarlo y arrastrarlo sobre nuestro primer WebBrowser. Si intentamos añadirlo a la grid o a la página, veremos que nos indica un mensaje de que no es un objetivo válido. una vez añadido al primer WebBrowser, en las propiedades del Behavior veremos nuestra DependencyProperty HtmlContent, donde podremos indicar nuestro Binding:

image

Podemos repetir la misma operación con el segundo WebBrowser, añadiendo un nuevo NavigateToStringBehavior y estableciendo el Binding a la propiedad Html2.

Tras hacer esto, si ejecutamos nuestra aplicación veremos el resultado:

image

Y Voila! tenemos nuestro código HTML mostrándose en un WebBrowser sin necesidad de usar Code Behind y con un Behavior reutilizable siempre que lo necesitemos y con soporte a enlace a datos.

Conclusión

Hemos podido ver como crear un Behavior es muy sencillo pero al mismo tiempo muy útil y potente, es una herramienta que nos permitirá crear comportamientos reutilizables, usables desde Expression Blend directamente y evitando usar Code Behind. Como siempre, aquí tenéis el proyecto de ejemplo, espero que os sea de utilidad.

Un saludo y Happy Coding!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *