ResourceDictionary y el soporte para Blend en WPF

La clase ResourceDictionary permite tener un diccionario de recursos para que lo utilicemos en nuestras aplicaciones. Dentro de WPF es normal usar esta clase, ya que como explicamos en otro post anterior (WPF para programadores de Windows Forms 5), estos se pueden sumar o “Merge” con otros diccionarios para tenernos todos centralizados dentro de Application.Current.Resources.

Esta característica para el soporte te temas (Themes) y para tener en un lugar centralizado los recursos de la aplicación es estupendo, pero si alguna vez has hecho un control con soporte para tiempo de diseño, digamos Visual Studio 2008 o Blend, hacer que esto funcione se puede convertir en un infierno. Veamos porque.

Esta clase Application es una clase implementada como un singleton que gestiona el ciclo de vida de una aplicación WPF, únicamente puede haber una instancia por dominio de aplicación. Imaginemos que por un momento tenemos un código parecido a este.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;

namespace ApplicationDesingTime
{
    public class MyCustomControl : ContentControl
    {
        public MyCustomControl()
        {
            Template = (ControlTemplate)App.Current.Resources["Style1"];
        }
    }
}

En el que estamos haciendo un control personalizado que cambia su Template actual para cambiar el árbol visual de controles y tener otro aspecto. Hasta aquí todo bien estamos accediendo al diccionario de recursos que tenemos dentro de nuestra aplicación. Si compilamos y nos llevamos este control que debería de tener este aspecto a blend y lo usamos aparece esto otro.

image

Blend

Esto porque ocurre, si ejecutamos la aplicación funciona perfectamente pero si la visualizamos en Blend resulta que no se visualiza correctamente. Es una lástima que tengamos nuestro control, más o menos complejo, pero no podamos verlo en tiempo de diseño. La clave está en la clase Application. Como antes he comentado la clase Application es única por dominio de aplicación lo que significa que dentro de Blend o Visual Studio existe una clase Application, pero es que resulta que, en el caso de Blend, la clase Application corresponde a la propia aplicación de Blend!!. Esto es un bug o algo así del framework o de Blend, o es que simplemente Blend es un entorno de hosting para nuestros controles y aunque Blend crea instancias de nuestros controles para tiempo de diseño, no es el mismo entorno que cuando se ejecutan.

Y ahora, ¿ya no vamos a tener controles con tiempo de diseño como los que viene con WPF?, claro que sí, pero hay que hacerlo de una manera especial.

Lo primero de todo es que necesitamos alguna manera de saber cuando estamos en blend o cuando estamos ejecutando la aplicación. Esto lo podríamos hacer viendo si el nombre del proceso en el que está mi control es blend.exe, pero si estamos en Visual Studio o en otro programa esto no funcionará, además, WPF tiene una manera más elegante de sabes si estamos en tiempo de diseño o no.

En System.ComponentModel hay una clase System.ComponentModel.DesignerProperties que tiene un método estatico, llamado GetIsInDesignMode que acepta un DependencyObject y te devuelve un booleano que indica si el control está en tiempo de diseño o no. Esto se hace a través de una DependencyProperty atachada que hay en esa clase.

Esta característica también está en Windows Forms, pero a diferencia de Windows Forms, si se consulta esta propiedad en el constructor, esta propiedad está establecida, mientras que en Windows Forms después de crear el objeto el diseñador establece la propiedad de tiempo de diseño.

Ahora que ya sabemos esta información vamos a hacer una clase helper que nos permita acceder a los elementos del ResourceDiccionary desde todos los tiempos.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;

namespace ApplicationDesingTime
{
    public class MyCustomControl : ContentControl
    {
        public MyCustomControl()
        {
            //Template = (ControlTemplate)App.Current.Resources["Ejemplo"];
            Template = TemplateHelper.GetData<ControlTemplate>(this, "Ejemplo");
        }
    }
}

image

Para iniciar un ResourceDictionary hay que establecer la propiedad Source indicando cual es el fichero .xaml que se va a utilizar como origen de datos. Esta propiedad Source es de tipo Uri, en el que hay que establecer el formato de ensamblado y ruta en un formato especial.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows;
using System.Diagnostics;

namespace ApplicationDesingTime
{
    public static class TemplateHelper
    {
        private static ResourceDictionary DesingTimeAppDiccionary;

        public static T GetData<T>(DependencyObject context, string name)
        {
            if (DesignerProperties.GetIsInDesignMode(context))
            {
                if (DesingTimeAppDiccionary == null)
                {
                    DesingTimeAppDiccionary = new ResourceDictionary();
                    try
                    {
                        DesingTimeAppDiccionary.Source = new Uri("/ApplicationDesingTime;component/Dictionary1.xaml", UriKind.RelativeOrAbsolute);
                    }
                    catch (Exception ex)
                    {
                        Trace.WriteLine(ex.ToString());
                    }
                }
                return (T)(object)DesingTimeAppDiccionary[name];
            }
            else
            {
                return (T)(object)App.Current.Resources[name];
            }
        }
    }
}

El formato de la Uri sería algo así.

/{assembly};component/{fichero.xaml}

Espero que esto os sirva en los proyectos de WPF que hagais y si haceis muchos controles con tiempo de diseño es interesante este pequeño truco.

Y por cierto si estais pensando en hacer una clase con xaml y un fichero de codigo trasero, hay que tener en cuenta que después esa clase no puede ser la clase base de otro control que esté compuesto de xaml y un fichero .cs así que esta es la mejor manera de modificar el arbol visual de un control para personalizarlo. Pero de eso hablaremos en otro post.

Deja un comentario

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