2
CONCEITOS BÁSICOS
1
A utilização da plataforma Silverlight pressupõe o conhecimento de algumas funcionalidades básicas que serão introduzidas ao longo deste capítulo. Iniciemos, então, este capítulo com uma apresentação da linguagem XAML, para em seguida nos debruçarmos sobre dependency properties, routed events e comandos.
2.1
XAML
XAML (Extensible Application Markup Language) é uma linguagem declarativa usada na construção e inicialização de objectos .NET. A XAML é uma linguagem XML, logo a capitulação dos nomes dos elementos é importante e deve ser respeitada. Uma linguagem declarativa preocupa-se em expressar a lógica de uma determinada computação sem se “prender” aos pormenores associados ao seu fluxo de controlo. Por outras palavras, uma linguagem declarativa indica apenas o que deve ser feito. Por outro lado, uma linguagem imperativa prefere explicitar a forma como a operação deve ser executada.
Esta linguagem introduz alguns termos reservados (designados por directivas) e define as regras que permitem aos parsers interpretarem e transformarem o XML em objectos .NET. Em Silverlight, a XAML é usada (maioritariamente) para definir as interfaces gráficas das aplicações. Um parser é que um componente que analisa o texto e o transforma num conjunto de tokens que são mapeados numa determinada gramática. Neste caso, o parser limita-se a interpretar o texto e a transformar as tags e atributos aí definidos em objectos .NET.
© FCA – Editora de Informática
28
2.1.1
SILVERLIGHT 4.0 – CURSO COMPLETO
ELEMENTOS E ATRIBUTOS
A linguagem XAML define um conjunto de regras que são usadas por um parser para transformar um elemento XML num objecto .NET. Analisemos o excerto XAML seguinte: XAML
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml x:Name=”bt” Content="Ola" />
O excerto anterior cria um novo elemento do tipo Button e é (praticamente) equivalente ao seguinte código C#: C#
Button bt = new Buttton{ Content = “Ola” };
Este exemplo permite-nos deduzir duas coisas: o nome de um elemento XAML tem de ser o mesmo de uma classe .NET (não esquecer a capitulação!) e os atributos definidos no XML são “copiados” para propriedades homónimas do objecto instanciado a partir do elemento XAML.
2.1.2
NAMESPACES
O leitor atento terá, porventura, reparado na utilização de dois namespaces XML (um “global”, com o valor http://schemas.microsoft.com/winfx/2006/-> xaml/presentation e outro associado ao prefixo x - http://-> schemas.microsoft.com/winfx/2006/xaml). O primeiro namespace (global) está associado a vários namespaces .NET definidos pela plataforma Silverlight (através do atributo XmlnsDefinitionAttribute – ver nota seguinte para mais informações). O segundo (associado ao prefixo x) permite a utilização de directivas XAML. É através destes mapeamentos que o parser consegue transformar o XML em objectos .NET e interpretar as directivas XAML. Refira-se ainda que estes namespaces são normalmente definidos no elemento de topo das “páginas” Silverlight, pelo que a sua declaração não é necessária ao nível dos controlos.
© FCA – Editora de Informática
CONCEITOS BÁSICOS
29
2.1.2.1 DIRECTIVAS INTRODUZIDAS PELA XAML
O exemplo inicial ilustra a utilização de uma dessas directivas (Name). A directiva Name permite indicar o nome do campo usado para representar o elemento na classe gerada a partir do ficheiro XAML. No exemplo anterior, o parser adiciona um campo, designado por bt, à classe parcial gerada, permitindo assim o fácil acesso a esse elemento a partir do código C#. Para além da directiva Name, existem ainda outras que são interpretadas pelo parser de Silverlight. A directiva x:Class é usada para indicar o nome da classe parcial obtida à custa do parsing do ficheiro de XAML. Esta directiva é aplicada ao elemento UserControl (elemento de topo de um user control Silverlight) quando pretendemos utilizar ficheiros de code-behind: XAML
<UserControl x:Class="QuickTests.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> </UserControl>
Para além destas, existem ainda outras não apresentadas nestes exemplos. A directiva x:Key é usada para identificar univocamente um elemento criado como recurso num elemento Silverlight (os recursos são apresentados no Capítulo 5 deste livro). Para além destas directivas, este namespace está ainda associado a um conjunto de extensões de markup que serão apresentadas nas próximas secções deste capítulo.
2.1.2.2 UTILIZAÇÃO DE OBJECTOS CRIADOS PELO PROGRAMADOR
Nesta altura, o leitor pode estar a interrogar-se acerca dos tipos de objectos que podem ser definidos através de XAML. A resposta é simples: qualquer objecto .NET que possua um construtor público sem parâmetros pode ser usado a partir de XAML. Para que esse elemento seja correctamente interpretado pelo parser, temos apenas de associar o namespace XML do elemento XAML ao namespace .NET onde a classe foi definida. Por exemplo, suponhamos que criámos o controlo seguinte: C#
namespace QuickTests { public class MyButton:Button { © FCA – Editora de Informática
30
SILVERLIGHT 4.0 – CURSO COMPLETO }
}
A utilização do tipo MyButton em XAML obriga-nos a associar o namespace .NET Quicktests a um prefixo XML: XAML
<my:MyButton xmlns:my="clr-namespace:QuickTests" Content="Ola" />
Como é óbvio, a introdução do prefixo my pode ser efectuada ao nível do elemento de topo de um user control Silverlight que estamos a desenvolver (como vimos no capítulo anterior, normalmente um objecto do tipo UserControl). A utilização de tipos .NET, definidos em assemblies externas, é também possível. Para tal, temos apenas de indicar a assembly através do termo reservado assembly na string do namespace: XAML
<my:MyButton xmlns:my="clr-namespace:QuickTests;assembly=MyAssembly" Content="Ola" />
Importa salientar que os termos reservados (clr-namespace e assembly) e separadores especiais (: e =) devem ser respeitados. A utilização incorrecta dos separadores é um dos erros mais usuais entre os principiantes. A plataforma Silverlight introduz dois atributos que simplificam a interacção com namespaces .NET: XmlnsDefinitionAttribute e XmlnsPrefixAttribute. O primeiro permite associar um namespace XML (identificado por um URI – Uniform Resource Identifier) a todos os namespaces definidos numa assembly. Por sua vez, o segundo define o prefixo usado por predefinição aquando da importação dessa assembly. Dos dois, o primeiro é, sem dúvida, o mais importante. As assemblies Silverlight recorrem a esta estratégia para associar vários namespaces .NET a um mesmo namespace XML. Se abrirmos a assembly System.Windows com um leitor de meta-data (ex.: .NET Reflector), encontramos várias instruções deste tipo: C#
[assembly: XmlnsDefinition( "http://schemas.microsoft.com/winfx/2006/xaml/presentation", "System.Windows")] O excerto anterior efectua o mapeamento do namespace .NET System.Windows no namespace XML © FCA – Editora de Informática
CONCEITOS BÁSICOS
31
http://schemas.microsoft.com/winfx/2006/xaml/presentation. Como é possível verificar, este namespace XML é precisamente o mesmo que temos vindo a usar como namespace XML global nos exemplos anteriores. Durante o processo de compilação, o parser recorre à meta-data e é capaz de efectuar o mapeamento deste namespace XML nos namespaces .NET introduzidos pelas assemblies. Para além deste namespace “predefinido”, existem ainda outros dois que serão muito usados ao longo deste livro: http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk e http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit. O primeiro identifica os vários namespaces .NET introduzidos pelas assemblies do SDK (Software Developer Kit) do Silverlight; por sua vez, o segundo identifica os vários namespaces .NET introduzidos pelo Silverlight Toolkit. A utilização deste atributo simplifica o código XAML, já que podemos simplesmente referir esse namespace XML para termos acesso a todos os objectos definidos nos vários namespaces .NET que foram previamente associados a esse URI. Sempre que criamos novas assemblies Silverlight, podemos (e devemos!) recorrer a este atributo para associar um namespace XML aos namespaces .NET contidos na nossa assembly.
2.1.3
ELEMENTO-PROPRIEDADE
O exemplo inicial atribuía uma simples string de texto à propriedade Content do botão. A atribuição de um valor simples a uma propriedade em XAML é geralmente efectuada através de atributos. Contudo, o XAML suporta ainda uma outra sintaxe para atribuição de valores: a utilização de elementos internos para definição de propriedades (elemento-propriedade). O excerto seguinte ilustra esta opção: XAML
<Button x:Name="bt"> <Button.Content>Ola</Button.Content> </Button>
Esta não é a nossa melhor opção quando queremos definir conteúdo “simples” (como a string anterior). Contudo, suponhamos que queremos utilizar conteúdos gráficos no interior do nosso botão (note-se que a propriedade Content é do tipo Object). Para exemplificar este cenário, suponhamos que o conteúdo do botão será uma elipse: XAML
<Button x:Name="bt"> <Button.Content> © FCA – Editora de Informática
32
SILVERLIGHT 4.0 – CURSO COMPLETO <Ellipse Fill="Red" Stretch="Fill" Width="100" Height="100" />
</Button.Content>
FIGURA 2.1 – Utilização da sintaxe propriedade-elemento para definir conteúdo de botão
A
sintaxe
elemento-propriedade segue sempre a forma <elemento.nomePropriedade> e só é válida quando definida no interior de um elemento XML que representa um objecto .NET. No exemplo anterior, somos mesmo obrigados a recorrer a esta sintaxe, já que não há nenhuma string capaz de descrever a elipse que pretendíamos atribuir à propriedade Content que possa ser interpretada pelo parser. A markup XAML utilizada no exemplo anterior pode ainda ser simplificada: XAML
<Button x:Name="bt"> <Ellipse Fill="Red" Stretch="Fill" Width="100" Height="100" /> </Button>
Tal deve-se à existência das chamadas propriedades de conteúdo. Uma propriedade de conteúdo permite-nos definir o valor de uma propriedade directamente no interior do seu elemento XAML. Uma classe pode recorrer ao atributo ContentPropertyAttribute para indicar qual a sua propriedade de conteúdo. No caso do controlo Button (que herda indirectamente de ContentControl), a propriedade de conteúdo é a propriedade Content, como podemos verificar através do excerto seguinte: C#
[ContentProperty("Content", true)] public class ContentControl : Control {
Quando o parser está perante uma classe que utiliza este atributo, todo o XML definido no interior de um elemento (não atribuído especificamente a uma © FCA – Editora de Informática
CONCEITOS BÁSICOS
33
propriedade através da sintaxe elemento-propriedade) é automaticamente associado à propriedade identificada pelo atributo ContentPropertyAttribute. Existem restrições para utilização de objectos em XAML? A utilização de uma classe em XAML obriga-nos a respeitar alguns requisitos. O primeiro aspecto a ter em conta é que apenas podemos utilizar classes não encadeadas (ou embebidas – também conhecidas como nested classes) que disponibilizem um construtor público sem parâmetros. As propriedades de uma classe que vai ser usada em XAML devem respeitar os seguintes requisitos: A propriedade deve ser de um tipo primitivo (inteiros, etc.). Propriedades de tipos não primitivos obrigam a que esses tipos possuam construtor público sem parâmetros ou estejam associados a um conversor capaz de efectuar a sua construção a partir de uma string (os conversores de tipo são apresentados na secção 2.1.5). Para além de propriedades “simples”, em XAML também podemos atribuir valores a propriedades que representam colecções de elementos. Todas as propriedades cujo tipo seja um elemento do tipo IList ou IDictionary (<TKey, TValue>) são interpretadas como colecções. Nestes casos, a adição de elementos através de XAML acaba por resultar na adição de elementos à colecção associada a essa propriedade (portanto, convém garantir que essa propriedade retorna um valor não nulo a partir do seu getter). O excerto seguinte ilustra a utilização desta sintaxe para definir os passos associados a um gradiente (o Capítulo 8 apresenta mais informações acerca de gradientes): XAML
<Button x:Name="bt" Content="Ola”> <Button.Background> <LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> <GradientStop Color="White" Offset="0" /> <GradientStop Color="Red" Offset="0.2" /> </LinearGradientBrush> </Button.Background> </Button>
A utilização de dicionários obriga-nos sempre a indicar a “chave” dessa entrada através da directiva XAML x:Key. O excerto seguinte ilustra a adição de uma entrada ao dicionário de recursos de um UserControl (os recursos são apresentados no Capítulo 5):
© FCA – Editora de Informática
34
SILVERLIGHT 4.0 – CURSO COMPLETO
XAML
<UserControl x:Class="QuickTests.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <UserControl.Resources> <LinearGradientBrush x:Key="bgColor" StartPoint="0,0" EndPoint="1,1"> <GradientStop Color="White" Offset="0" /> <GradientStop Color="Red" Offset="0.2" /> </LinearGradientBrush> </UserControl.Resources> <Button x:Name="bt Content="Ola" Background="{StaticResource bgColor}" /> </UserControl>
A extensão de markup StaticResource permite-nos obter uma referência para um recurso previamente declarado pela chave indicada (uma vez mais recordamos que os recursos são apresentados detalhadamente no Capítulo 5).
2.1.4
EVENTOS
Em XAML, também podemos configurar handlers para tratamento de eventos. Analisemos o excerto seguinte: XAML
<Button x:Name="bt" Content="Ola" Click="TrataClique" /> C#
//code-behind void TrataClique(Object sender, EventArgs e) { //efectua operações }
O método TrataEvento deve ser definido no ficheiro de code-behind associado ao user control Silverlight actual (recordar que a associação com o ficheiro de code-behind é feita através da directiva XAML x:Class). Apesar de possível, a definição de handlers em XAML não será a melhor opção. Uma melhor estratégia passa pela aplicação da directiva x:Name ao elemento e correspondente acesso através do ficheiro de code-behind (esta estratégia permite a separação
© FCA – Editora de Informática
CONCEITOS BÁSICOS
35
completa entre a apresentação definida pela XAML e o comportamento definido pelo C#): XAML
<Button x:Name="bt" Content="Ola" /> C#
//code-behind public MainPage() { InitializeComponent(); bt.Click += (sender, e ) => { /*efectua operações */ }; }
2.1.5
CONVERSORES DE TIPOS
No exemplo da secção 2.1.3, recorremos a uma string para atribuir o valor à propriedade Fill da classe Ellipse. Se consultarmos a documentação, facilmente nos apercebemos de que a propriedade Fill é do tipo Brush (uma classe definida pela plataforma Silverlight). Mas então não deveríamos utilizar a sintaxe de elemento-propriedade neste caso? A verdade é que tal não é necessário, já que a plataforma suporta o conceito de conversor de tipo (type converter). Um conversor de tipo é uma classe que herda de TypeConverter e que é responsável por efectuar a conversão de uma string num determinado objecto. Quando o parser interpreta o XAML, tenta sempre encontrar um conversor capaz de efectuar a conversão da string XML para a propriedade em causa. A associação de um conversor a um tipo ou propriedade é efectuada através do atributo TypeConverterAttribute. É importante reter que a utilização de conversores personalizados implica sempre a criação de um novo conversor (isto é, de uma classe que herda de TypeConverter) e a utilização do atributo TypeConverterAttribute para associar esse conversor a uma classe ou propriedade desse tipo. Quando aplicamos o atributo a uma classe, este será utilizado por todas as propriedades desse tipo expostas por qualquer outra classe; por outro lado, se aplicarmos o atributo a uma propriedade, então esse conversor será apenas usado durante a avaliação dessa propriedade.
© FCA – Editora de Informática
36
SILVERLIGHT 4.0 – CURSO COMPLETO
Refira-se que muitos conversores predefinidos pela plataforma Silverlight são inteiramente construídos em unmanaged code e, por isso, não seguem estas regras (ao contrário do que acontece, por exemplo, com a plataforma WPF – Windows Presentation Framework). Essa é a principal justificação por não encontrarmos muitos conversores no código IL da plataforma Silverlight.
2.1.6
EXTENSÕES DE MARKUP
As extensões de markup são outro dos mecanismos de expansão do XAML. Estas extensões são delimitadas por chavetas ({ }) no XAML. Sempre que o parser encontra uma propriedade (definida através da sintaxe de atributo ou de elemento-propriedade) cujo valor está delimitado por chavetas, então esse valor é interpretado como uma extensão de markup (e não como uma string que será atribuída – directa ou indirectament – a uma propriedade). A plataforma Silverlight introduz várias extensões de markup, apresentadas na tabela seguinte: NOME Binding
DESCRIÇÃO
StaticResource
TemplateBinding
RelativeSource
X:Null
Utilizada em cenários de data binding de forma a permitir a actualização de propriedades em run time. O Capítulo 7 apresenta mais detalhes sobre este tipo de operações. Usada para atribuir um recurso definido num dicionário a uma propriedade de um elemento. O Capítulo 5 apresenta mais informações acerca desta extensão. Utilizado na definição de templates. Permite “ligar” uma propriedade de um controlo usado como template à propriedade do controlo cuja interface está a ser modificada através do template. O Capítulo 6 apresenta mais informações sobre esta extensão. Permite referenciar outro elemento presente na árvore de controlos XAML. Pode ser usada de forma a obter uma referência ao próprio controlo (Self – possibilita cenários de data binding onde o objecto fonte deve também ser usado como objecto destino) ou de forma a obter uma referência para o controlo cujo template está a ser definido (TemplatedParent). Esta extensão serve para atribuir o valor null à propriedade de um elemento definido em XAML.
TABELA 2.1 – Extensões de markup introduzidas pela plataforma Silverlight
© FCA – Editora de Informática