Na sequência do post anterior sobre temas e skins, hoje vamos falar um pouco sobre o funcionamento interno destas novas funcionalidades.Tal como acontece com as páginas, os temas são transformados em classes. Cada tema (definido como vimos através de uma pasta no interior da parta especia app_themes) é transformado numa classe derivada de PageTheme. Cada uma das definições existentes nas skins associadas ao theme são transformadas em métodos capazes de aplicar essas definições aos controlos associados. Por exemplo, suponhamos que temos uma tema designado de Theme1 que possui as seguintes definições de skins no seu interior:
<asp:TextBox runat="server" BorderColor="darkblue" BorderStyle="solid" BorderWidth="1" />
<asp:Button runat="server" BorderColor="darkblue" BorderWidth="1" BorderStyle="Solid" ForeColor="DarkBlue"/>
<asp:TextBox runat="server" BorderColor="red" BorderStyle="solid" BorderWidth="1" SkinID="red" />
<asp:Button runat="server" BorderColor="red" BorderWidth="1" BorderStyle="Solid" ForeColor="red" SkinID="red" />
O primeiro conjunto de definições provém de um ficheiro designado de Default.skin; por sua vez, o segundo conjunto foi introduzido numficheiro designado de red.skin. Como seria de esperar, ambos os ficheiros estão colocados no interior da pasta Theme1. Apesar de estarem situados em ficheiros diferentes, todas as definições dos skins serão "passadas" para o interior da classe que representa o tema. A utilização do .Net Reflector mostra-nos o aspecto da classe:
public class Theme1 : PageTheme
{
// Methods
static Theme1();
public Theme1();
private Control __BuildControl__control2(Control ctrl);
private Control __BuildControl__control3(Control ctrl);
private Control __BuildControl__control4(Control ctrl);
private Control __BuildControl__control5(Control ctrl);
// Properties
protected override string AppRelativeTemplateSourceDirectory { get; }
protected override IDictionary ControlSkins { get; }
protected override string[] LinkedStyleSheets { get; }
// Fields
private static object __BuildControl__control2_skinKey;
private static object __BuildControl__control3_skinKey;
private static object __BuildControl__control4_skinKey;
private static object __BuildControl__control5_skinKey;
private HybridDictionary __controlSkins;
private static bool __initialized;
private string[] __linkedStyleSheets;
}
Cada uma das definições das skins resulta na geração de um método do tipo __BuildControl__XXX que recebe como parâmetro um elemento do tipo Control. O construtor da classe possui código importante para o correcto funcionamento da classe:
public Theme1()
{
this.__controlSkins = new HybridDictionary(4);
this.__linkedStyleSheets = null;
this.__controlSkins[RuntimeHelpers.GetObjectValue(Theme1.__BuildControl__control2_skinKey)] = new ControlSkin(typeof(TextBox), new ControlSkinDelegate(this.__BuildControl__control2));
this.__controlSkins[RuntimeHelpers.GetObjectValue(Theme1.__BuildControl__control3_skinKey)] = new ControlSkin(typeof(Button), new ControlSkinDelegate(this.__BuildControl__control3));
this.__controlSkins[RuntimeHelpers.GetObjectValue(Theme1.__BuildControl__control4_skinKey)] = new ControlSkin(typeof(TextBox), new ControlSkinDelegate(this.__BuildControl__control4));
this.__controlSkins[RuntimeHelpers.GetObjectValue(Theme1.__BuildControl__control5_skinKey)] = new ControlSkin(typeof(Button), new ControlSkinDelegate(this.__BuildControl__control5));
if (!Theme1.__initialized)
{
Theme1.__initialized = true;
}
}
Todas as skins são armazenadas internamente num dicionário em que a chave é obtida à custa das propriedades estáticas __BuildControl__controlXXX_skinKey. Os valores destas propriedades são definidos através do construtor estático injectado pela plataforma:
static Theme1()
{
Theme1.__initialized = false;
Theme1.__BuildControl__control2_skinKey = RuntimeHelpers.GetObjectValue(PageTheme.CreateSkinKey(typeof(TextBox), ""));
Theme1.__BuildControl__control3_skinKey = RuntimeHelpers.GetObjectValue(PageTheme.CreateSkinKey(typeof(Button), ""));
Theme1.__BuildControl__control4_skinKey = RuntimeHelpers.GetObjectValue(PageTheme.CreateSkinKey(typeof(TextBox), "red"));
Theme1.__BuildControl__control5_skinKey = RuntimeHelpers.GetObjectValue(PageTheme.CreateSkinKey(typeof(Button), "red"));
}
Como é possível verificar, a chave será sempre definida à custa do tipo de controlo e do valor fornecido à propriedade SkinID. O dicionário construído será utilizado pela classe Page de forma a aplicar correctamente as definições existentes em cada skin. A altura em que a skin é aplicada depende do tipo de tema utilizado. Por exemplo, se recorrermos à propriedade Theme, então a aplicação dos temas será efectuada entre os eventos PreInit e Init.
Entre esses dois eventos a classe Page começa por inicializar os temas através da execução do método InitializeTheme. A principal função deste método é compilar os conteúdos dos directórios dos temas de forma a que seja possível inicializar o campo _theme da classe Page com uma instância correcta do tema associado à página actual. Após executar este método, a página limita-se a inicializar recursivamente todos os controlos existentes na página. Um dos passos dessa inicialização consiste na invocação do método ApplySkin que, por sua vez, será responsável por executar o método ApplyControlSkin definido pela classe PageTheme através do campo interno _theme previamente inicializado. Como vimos anteriormente, a classe PageTheme é utilizada como classe base de todos os temas obtidos a partir do parsing dos vários directórios e ficheiros contidos no interior da pasta /app_themes.
O método ApplyControlSkin limita-se a percorrer a colecção de skins existente no dicionário (que é inicializado através do construtor da classe derivada) para aplicar todos as skins adequadas. O método ApplyControlSkin é implementado através do seguinte código:
internal void ApplyControlSkin(Control control)
{
if (control == null)
{
throw new ArgumentNullException("control");
}
ControlSkin skin1 = null;
string text1 = control.SkinID;
skin1 = (ControlSkin) this.ControlSkins[PageTheme.CreateSkinKey(control.GetType(), text1)];
if (skin1 != null)
{
skin1.ApplySkin(control);
}
}
A única finalidade da classe ControlSkin é permitir a execução de um método que efectua a aplicação das definições associadas a uma determinada skin. Se repararem no código utilizado no construtor da classe Theme1 podem verificar que cada elemento deste tipo recebe uma referência a um controlo e a um método existente na página. Esse método será invocado directamente através do método ApplySkin definido pela classe ControlSkin para aplicar os estilos definidos numa determinada skin (portanto, no excerto anterior o método ApplySkin irá executar um dos métodos __BuildContro__XXX definido pela classe Theme1). Já agora, vamos apresentar um "dump" do código gerado para um dos métodos __BuildControl__XXX responsáveis pela aplicação do tema:
private Control __BuildControl__control2(Control ctrl)
{
Unit unit1;
TextBox box1 = (TextBox) ctrl;
box1.BorderColor = Color.DarkBlue;
box1.BorderStyle = BorderStyle.Solid;
unit1 = new Unit((double) 1, UnitType.Pixel);
box1.BorderWidth = unit1;
return box1;
}
Portanto, todas as definições introduzidas numa skin são simplesmente copiadas para o controlo (o processo de parsing utilizado é muito semelhante ao aplicado a uma página aspx!). Cada método __BuildControl__XXX limita-se a efectuar o parsing dos atributos definidos nas tags e transformá-los em atribuições a propriedades desses elementos.
Bom, para terminar falta falar acerca da aplicação dos temas através da propriedade StyleSheetTheme. A aplicação de um tema através da propriedade StyleSheetTheme é feita na construção do controlo. Por exemplo, se olharmos para o código gerado a partir do parsing efectuado a uma página aspx, vamos encontrar algo semelhante ao seguinte.
private TextBox __BuildControlnome()
{
TextBox box1 = new TextBox();
this.nome = box1;
box1.ApplyStyleSheetSkin(this);
box1.BorderColor = Color.Red;
box1.ID = "nome";
return box1;
}
Não sei se já repararam na linha box1.ApplyStyleSheetSkin! Este método (definido pela classe Control) limita-se a tentar obter uma referência para a página actual e a invocar o método ApplyControlStyleSheet defino por essa página. O método tem o seguinte aspecto:
internal bool ApplyControlStyleSheet(Control ctrl)
{
if (this._styleSheet != null)
{
this._styleSheet.ApplyControlSkin(ctrl);
return true;
}
return false;
}
Se tivermos em conta que a variável _styleSheet é do tipo Theme facilmente se depreende que a partir desta altura o processo de aplicação de estilo é igual ao explicado anteriormente para a variável _theme.
Como é possível verificar, a diferença de comportamento relacionada com a forma como os estilos são aplicados deve-se à altura em que são aplicados os estilos definidos nas skins! Em ambos os casos as definições são copiadas para o controlo (desde que eixsta uma skin defina uma determinada apresentação para esse controlo). Se utilizarmos a propriedade Theme, as definições da skin só serão copiadas após o evento PreInit (que por sua vez ocorre após ser executada a função responsável pela construção do controlo na página aspx). Por outro lado, a utilização do atributo StyleSheetTheme faz com que a aplicação das definições seja feita antes da atribuição das definições efectuadas na tag do controlo na página aspx (como podemos comprover pelos excertos de código anteriores).
Apesar de ter sido escrito em apenas 10 minutos, espero que este post tenha conseguido apresentar os principais aspectos relacionados com o funcionamento interno dos temas e skins :)
posted on Monday, May 16, 2005 11:38 PM