You are on page 1of 163

Exercícios

Adotando o Silverlight 2.0


[IL400]

1
Índice

A- Princípios Básicos do Silverlight 2 .................................................................................... 7


Seção 1: Criando uma Aplicação Simples com Silverlight ................................................... 8
Controles Adicionais ..............................................................................................................13
Crédito Extra .........................................................................................................................14
Seção 2: Layout, Controles e Conteúdo de Vetor.................................................................15
Canvas.................................................................................................................................15
StackPanel ...........................................................................................................................20
Grid .......................................................................................................................................22
Seção 3: Elementos e Objetos XAML ....................................................................................29
Seção 4: Manipulação de Entrada e Eventos ........................................................................32
Manipulação Básica de Eventos de Mouse e Teclado .........................................................32
Roteamento de Eventos e Fontes de Eventos .....................................................................34
Seção 5: Modo de Tela Inteira ................................................................................................38
B- Animação no Silverlight.....................................................................................................40
Seção 1: Uma olhada na API de Animação ..............................................................................41
Storyboards ...........................................................................................................................41
Animações ............................................................................................................................42
Animações de Quadro Chave ................................................................................................42
Animações de Objeto ............................................................................................................44
Seção 2: Usando o Blend para Projetar Animações.............................................................46
Ferramenta Visual que tem Suporte para a API ....................................................................46
Criando uma animação ........................................................................................................46
Inspecionando propriedades animadas ................................................................................47
Editando o storyboard ..........................................................................................................47
Editando KeySplines ............................................................................................................47
Resumo ...............................................................................................................................47
Seção 3: Reutilizando Storyboards .......................................................................................48
Geração de Storyboard de Procedimento..............................................................................48
Reutilizando o Storyboard gerado .........................................................................................48
Seção 4: Usando o DispatchTimer para Animação de Procedimento.................................50

2
C- Integração de Navegador do Silverlight ...........................................................................51
Introdução ............................................................................................................................51
Seção 1 – Expondo Funções .NET ao Navegador. ...............................................................52
Entendendo o XAML e o Código .NET do Silverlight .............................................................52
Expondo um método .NET ao JavaScript ..............................................................................54
Chamando o Método .NET a partir do JavaScript. .................................................................55
Seção 2 – Manipulando a Árvore de Renderização XAML a partir do Navegador ..............57
Atualizando elementos XAML existentes ...............................................................................57
Criando novos Elementos XAML ...........................................................................................58
Mais Estudo...........................................................................................................................60
Seção 3 – Chamando o Script do Navegador a partir de .NET ............................................61
Mais Estudo...........................................................................................................................63
D- Personalizando a Aparência ..............................................................................................64
Introdução ............................................................................................................................64
Objetivos do Laboratório: .....................................................................................................64
Exercício 1: Criando o controle deslizante do Voyager ........................................................65
Tarefa 1: Montando seu controle deslizante .....................................................................66
Tarefa 2: Personalizando o controle deslizante ..................................................................69
Tarefa 3: Personalizando o Elevador .................................................................................71
Tarefa 4: Testando nosso controle deslizante. ...................................................................74
Tarefa 5: Exercícios para o usuário....................................................................................74
Apêndices .................................................................................................................................75
Exercício 1: Respostas das Tarefas ......................................................................................75
Tarefa 2: Personalizando o controle deslizante. .................................................................75
Tarefa 3: Personalizando o Elevador .................................................................................77
E- Layout Personalizado no Silverlight .................................................................................82
Introdução ............................................................................................................................82
Objetivos do Laboratório: .....................................................................................................83
Seção 1: Criando um WrapPanel Personalizado ..................................................................84
Tarefa 1: Crie uma classe WrapPanel ..............................................................................85
Tarefa 2: Meça seus Filhos para encontrar seu Tamanho Desejado. ...............................86
Tarefa 3: Organize nossos filhos .........................................................................................87
Tarefa 4: Exercitando nosso Painel .....................................................................................88

3
Seção 2 – Implementando um TimelineStackPanel para a aplicação do Voyager .............89
Tarefa 1: Crie uma classe TimelineStackPanel ..................................................................91
Tarefa 2: Implementando Measure (Medida) em nosso TimelineStackPanel .....................91
Tarefa 3 Adicionando Propriedades de Dependência Anexadas ao TimelineStackPanel..92
Tarefa 4: Notificando o Panel quando um Begin ou End for alterado .................................93
Tarefa 5: Adicionando Propriedades de Dependência Low e High ao nosso Painel. ..........95
Tarefa 6: Notificando o Panel que as propriedades Low/High mudaram ............................97
Tarefa 7: Implementando o Arrange (Organizar) ................................................................98
Tarefa 8: Resolvendo um problema de Conversor ...........................................................100
Tarefa 9: Testando nosso TimelineStackPanel a partir do código ....................................101
Tarefa 10: Testando nosso TimelineStackPanel a partir do XAML ...................................102
Tarefa 11: Exercícios para o usuário................................................................................102
Apêndices ...............................................................................................................................103
Seção 1: Respostas das Tarefas .........................................................................................103
Tarefa 1: Crie uma classe WrapPanel..............................................................................103
Tarefa 2: Medindo nossos filhos ......................................................................................103
Tarefa 3: Passo 1 – Substituindo o Arrange .....................................................................103
Tarefa 3: Passo 2 -- Implementando o Arrange ...............................................................104
Tarefa 4: Exercitando nosso WrapPanel. .........................................................................104
Exercício 2: Respostas das Tarefas ....................................................................................105
Tarefa 1: Criando um TimelineStackPanel .......................................................................105
Tarefa 3: Adicionando propriedades de dependência anexadas para Begin e End ..........105
Tarefa 4: Notificando o Panel quando um Begin ou End for alterado ...............................106
Tarefa 5: Adicionando Propriedades de Dependência Low e High ao nosso Painel. ........107
Tarefa 6: Notificando o Panel que as propriedades Low/High mudaram ..........................107
Tarefa 7: Implementando o Arrange.................................................................................108
Tarefa 8: Resolvendo um problema de Conversor ...........................................................109
Tarefa 9: Testando nosso TimelineStackPanel a partir do código ....................................110
Tarefa 10: Testando nosso TimelineStackPanel a partir do XAML ...................................110
Tarefa 11: Exercícios para o usuário................................................................................110
F- Usando Dados nas Aplicações do Silverlight ................................................................111
Introdução ...............................................................................................................................111
Objetivos do Laboratório: ........................................................................................................111

4
Tarefa 1: Criando uma Ligação Simples usando XAML e DataContext............................113
Tarefa 2: Ligando a uma Coleção (usando um DataTemplate) ........................................114
Tarefa 3: Substituindo a Listbox por um DataGrid ............................................................116
Tarefa 4: Personalizando as colunas no DataGrid ...........................................................117
Tarefa 5 – Exibição Mestre/Detalhes usando Ligações a partir do código........................118
Tarefa 6: Exercício para o usuário ...................................................................................120
Apêndice .................................................................................................................................121
Seção 1: Respostas das Tarefas .........................................................................................121
Tarefa 1: Criando uma Ligação Simples usando XAML e DataContext ............................121
Tarefa 2: Ligando a uma Coleção (usando um DataTemplate) ........................................121
Tarefa 3: Substituindo a Listbox por um DataGrid ............................................................124
Tarefa 4: Personalizando as colunas no DataGrid ...........................................................125
Tarefa 5: Implementando InotifyPropertyChanged (Passo 17) .........................................126
Tarefa 6 - Implementando INotifyPropertyChanged (Passo 9). .......................................127
G- Construindo um Media Player Simples ..........................................................................128
Seção 1: Criando uma Página de Mídia no Silverlight .......................................................129
Seção 2: Adicionando controles de Reprodução à sua Mídia ...........................................131
Seção 3: Usando o Progresso de Download e o Progresso de Buffer ..............................133
Seção 4: Gerenciando Marcadores de Mídia.......................................................................134
H- Silverlight e Aplicações Conectadas ..............................................................................136
Introdução ...............................................................................................................................136
Seção 1: Usando o WebClient para Ler Dados Remotos de Ligação para o Silverlight ..137
Tarefa 1: Crie uma aplicação do Silverlight e adicione a Classe CityData ...........................137
Tarefa 2: Crie o Serviço XML ..............................................................................................138
Tarefa 3: Crie o XAML ao qual fará ligações .......................................................................140
Tarefa 4: Crie a solicitação e o retorno de chamada do WebClient .....................................141
Tarefa 5: Ligando os Dados no Retorno de Chamada .........................................................143
Mais Estudo.........................................................................................................................144
Seção 2: Usando WebRequest / WebResponse para obter Dados ...................................145
Tarefa 1: Crie o Projeto e instale a aplicação de servidor ....................................................146
Tarefa 2: Crie a interface de usuário do Silverlight ..............................................................148
Tarefa 3: Escreva a lógica da interface de usuário e da Ligação de Dados .........................149
Seção 3: Construindo e Ligando a um Serviço WCF .........................................................152

5
Tarefa 1: Crie o Projeto do Silverlight e adicione o Serviço WCF ........................................153
Tarefa 2: Crie o Cliente do Silverlight e Ligue-o ao Serviço .................................................154
Apêndices ...............................................................................................................................156
Seção 1: Respostas das Tarefas .........................................................................................156
Tarefa 1: Classe CityData ................................................................................................156
Tarefa 2: Manipulador Genérico.......................................................................................156
Tarefa 3: XAML Ligado ....................................................................................................157
Tarefa 4: Criando a chamada para o Serviço POX ..........................................................157
Tarefa 5: Ligando os Dados no Retorno de Chamada .....................................................158
Seção 2: ..............................................................................................................................158
Tarefa 1: Função GetCities ..............................................................................................158
Tarefa 2: XAML da interface de usuário ...........................................................................159
Seção 3 ...............................................................................................................................161
Tarefa 1: Classe de Dados de Cidades ............................................................................161

6
A- Princípios Básicos do
Silverlight 2
Conceitos básicos do desenvolvimento do Silverlight 2

Este laboratório explora as ferramentas e os recursos fundamentais que servem de base para qualquer
aplicação do Silverlight.

O laboratório não foi projetado para ser feito do início ao fim. Ele foi estruturado como uma série de
exercícios práticos ‘passo a passo’, ilustrando determinadas técnicas. Você é estimulado a explorar e
experimentar. Pense em um exemplo do que gostaria de construir, e use as instruções deste laboratório
como degraus para alcançar esse objetivo.

O conteúdo deste laboratório é baseado na versão Beta 2 do Silverlight 2. Essa é uma versão preliminar
que não representa o conjunto final de recursos ou a funcionalidade final.

Conteúdo:

1. Criando uma Aplicação Simples com Silverlight


2. Layout, Controles e Conteúdo de Vetor
3. Elementos e Objetos XAML
4. Manipulação de Entrada e Eventos
5. Modo de Tela Inteira

7
Seção 1: Criando uma Aplicação Simples
com Silverlight

Esta seção ilustra as técnicas básicas de programação necessárias em qualquer aplicação Silverlight:
construção e depuração, trabalho com objetos de interface de usuário e manipulação de entrada.

Com as ferramentas do Silverlight para Visual Studio instaladas, o Visual Studio 2008 permite que você
construa e depure projetos do Silverlight. Os passos a seguir demonstrarão esse processo e ilustrarão
alguns dos recursos dos projetos do Silverlight 2 que são diferentes de outros projetos no Visual Studio.

1. Execute o Visual Studio 2008.


2. No menu File, selecione New  Project … ou pressione Ctrl+Shift+N.
3. Na árvore ‘Project types’ à esquerda, selecione Visual C#  Silverlight. Depois, na lista
‘Templates’ à direita, selecione ‘Silverlight Application’.

4. Para depurar e testar um projeto do Silverlight, você vai precisar de um projeto de site na
solução. Isso será separado do projeto do Silverlight propriamente dito – os projetos do
Silverlight apenas constroem um pacote binário do Silverlight conhecido como um arquivo XAP –
vamos falar brevemente sobre isso. Já que isso significa que você normalmente trabalhará com
pelo menos dois projetos, não se esqueça de marcar a caixa de verificação ‘Create directory for
solution’. Dê um nome adequado ao projeto (para este laboratório, vamos usar
FirstSilverlightProject) e clique no botão ‘OK’.

8
5. A seguinte caixa de diálogo aparecerá:

Um projeto do Silverlight não pode ser executado isoladamente – ele deve sempre ser
executado no contexto de uma página da Web pai, que é onde o controle do Silverlight
propriamente dito é incorporado. O assistente permite que você escolha como isso acontecerá.
(Observação: você também pode estabelecer uma associação entre um projeto do Silverlight e
um projeto da web no Solution Explorer do Visual Studio. Se você clicar com o botão direito em
qualquer projeto da web que faça parte de uma solução que contém um projeto do Silverlight,
verá uma opção ‘Add Silverlight Link...’ no menu de contexto. Portanto você sempre tem a
opção de reorganizar a estrutura de seu projeto se mudar de idéia com relação as escolhas que
fez nesse assistente).

A segunda opção, ‘Dynamically generate an HTML test page to host Silverlight within this
project’, é a mais simples – ela apenas cria um único arquivo HTML (oculto) para você que será
usado para incorporar o plug-in do Silverlight. Contudo, isso é insatisfatório por duas razões.
Primeiro, não há acesso ao ambiente de design completo que o Visual Studio pode oferecer para
um projeto da web (particularmente útil se o conteúdo do Silverlight não for a totalidade da
página mas meramente um elemento). E em segundo lugar, isso significa que quando você
executa a aplicação, ela será executada a partir de um arquivo HTML local no disco, em vez de
via HTTP. Um arquivo HTML local pode não fornecer um ambiente realista no qual você executa
seu código, devido ao contexto de segurança em que o navegador será executado.

O terceiro botão de opção do assistente fica desativado porque você acabou de criar uma nova
solução. Se você fosse adicionar um novo Projeto do Silverlight a uma solução existente que já
continha um site, isso permitiria que você usasse esse site para executar o conteúdo do
Silverlight.

Nesse caso, a primeira opção: ‘Add a new Web to the solution for hosting the control’ é a mais
apropriada. Você pode então escolher o tipo de projeto da web – um Projeto de Site ou um

9
Projeto de Aplicação da Web. Essa escolha não faz diferença para o Silverlight – ele está lá
porque o ASP.NET tem suporte para dois estilos diferentes de projeto da web. Para este
laboratório, escolhemos 'Web Site’.

Por fim, note que a caixa de verificação ‘Copy to configuration specific folders’ determina se o
diretório de saída é aninhado para dar suporte a múltiplas configurações de compilação (build).
Se você quiser construir tanto a versão de ‘lançamento’ quanto a de ‘depuração’ de seu arquivo
XAP, por exemplo, é melhor marcar essa caixa.

Clique em OK. O Visual Studio vai criar os dois projetos – seu projeto do Silverlight e um site
para hospedá-lo.

6. Dos dois arquivos que o Visual Studio abre após criar o projeto, um deles não é de uso imediato
para você neste laboratório. Ele abrirá a Default.aspx, a página principal de sua aplicação da
web. No entanto, essa página não tem seu conteúdo do Silverlight. Em vez disso, o assistente
adicionou uma segunda página, que será chamada de ProjectNameTestPage.aspx, e a tornou a
página padrão para depuração. (FirstSilverlightProjectTestPage.aspx, neste caso). Feche ou
exclua a Default.aspx e abra a FirstSilverlightProjectTestPage.aspx.

7. Note que perto do topo desse arquivo aspx, uma montagem (assembly) é trazida ao escopo
contendo as extensões do ASP.NET que têm suporte para o Silverlight:

<%@ Register Assembly="System.Web.Silverlight"


Namespace="System.Web.UI.SilverlightControls"
TagPrefix="asp" %>

Essas extensões incluem o controle <asp:Silverlight>, que é como esse projeto carrega seu
conteúdo do Silverlight, como você pode ver mais abaixo na página. (Se você estiver
familiarizado com a técnica do Silverlight 1.0 de usar o script Silverlight.js, e chamar uma função
JavaScript para carregar o controle do Silverlight, esse método ainda funciona, mas não é o
modo como o assistente do Silverlight escolhe fazer as coisas).

8. Encontre o controle do Silverlight. Note que sua propriedade Fonte se refere a


“~/ClientBin/FirstSilverlightProject.xap". O arquivo .xap a que isso se refere é a saída de
compilação (build) do projeto do Silverlight.

9. Construa o projeto a fim de criar esse arquivo .xap. No Windows Explorer, vá e encontre o
arquivo, que você encontrará na pasta FirstSilverlightProject_Web\ClientBin. Note que o
diretório ClientBin fica oculto por padrão; na janela de ferramentas do Solution Explorer
(Gerenciador de Soluções), você pode visualizá-lo clicando no botão Show All Files na barra de
ferramentas:

10
10. Esse arquivo .xap é na verdade apenas um arquivo ZIP. Faça uma cópia desse arquivo e depois
renomeie a extensão da cópia de .xap para .zip. Dê um clique duplo no arquivo ZIP para abri-lo,
e você verá que ele contém apenas dois arquivos: um DLL que contém todo o código C#
compilado de seu projeto do Silverlight e um arquivo AppManifest.xaml. Esse manifesto lista os
conteúdos do ZIP e indica qual DLL contém o ponto de entrada. O DLL também contém a
linguagem XAML (eXtensible Application Markup Language) que descreve a interface de usuário
de sua aplicação – todos os arquivos XAML são compilados dentro do DLL como recursos
incorporados.

11. Tente adicionar um bitmap ao seu projeto do Silverlight. Basta usar o item do menu de contexto
‘Add  Existing Item’ no Solution Explorer. (Lembre-se de adicionar isso ao seu projeto do
Silverlight, e não o projeto da web associado). Uma vez que tiver adicionado isso, o Visual Studio
vai definir a Ação de Compilação (Build) como Conteúdo. Em um projeto do Silverlight, isso
significa que o item será adicionado ao arquivo .xap – tente reconstruir o projeto e depois repita
o processo de extrair os conteúdos do arquivo .xap, e você verá o bitmap que acabou de
adicionar al lado do DLL e manifesto.

12. Para testar seu projeto, você vai precisar de algum conteúdo no Page.xaml, caso contrário não
haverá nada para ver. Nós começaremos com algo trivial. Abra o Page.xaml e dentro de Grid,
adicione o seguinte:

<Button x:Name="BigButton" Content="Click Me!" FontSize="36" />

Essa marcação cria um botão chamado BigButton que contém o texto “Click Me!” (Clique em
mim!) na fonte de sistema padrão, com um tamanho de 36pt.

13. Agora vamos adicionar um pouco de interatividade à aplicação criando um manipulador de


eventos de clique para o botão. Um pouco antes da marca de fechamento, digite ‘Click=’ (sem
aspas). O recurso ItelliSense do Visual Studio vai mostrar a opção de criar um novo manipulador:

Clique na dica de ferramenta, o código deve ser assim agora:

<Button x:Name="BigButton" Content="Click Me!" FontSize="36"

11
Click="BigButton_Click" />

14. Em uma aplicação do Silverlight 2, as páginas XAML normalmente têm um arquivo code-behind
que define o comportamento da interface de usuário. Por convenção, o arquivo code-behind é
nomeado com um “.cs” (ou “.vb” para um projeto do Visual Basic) no final do nome do arquivo
XAML. Então abra o Page.xaml.cs para ver o code-behind de seu arquivo XAML. Note que o
Visual Studio criou automaticamente um manipulador de eventos para o evento de clique em
botão, desta forma:

private void BigButton_Click(object sender, RoutedEventArgs e)


{

15. No menu, escolha Build / Build Solution para garantir que a conclusão do código do Visual Studio
esteja em sincronia com o novo XAML que você adicionou.

16. Dentro do manipulador de eventos de clique, adicione as duas linhas de código a seguir:

BigButton.Background = new SolidColorBrush(Colors.Red);


BigButton.Content = "You clicked me!";

17. Ponha um ponto de interrupção na primeira linha do código especificado no passo anterior (o
que você pode fazer colocando o cursor nessa linha e pressionando F9). Comece a depurar o
projeto pressionando F5. (Isso também vai construir o projeto para você). Na primeira vez em
que depurar um projeto do Silverlight no estilo ‘site’ do projeto da web, você verá a seguinte
mensagem:

Certifique-se de que o primeiro botão de opção está selecionado e clique em OK.

18. Uma janela do navegador da web vai aparecer com o conteúdo do Silverlight. Clique no botão.
Logo depois, o Visual Studio deve mostrar que você acessou o ponto de interrupção, verificando
que você é capaz de depurar o código C# que está sendo executado no navegador da web.

12
19. Pressione F5 para permitir que a aplicação continue. O navegador da web deve estar mostrando
agora o texto atualizado dentro de um botão vermelho.

Controles Adicionais

Vale a pena destacar que na versão Beta 2, alguns controles mais complexos como DataGrid,
Calendar, TabControl e GridSplitter não estão incluídos no tempo de execução básico, mas estão
incluídos em montagens separadas (o DataGrid está em System.Windows.Controls.Data.dll e os
outros estão em System.Windows.Controls.Extended.dll). Isso significa que você não os verá por
padrão quando usar o IntelliSense. Veja como pode adicioná-los:

Primeiro, você precisará adicionar uma referência a uma dessas montagens ou a ambas. No
projeto do Silverlight, clique com o botão direito no nó de Referências e escolha “Add
Reference...”. Dentro da guia do .NET, role para baixo até encontrar
System.Windows.Controls.Data e .Extended (redimensione a primeira coluna para ver os nomes
completos):

13
Agora você pode fazer referência aos controles dentro desses namespaces a partir do código.
Para acessá-los a partir do XAML, você também precisará adicionar referências a namespaces no
topo de seu arquivo XAML, imediatamente após as declarações de namespace xmlns e xmlns:x
padrão. Use as duas linhas de código a seguir:

xmlns:ex="clr-
namespace:System.Windows.Controls;assembly=System.Windows.Controls.Extended"
xmlns:data="clr-
namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"

Agora está pronto. Tudo o que você precisa lembrar de fazer é usar o escopo de namespace, por
exemplo, <data:DataGrid> para instanciar o DataGrid ou <ex:Calendar> para instanciar o
Calendar.

Crédito Extra

Se você quiser ser mais independente e explorar um pouco, experimente. Por exemplo,
verifique as outras propriedades disponíveis no elemento Button a partir do editor do XAML, ou
substitua o Button por um elemento TextBox, TextBlock ou Calendar. (Não se esqueça de
remover ou editar o manipulador de eventos na visualização do código também). Você pode
alterar as propriedades a partir do XAML ou da visualização do código; pode também adicionar
um código de inicialização extra após a chamada InitializeComponent() no construtor de
Páginas.

14
Seção 2: Layout, Controles e Conteúdo
de Vetor

Até agora aprendemos a criar um projeto do Silverlight, adicionar um elemento e interagir com
esse elemento através do uso de manipuladores de eventos definidos no código C#. Mas nossa
aplicação do Silverlight não vai ser muito flexível com apenas um controle.

Painéis são elementos que podem conter múltiplos elementos filhos, e que determinam os
tamanhos e as posições desses filhos. O mais simples é o Canvas – este só tem suporte para
layout fixo, e funciona do mesmo modo que no Silverlight 1.0. O Grid e o StackPanel são de
maior interesse. Eles são novos no Silverlight 2 (e funcionam do mesmo modo que seus
homônimos no WPF).

As seções a seguir ilustram o uso básico dos novos painéis, e apresentam alguns exemplos de
como múltiplos painéis podem ser combinados para produzir layouts mais complexos.

Canvas

Observação: Se você já estiver familiarizado com o Silverlight 1.0, pode pular esta seção e
proceder à do container StackPanel.

A opção mais simples é projetar seu conteúdo do Silverlight com um tamanho fixo, posicionar
elementos individuais com coordenadas (x,y) absolutas e organizar sua página da web para
acomodar esse tamanho fixo. Isso funciona bem quando você está incorporando conteúdo do
Silverlight dentro de um conteúdo HTML existente e o redimensionamento automático do
conteúdo do Silverlight não faz parte das considerações. Os passos a seguir mostram como fazer
isso.

Crie um novo projeto do Silverlight no Visual Studio chamado FixedSize. Deixe que o Visual
Studio crie uma Web associada para hospedar o conteúdo do Silverlight.

Abra o Page.xaml. Note que o elemento raiz já tem um tamanho fixo – as propriedades Width e
Height foram definidas em 400 e 300 pixels respectivamente. Por padrão, o conteúdo do
UserControl é um Grid; substitua os elementos de abertura e de fechamento pelo Canvas, que é
uma boa opção para layout fixo.

15
Dentro do elemento Canvas, adicione:

<Rectangle Canvas.Left="60" Canvas.Top="60"


Width="280" Height="180"
Fill="#FFBB8536" Stroke="#FF8A580F"
StrokeThickness="10" RadiusX="70" RadiusY="76.5" />

<Ellipse Canvas.Left="20" Canvas.Top="20"


Width="220" Height="220"
Fill="#FF3685BB" Stroke="#FF0F588A"
StrokeThickness="10" />

<TextBlock FontFamily="Verdana" FontSize="30"


Canvas.Left="180" Canvas.Top="245"
Text="Fixed Layout" />

Rectangle e Ellipse são os dois primitivos simples para desenhar; mais tarde, você verá um
elemento Path muito mais sofisticado que tem suporte para curvas de Bézier, linhas e formatos
preenchidos. Note que as propriedades anexadas Canvas.Left e Canvas.Top fornecem controle
baseado em pixel sobre a localização coordenada dos elementos.

Ter que adicionar e posicionar cada elemento à mão com o XAML é algo bastante trabalhoso.
Em vez disso, vamos explorar brevemente o Expression Blend, uma ferramenta de design
interativo que funciona junto com o Visual Studio para fornecer uma experiência de design rica
par ao layout de conteúdos em um arquivo XAML. Clique com o botão direito em Page.xaml e
escolha “Open in Expression Blend”. Você verá o mesmo conteúdo abrir na ferramenta de
design:

Mude a visualização para o modo ‘Divisão’, usando as guias do lado direito do Canvas. Isso
permitirá que você veja como o XAML muda conforme você edita o formato, ou que edite o
XAML à mão e veja o efeito imediatamente.

16
Para começar, vamos ver as classes de formato. Elas podem ser encontradas em dois ícones na
barra de ferramentas à esquerda:

A ferramenta Pen (Caneta) permite desenhar curvas de Bézier clicando e arrastando. Se você
arrastar, ela desenhará segmentos de linha curvados. Se você clicar, ela desenhará ângulos, e se
arrastar dois ou mais ângulos em seguida eles serão unidos por segmentos de linha retos. No
XAML, o formato é definido pela propriedade Data do elemento Path, que usa uma sintaxe SVG
compacta para representar o contorno do path.

O Silverlight usa a mesma sintaxe que o Windows Presentation Foundation; se quiser aprender
mais sobre ela, está documentada em:

http://msdn2.microsoft.com/library/system.windows.media.pathgeometry.aspx.

Os outros dois tipos de formato para os quais o Blend tem suporte são o Rectangle e o Ellipse.
Eles estão disponíveis no item da barra de ferramentas abaixo da Pen. (Clique e mantenha
pressionado o mouse para fazer o submenu aparecer – ele só mostrará uma das ferramentas
por vez. Note que Line não é um formato separado – é apenas uma forma mais simples de criar
objetos Path). Você pode definir a largura e a altura desses formatos, e no caso de um Rectangle
(Retângulo), o Blend fornece alças de ajuste para arredondar os ângulos. Tente ajustá-los e veja
o efeito que isso tem no XAML.

Todos os formatos podem ter seu Fill (Preenchimento) e Stroke (Traço) ajustados da mesma
forma. No lado direito, certifique-se de que o painel Properties (Propriedades) está selecionado,
e você poderá editá-los nas seções Brushes e Appearance (Pincéis e Aparência). Faça um teste e
observe o efeito que têm visualmente e no Xaml.

Observação: O Silverlight tem suporte para uma gama um pouco maior de formatos que o
Blend. Ele também tem suporte para Line (Linha), Polygon (Polígono) e Polyline (Polilinha). O

17
Blend não os usa porque tudo o que fazem também pode ser feito com o Path. Mas se você
gostaria de testá-los no XAML, encontrará a documentação para eles no Visual Studio
Documentation, pesquisando pelo namespace System.Windows.Shapes,

Para certos tipos de gráficos, imagens de bitmap podem ser mais apropriadas que os gráficos
orientados a vetor escaláveis oferecidos pelos tipos de formato. Então a seguir você usará a
marca Image, que tem suporte para arquivos de bitmap JPEG e PNG. No Blend, selecione a guia
‘Project’ no canto superior direito. Aparecerá uma exibição de árvore de seu projeto. Clique com
o botão direito no item de projeto (FixedSize) (o que está abaixo da Solução) e selecione
Adicionar item existente. Encontre qualquer imagem de bitmap – pode ser uma das imagens
JPEG de exemplo que vêm com o Windows - e abra-a.

Arraste o arquivo de imagem que acabou de adicionar da exibição de árvore do Projeto para a
superfície de design. Redimensione-a se necessário para fazer com que caiba no espaço.
(Certifique-se de que tem a ferramenta Selection ativa para poder mover e redimensionar itens.
É a ferramenta no topo da barra de ferramentas à esquerda). Inspecione o XAML para ver o
elemento de Imagem que ele criou para mostrar sua imagem.

Observação: o MediaElement, que tem suporte para a reprodução de arquivos de vídeo, pode
ser usado da mesma forma que o elemento Image do Blend. Tente adicionar um arquivo WMV
ao seu projeto da mesma forma que adicionou uma imagem. (A documentação do Silverlight
descreve os formatos de arquivo de mídia para os quais o Silverlight tem suporte).

Finalmente, você vai testar o TextBlock. Ele pode ser encontrado no final da barra de
ferramentas. Note que o TextBox e o TextBlock compartilham o mesmo local na barra de
ferramentas; TextBox é para edição de texto, enquanto o TextBlock apenas exibe o texto.
Certifique-se de que selecionou o TextBlock – se não clicar e mantiver pressionado o botão da
barra de ferramentas para mostrar as duas opções.

Arraste um retângulo na superfície de design – isso determinará o tamanho de seu TextBlock.


Também o colocará em modo de edição. Clique fora do TextBlock para terminar a edição. Para
editar o texto novamente, escolha a ferramenta Selection e dê um clique duplo no TextBlock.

No XAML, o elemento TextBlock tem suporte para duas maneiras de representar texto. Você
pode apenas definir a propriedade do texto diretamente como um atributo dentro da marca
TextBlock, por exemplo:

<TextBlock … Text="Foo" />

Ou pode fornecer texto formatado de múltiplas linhas:

<TextBlock TextWrapping="Wrap">
<Run Text="This is some " />
<Run FontWeight="Bold" Text="formatted " />
<Run FontStyle="Italic" Text="text." />
<LineBreak />
<Run Text="It spans multiple lines." />
</TextBlock>

18
Embora o Blend defina elementos Width e Height (largura e altura) quando você arrasta um
TextBlock, o bloco vai se autodimensionar se você não especificar essas coisas de maneira
explícita. Tente remover o Width e o Height. Tente então especificar apenas um Width,
certificando-se de que a propriedade TextWrapping está definida como “Wrap”.

Sinta-se à vontade para testar o Blend um pouco mais - use a janela de Propriedades à direita
para testar os pincéis, os gradientes, as transformações e a aparência. Salve então as alterações
e feche o Blend para retornar ao editor do Visual Studio. Note que o Visual Studio recarrega o
arquivo XAML para garantir que as alterações que você fez sejam atualizadas.

O XAML pode ser usado para representar quase todos os conteúdos de gráficos baseados em
vetor. O Expression Design contém suporte embutido para exportar sua saída ao XAML, e há
filtros e conversores de terceiros disponíveis para outros formatos como o Adobe Illustrator.
Para demonstrar isso, exclua tudo o que está dentro do elemento Canvas do projeto XAML.

Agora abra o arquivo SpaceBeetle.xaml com o Notepad – é uma imagem de exemplo do


Expression Design que foi exportada para o XAML. Certifique-se de que a quebra automática de
linha está desativada no Notepad, e então copie e cole os conteúdos inteiros para o corpo do
elemento Canvas. Você verá que a imagem de vetor é composta de muitos elementos Path,
cada qual constituindo uma parte da imagem total.

Exploração extra opcional: Abra o Expression Design e explore essa ferramenta como um modo
de criar arte de vetor. Tente abrir uma das outras imagens de exemplo e exportá-la para o
XAML. Lembre-se de usar a opção (Silverlight” ou “XAML Silverlight Canvas” (dependendo de
qual versão do Expression Design você está usando) para criar um XAML compatível com o
subconjunto para o qual o Silverlight tem suporte:

Expression Design 1 Expression Design 2

19
Antes de terminar esse projeto, vamos explorar rapidamente o modo como o conteúdo do
Silverlight é hospedado dentro de um container HTML pai. O elemento Canvas dentro do XAML
fornece valores de largura e altura que definem o tamanho do container e restringe seus
elementos filhos, mas isso não afeta o tamanho do controle do Silverlight propriamente dito -
isso é feito a partir do HTML. Abra o arquivo FixedSizeTestPage.aspx e encontre o controle do
Silverlight. Por padrão, o projeto define o controle em um tamanho de 640x480:

<asp:Silverlight ID="Xaml1" runat="server"


Source="~/ClientBin/FixedSize.xap" Version="2.0"
Width="640px" Height="480px" />

Execute a aplicação. O conteúdo deve aparecer com o tamanho especificado. Tente adicionar
outro conteúdo HTML (por exemplo, um texto) antes e depois do controle do Silverlight, para
que você possa ver a posição do plug-in do Silverlight em relação ao conteúdo HTML. O controle
do Silverlight acaba encapsulando o plug-in com um <span>, para que ele se comporte como um
elemento embutido para fins de layout do HTML.

O controle asp:Silverlight simplesmente pega propriedades Width e Height fixas e as coloca em


um estilo gerado no tempo de execução. (Você não verá isso se visualizar a fonte da página
HTML, porque o estilo é gerado dinamicamente com script. Contudo, se você usar uma
ferramenta inspetora DOM como a que está incluída no Internet Explorer 8, poderá ver o estilo).
Um método alternativo é usar estilos CSS diretamente. Para isso, remova a propriedade Width e
Height e defina a propriedade CssClass como slPlugin:

<asp:Silverlight ID="Xaml1" runat="server"


Source="~/ClientBin/FixedSize.xap" Version="2.0"
CssClass="slPlugin" />

Na seção <head> da página, após o título, adicione o seguinte:

<style type="text/css">
.slPlugin
{
width: 640px;
height: 480px;
}
</style>

Execute a aplicação novamente. O comportamento deve ser o mesmo de antes.

StackPanel

O StackPanel organiza seus filhos em uma pilha vertical ou horizontal. Se você estiver familiarizado
com o StackPanel do WPF, o do Silverlight 2 funciona da mesma forma.

20
1. Crie um novo projeto de aplicação do Silverlight; abra o Page.xaml e remova as propriedades
Width e Height explícitas do UserControl.
2. Substitua o elemento Grid por um StackPanel. Dentro do StackPanel, adicione alguns botões de
opção, como:

<StackPanel x:Name="LayoutRoot" Background="White">


<RadioButton Content="First" />
<RadioButton Content="Second" />
<RadioButton Content="Third" />
<RadioButton Content="Fourth" />
</StackPanel>

3. Veja como os itens são organizados em uma pilha vertical. Edite a propriedade Content de um
dos botões para que seja uma seqüência de caracteres muito mais longa, e note como o
StackPanel se redimensiona para o conteúdo filho.

4. No segundo RadioButton, tente adicionar uma propriedade HorizontalAlignment com o valor


“Right” (Direita), e veja como isso afeta o layout.

5. No StackPanel, adicione uma propriedade Orientation com o valor “Horizontal”. Execute a


aplicação novamente, e agora você verá os itens organizados em uma pilha horizontal. Note que
a propriedade HorizontalAlignment (alinhamento horizontal) não tem mais efeito nos
TextBlocks. Isso porque um painel de pilha sempre fornece exatamente o espaço necessário
para cada elemento na direção do empilhamento. Já que agora o painel está empilhando
horizontalmente, ele está medindo cada bloco de texto horizontalmente e alocando exatamente
o espaço suficiente. Já que não há espaço livre horizontalmente, não há diferença entre
alinhamento esquerdo, direito ou centralizado dentro do espaço alocado.

6. Os StackPanels podem conter uma ampla variedade de elementos filhos. Faça uma experiência:
tente adicionar elementos de formato como elipses ou retângulos (não se esqueça de
especificar uma cor de preenchimento), ou controles como TextBox, Scrollbar, Calendar e Slider.
Tente aninhar um StackPanel dentro de um StackPanel. (Para facilitar a visualização da extensão
do painel, defina sua propriedade Background com uma cor sólida como “PaleGreen”).

Note que, diferentemente dos controles, que derivam de uma classe pai chamada UIElement e
têm um tamanho intrínseco determinado pelo seu conteúdo, elementos de formato como o
Rectangle são objetos mais primitivos por razões de desempenho, e não têm um tamanho
intrínseco a menos que você especifique uma Width e Height explícita. Já que um StackPanel só
oferece o espaço exato necessário na direção do empilhamento, você terá que especificar uma
Width (largura) para os elementos gráficos que são filhos do painel horizontal, e uma Height
(altura) para os que são filhos do painel vertical – caso contrário os elementos terão zero Width
ou Height respectivamente. (Você pode especificar tanto a Width como a Height se preferir.
Mas pode omitir a Height no painel horizontal já que o elemento vai simplesmente assumir a
altura total do painel; da mesma forma, se não especificar a Width de um elemento gráfico em
uma painel vertical, ele será tão largo quanto o próprio painel).

21
7. Tente adicionar uma propriedade Margin a um ou mais controles e veja como isso afeta o
layout. As margens podem ser especificadas como um valor único, como um par de valores
separados por vírgula que representam as margens esquerda/direita e superior/inferior
respectivamente, ou como um conjunto de quatro valores separados por vírgula que
representam as margens esquerda, superior, direita e inferior individualmente.

Grid
Grid é o container de layout mais sofisticado e flexível, permitindo que o conteúdo seja escalado
conforme o redimensionamento do container pai. O painel Grid dimensiona e posiciona seus
filhos gravando o espaço em uma grade. Você pode especificar o número de linhas e colunas de
que precisa, usando o dimensionamento fixo, automático ou proporcional. (Por exemplo, você
pode dar a uma coluna um quarto da largura e a outra o espaço remanescente). Os filhos de
uma grade podem ocupar uma única célula, ou podem abranger múltiplas células. As células
também podem conter múltiplos filhos. Os passos a seguir mostram esses recursos do Grid,
conforme vamos construindo um seletor de cores simples.

1. Crie um novo projeto do Silverlight chamado SimpleColorPicker. Deixe que o Visual Studio
crie uma Web associada para hospedar o conteúdo do Silverlight. Nós vamos criar uma
interface simples para selecionar qualquer cor que o Silverlight possa exibir. Vai ficar assim:

No lado esquerdo do seletor de cores estão quadro controles deslizantes para selecionar os
valores alfa, vermelho, verde e azul. No lado direito há uma pré-visualização da cor, junto com
seu valor hex e uma caixa de grupo para selecionar o modelo de cor que queremos usar. Há
várias formas de fazer o layout do conteúdo, mas nós vamos usar uma grade como o container
pai, dividindo-a em duas colunas de três linhas, com a primeira coluna abrangendo as três
linhas, como mostram as linhas vermelhas abaixo:

22
Dentro do Grid, adicione um elemento <Grid.ColumnDefinitions>. Dentro dele, adicione duas
marcas <ColumnDefinition />. Isso indica que a grade terá duas colunas. Da mesma forma,
adicione um elemento <Grid. RowDefinitions> contendo três marcas <RowDefinition />. O
código deve ficar assim:

<Grid x:Name="LayoutRoot" Background="White">


<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>

Nós podemos modificar a largura das colunas ou a altura das linhas usando as propriedades
Width e Height, respectivamente. Elas podem ser especificadas como pixels absolutos (por
exemplo, ColumnDefinition Width="250"), como uma razão relativa a outras colunas ou linhas,
ou dimensionar automaticamente conforme seu conteúdo (use Width="Auto"). Para este
exemplo, vamos definir as larguras das colunas em 60% e 40%. Vamos começar mostrando as
linhas de grade para fins de depuração. Adicione o seguinte atributo ao elemento Grid:

<Grid x:Name="LayoutRoot" Background="White" ShowGridLines="True">

Note as linhas azul/amarela na exibição do designer. (Esse recurso serve como um auxílio ao
desenvolvimento e não para o desenho de linhas de propósito geral. Use o elemento border
para ter um controle refinado sobre as bordas das células). Agora edite as larguras de cada
coluna, assim:

<Grid.ColumnDefinitions>
<ColumnDefinition Width="60*" />
<ColumnDefinition Width="40*" />
</Grid.ColumnDefinitions>

(É a razão dos valores que importa aqui, e não os valores absolutos. Nós podíamos ter definido
3* e 2* em vez de 60* e 40* e obtido exatamente o mesmo resultado).

Quando usamos o Canvas como um painel de container em uma seção anterior, pudemos
definir o local exato de seus filhos através do uso de propriedades anexadas. Definindo o
Canvas.Left e o Canvas.Top em qualquer elemento, nós poderíamos definir uma coordenada
(x,y) para qualquer elemento filho. Com um container escalável como o Grid, em vez de
especificar propriedades Top e Left, especificamos a célula em que o elemento deve aparecer
usando as propriedades anexadas Grid.Row e Grid.Column. Se quisermos um controle mais
refinado sobre o local desse elemento dentro de uma célula de grade, podemos aplicar uma
margem.

Na célula superior direita, vamos adicionar um retângulo que será preenchido com uma cor de
pré-visualização. Imediatamente antes do elemento </Grid> de fechamento, adicione o
seguinte:

23
<Rectangle Grid.Row="0" Grid.Column="1"
x:Name="PreviewColor"
Fill="CadetBlue" Margin="10" Stroke="#FF666666" StrokeThickness="2" />

Note que as linhas e colunas são baseadas em zero, portanto isso representa a primeira linha e a
segunda coluna. O x:Name é o que nos permitirá fazer referência a esse retângulo a partir do
código. O aspecto mais interessante do elemento acima é que não precisamos especificar de
forma explícita um tamanho para o retângulo – ele preenche automaticamente a célula (exceto
para a margem). Tente mudar o tamanho da margem e definir as propriedades RadiusX e
RadiusY para arredondar os ângulos do retângulo, e também a Width e a Height para ajustar o
tamanho do retângulo.

Às vezes é mais fácil aninhar os painéis do container um dentro do outro. Vamos adicionar uma
caixa de texto e rotular imediatamente abaixo da pré-visualização do retângulo. Adicione o
seguinte XAML:

<StackPanel Grid.Row="1" Grid.Column="1" >


<TextBlock FontSize="12">Color</TextBlock>
<TextBox x:Name="HexColor" Text="#FF5F9EA0" Margin="10,5" FontSize="11"/>
</StackPanel>

Por padrão, o StackPanel é alinhado com o topo da célula; nós podemos ajustar isso
adicionando VerticalAlignment="Center". Tente isso agora.

Da mesma forma, vamos adicionar alguns botões de opção no final da página:

<StackPanel Grid.Row="2" Grid.Column="1">


<TextBlock FontSize="12">Color Model</TextBlock>
<RadioButton Content="ARGB" Margin="10,0" IsChecked="True"/>
<RadioButton Content="HSL" Margin="10,0" />
</StackPanel>

Se você estiver familiarizado com outros ambientes como o Windows Forms, pode ficar
surpreso em ver que não há um container GroupBox para os botões de opção. Como o
Silverlight sabe quais botões de opção ficam juntos?

Por padrão, os botões de opção são agrupados de acordo com seu container pai (neste caso, o
StackPanel). Você pode, no entanto, mudar isso usando a propriedade GroupName – isso lhe
permite ter múltiplos grupos de botões por pai.

Por último, vamos adicionar os controles deslizantes para o lado esquerdo do controle.

<StackPanel Grid.Row="0" Grid.Column="0" Grid.RowSpan="3"


VerticalAlignment="Center">
<TextBlock Text="Alpha" FontSize="12" Margin="10,15,0,0"/>
<Slider x:Name="AlphaSlider" Margin="20,0,10,0" Maximum="255" Value="255"/>
<TextBlock Text="Red" FontSize="12" Margin="10,15,0,0"/>
<Slider x:Name="RedSlider" Margin="20,0,10,0" Maximum="255" Value="95"/>
<TextBlock Text="Green" FontSize="12" Margin="10,15,0,0"/>
<Slider x:Name="GreenSlider" Margin="20,0,10,0" Maximum="255" Value="158"/>
<TextBlock Text="Blue" FontSize="12" Margin="10,15,0,0"/>
<Slider x:Name="BlueSlider" Margin="20,0,10,0" Maximum="255" Value="160"/>
</StackPanel>

24
Aqui você pode ver que estamos definindo uma propriedade RowSpan para mesclar as três
linhas na coluna esquerda. Isso nos dá uma forma conveniente de usar uma única grade,
embora nem todas as colunas tenham o mesmo número de linhas. Note o uso de um controle
Slider para permitir que o usuário selecione uma cor – por padrão ele vai do 0 ao 1, então
definimos explicitamente o máximo como 255 e os valores como o equivalente decimal de
#FF5F9EA0.

Concluímos o layout da aplicação, vamos agora remover a propriedade ShowGridLines já que ela
não é mais necessária:

<Grid x:Name="LayoutRoot" Background="White">

Tudo o que precisamos fazer agora é adicionar um código à aplicação para cuidar do movimento
do controle deslizante. Posicione o cursor de texto após o atributo de Valor mas antes do
elemento de fechamento do Slider chamado AlphaSlider, e digite ValueChanged=

Uma dica de ferramenta do IntelliSense vai aparecer, oferecendo a você a oportunidade de criar
um novo manipulador de eventos:

Dê um clique duplo na dica de ferramenta com seu mouse.

Agora abra o Page.xaml.cs no Solution Explorer (um filho do Page.xaml), e verá que um stub do
manipulador de eventos foi adicionado ao código.

private void AlphaSlider_ValueChanged(object sender,


RoutedPropertyChangedEventArgs<double> e)
{

Vamos preencher esse manipulador de eventos com alguma lógica para atualizar o retângulo e a
caixa de texto de modo a corresponder aos valores atuais dos controles deslizantes:

Color color = Color.FromArgb((byte)AlphaSlider.Value, (byte)RedSlider.Value,


(byte)GreenSlider.Value, (byte)BlueSlider.Value);

PreviewColor.Fill = new SolidColorBrush(color);


HexColor.Text = color.ToString();

O que estamos fazendo aqui é criar um novo objeto de cor com base no valor dos controles
deslizantes, e depois usá-lo em um pincel para preencher o retângulo, bem como atualizar a
caixa de texto. O valor dos controles deslizantes é armazenado como um valor de ponto

25
flutuante, então temos que converter cada um deles para um byte antes de passá-los ao
método estático Color.FromArgb(), que usa quatro parâmetros representando os valores alfa,
vermelho, verde e azul e retorna um objeto que representa a cor propriamente dita.

Já que queremos que o mesmo código seja executado independentemente de qual controle
deslizante é manipulado, podemos simplificar as coisas definindo o mesmo manipulador para o
evento ValueChanged de cada controle deslizante. Renomeie o manipulador de eventos de
AlphaSlider_ValueChanged para Slider_ValueChanged (corrija-o tanto no código C# quanto no
XAML). Agora vá até o XAML para o controle deslizante vermelho, e digite novamente
ValueChanged=

Note que agora é possível selecionar o evento Slider_ValueChanged existente no IntelliSense:

Repita isso para os controles deslizantes verde e azul.

Observação: Você também pode usar a ligação de dados para conectar um elemento a uma
fonte de dados subjacente; para mais informações sobre o uso da ligação de dados dessa forma,
verifique o laboratório relacionado disponível separadamente.

Tente construir o projeto selecionando a opção do menu Build / Build Solution. A aplicação deve
compilar sem erros. Vamos vê-la em ação. Para isso, pressione F5 ou escolha Debug / Start
Debugging no menu. Se esta for a primeira vez que tenta executar este projeto, você verá a
seguinte caixa de diálogo:

Selecione a opção padrão e clique em OK.

Problema na Beta 2 Você verá que embora a aplicação compile, ela não é executada com
sucesso (você recebe um NullReferenceException). A razão para isso é que o evento
ValueChanged dispara durante a inicialização dos componentes, porém como os componentes
não estão totalmente carregados, não é possível acessá-los a partir do código. Há duas formas
de resolver isso: você pode tentar uma das duas para este laboratório. Adicione os
manipuladores de eventos a partir do código depois que o método InitializeComponent() tiver
disparado, ou defina um campo de bool particular na classe como verdadeiro quando o
InitializeComponent tiver executado e teste o valor do bool no evento antes de executar o
código. Agora reconstrua e execute.

26
Teste a aplicação: você deve conseguir mover os controles deslizantes e ver o preenchimento do
retângulo e a caixa de texto mudarem de acordo.

Agora vamos testar um pouco a depuração do Visual Studio. Sem fechar o navegador, mude
para o Visual Studio. Vá para a primeira linha de código no evento Slider_ValueChanged, e
adicione um ponto de interrupção pressionando F9 ou clicando na margem esquerda. Você verá
um ponto vermelho aparecer para indicar que um ponto de interrupção foi definido:

Agora tente mover um dos controles deslizantes. A aplicação vai disparar instantaneamente o
ponto de interrupção e o Visual Studio vai mudar para o modo de depuração. No final da tela,
você verá uma série de janelas de ferramenta diferentes que fornecem várias exibições da
aplicação em execução.

No lado esquerdo, a janela Autos mostra variáveis que são usadas nas instruções atuais e ao
redor; A janela Locals mostra todos os objetos no contexto atual; a janela Watch permite definir
uma observação em qualquer objeto ou propriedade que desejar. (Use o menu Debug /
Windows para adicioná-las ao seu editor se não vir uma delas por padrão). Você não apenas
pode ver as variáveis aqui, mas pode também modificá-las. Se nunca usou o Visual Studio antes,
tente isso: edite o GreenSlider.Value para que seja 255.0 e o RedSlider.Value para que seja 0.
(Você também pode modificar o BlueSlider.Value inserindo isso no lado esquerdo da grade de
propriedades da janela Watch – por padrão, isso não aparece porque o serviço de linguagem só
exibe as primeiras entradas automaticamente). Agora pressione F5 para continuar a execução
da aplicação. Você verá que os valores alterados são refletidos na aplicação no tempo de
execução. Quando terminar, feche o navegador e o Visual Studio vai voltar automaticamente ao
modo de edição. (Você pode desativar o ponto de interrupção pressionando F9 na devida linha
novamente).

Para provar que o Grid é um objeto fácil de escalar, vamos voltar à exibição do XAML no Visual
Studio. No topo do arquivo, você verá que já há uma largura e uma altura padrão aplicadas ao
UserControl de 400x300. Remova os atributos Width e Height. Você verá que a exibição do
design vai parecer minúscula repentinamente. O que está acontecendo agora é que o objeto
inteiro está se dimensionando para o mínimo que tenha suporte para seu conteúdo filho. Na
verdade, isso é tão pequeno que os controles deslizantes na coluna esquerda têm tão pouco
espaço que se tornam inúteis, portanto vamos ajustar isso um pouco.

27
Na coleção ColumnDefinitions, nós vamos adicionar propriedades MinWidth a ambas as colunas
para que o controle não possa ter uma dimensão menor que um tamanho específico. Defina a
largura mínima da primeira para 100 pixels, e a segunda para 90 pixels, assim:

<Grid.ColumnDefinitions>
<ColumnDefinition Width="60*" MinWidth="100"/>
<ColumnDefinition Width="40*" MinWidth="90"/>
</Grid.ColumnDefinitions>

(Note que esses valores não podem ser obedecidos se a janela do navegador obrigar o controle
geral do Silverlight a ser menor que esse tamanho).

Agora vamos executar a aplicação novamente. Desta vez, você deve conseguir redimensionar a
janela e fazer com que o controle fique visível.

28
Seção 3: Elementos e Objetos XAML
Conforme vimos acima, as interfaces de usuário do Silverlight são normalmente construídas com o
uso do XAML. Cada elemento da interface de usuário criado com XAML tem um objeto .NET
correspondente no Silverlight 2. Uma coisa importante a notar é que você não é obrigado a usar o
XAML para criar os elementos – você pode escrever um código que crie objetos de elemento da
interface de usuário diretamente, sem usar o XAML. A seção a seguir usa essa técnica para desenhar
um gráfico de barras.

1. Crie um novo projeto do Silverlight no Visual Studio chamado BarGraph. Deixe que o Visual
Studio crie uma Web associada para hospedar o conteúdo do Silverlight.

Abra o Page.xaml. Remova os elementos Width e Height do UserControl, e defina uma Width
(largura) e uma Height (altura) no Grid (grade) de 600 e 400 pixels respectivamente.

Adicione uma nova classe chamada GraphBuilder para seu projeto do Silverlight. Clique com o
botão direito no projeto BarGraph e escolha Add / Class.

Neste laboratório, vamos embutir em código alguns recursos do gráfico, portanto adicione os
seguintes campos à classe para representar essas configurações fixas:

const int FirstBar = -5;


const int LastBar = 5;
const double Mean = 0;
const double StandardDeviation = 1;

Adicione a seguinte função – esta é a função de densidade de probabilidade para a distribuição


normal, e fornecerá os valores para o gráfico:

static double NormalProbability(double x)


{
double stdx = (x - Mean) / StandardDeviation;
double stdy = (1 / (2 * Math.PI)) * Math.Exp(-(stdx * stdx) / 2);

return stdy / StandardDeviation;


}

Adicione o seguinte método a esta classe:

public static void BuildGraphInGrid(Grid g)


{
}

Você vai adicionar código a este método para criar o gráfico.

Já que precisaremos repetir os mesmos passos para cada barra do gráfico, comece com um
loop:

for (int bar = FirstBar; bar <= LastBar; ++bar)

29
{
}

O código restante vai dentro desse loop.

Nós precisamos de uma coluna de grade para cada barra do gráfico, então adicione o seguinte
código:

ColumnDefinition colDef = new ColumnDefinition();


g.ColumnDefinitions.Add(colDef);

Cada barra será representada por um Rectangle, então vamos criar um com preenchimento e
contorno adequados:

Rectangle barRect = new Rectangle();


barRect.Fill = new SolidColorBrush(Colors.Red);
barRect.Stroke = new SolidColorBrush(Colors.Black);
barRect.StrokeThickness = 1.5;
barRect.Margin = new Thickness(5, 0, 5, 0);
barRect.VerticalAlignment = VerticalAlignment.Bottom;

A Margem garante que haja algum espaço dos lados da barra. O VerticalAlignment garante que
a barra inicie na parte inferior do gráfico.

Em seguida, defina a altura da barra conforme a função que estamos plotando:

double value = NormalProbability(bar);


barRect.Height = value * g.Height * 4;

Note que é considerada uma escala vertical de 0 a 0.25. É uma escala razoável para essa
aplicação específica. Uma ferramenta de gráfico mais sofisticada precisaria, é claro, fazer algo
mais inteligente a respeito das escalas.

Para vê-la, temos que adicionar a barra ao gráfico. Isso significa definir sua coluna de grade e
adicioná-la à grade:

Grid.SetColumn(barRect, bar - FirstBar);


g.Children.Add(barRect);

Finalmente, para ver os resultados de seu trabalho, você precisa chamar o código que escreveu.
Abra o arquivo code-behind Page.xaml.cs, e no construtor adicione o seguinte depois do
método InitializeComponent:

GraphBuilder.BuildGraphInGrid(LayoutRoot);

30
Execute a aplicação. Você deve ver uma série de barras como esta:

Se você não vir nada na tela, é possível que tenha pulado o passo em que define a altura e a
largura na grade. Definir essas propriedades na grade (em vez do controle) é importante,
porque sem ela o gráfico de barras é desenhado antes do dimensionamento da grade, o que
significa que ela terá largura e altura de “Auto” e altura e largura reais de 0. Como resultado,
todas as alturas das barras serão 0!

(Avançado). Remova a largura e a altura fixas do controle da grade e modifique a aplicação para
permitir que a grade seja reescalada conforme o usuário redimensiona a janela do navegador.
(Dica: você terá que interceptar o evento SizeChanged e corrigir um pouco o método
BuildGraphInGrid para que ele não continue adicionando colunas e retângulos toda vez que o
tamanho for alterado).

(Opcional). Use essa aplicação para testar mais o Silverlight. Aqui estão algumas idéias:

a. Modifique o algoritmo no método BuildGraphInGrid para produzir um tipo diferente


de grade. Por exemplo, use os valores para modificar a cor das barras;

b. Adicione um eixo à grade – origine linhas e rótulos nos intervalos;

c. Adicione um pouco de interatividade – uma dica de ferramenta mostrando os


valores reais em um evento de focalização do mouse.

31
Seção 4: Manipulação de Entrada e
Eventos
Esta parte do laboratório se concentra em vários aspectos da manipulação de entrada.
Começamos olhando a manipulação básica de eventos de mouse e teclado, antes de cobrir
manipulações de eventos roteados mais avançadas, à medida que criamos um mini-jogo que
exercita tudo o que aprendemos até agora.

Manipulação Básica de Eventos de Mouse e Teclado

Nesta seção, você vai realizar algumas manipulações de eventos bastante simples, para permitir
que um objeto seja arrastado pelo controle do Silverlight com o mouse, e sua cor seja alterada
com o teclado.

Crie um novo projeto do Silverlight no Visual Studio chamado BasicInput. Deixe que o Visual
Studio crie uma Web associada para hospedar o conteúdo do Silverlight.

No Page.xaml, mude o elemento Grid para um Canvas e adicione uma elipse dentro do
elemento Canvas, assim:

<Canvas x:Name="LayoutRoot" Background="White">


<Ellipse x:Name="ellipse"
Fill="Blue" Width="100" Height="50"
Canvas.Top="20" Canvas.Left="20" />
</Canvas>

Construa o projeto (Ctrl+Shift+B ou Build / Build Solution) para garantir que o IntelliSense fique
atualizado com as mudanças que você fez no XAML.

Abra o Page.xaml.cs. No construtor, após a chamada do InitializeComponent, adicione código


para conectar manipuladores para os eventos MouseLeftButtonDown, MouseLeftButtonUp e
MouseMove da elipse. (Lembre-se de que no exemplo do seletor de cores, fizemos isso com o
código XAML adicionando um atributo para cada evento. Desta vez faremos através do código
para demonstrar um método alternativo. Nenhum é “melhor” – você pode usar o estilo ou o
fluxo de trabalho de sua preferência). Novamente, o Visual Studio pode fazer a maior parte do
trabalho – se você digitar apenas +=, ele mostrará uma ToolTip (dica de ferramenta):

Se você pressionar a tecla TAB duas vezes, ele vai gerar o construtor delegado primeiro, e o
método do manipulador de eventos depois. Faça isso para os três eventos.

32
Remova de cada método o código que abre uma exceção. O Visual Studio adiciona isso para que
você não se esqueça de escrever o método. Aqui, isso só vai atrapalhar conforme avançarmos
pelos passos.

Você vai adicionar código que permite ao usuário arrastar a elipse. O manipulador MouseMOve
fará o trabalho, mas ele precisa saber duas coisas: se a operação arrastar está em progresso no
momento e, se estiver, onde o mouse estava com relação à elipse quando a operação arrastar
começou, para saber onde posicioná-lo agora. Para armazenar esse estado, adicione os
seguintes campos à classe Page:

public partial class Page : UserControl


{
bool dragInProgress = false;
Point dragOffset;

No código MouseLeftButtonDown, defina estes campos:

dragInProgress = true;
dragOffset = e.GetPosition(ellipse);

No manipulador MouseMove, atualize a posição se uma operação arrastar estiver em progresso:

if (dragInProgress)
{
Point mousePoint = e.GetPosition(this);
Canvas.SetLeft(ellipse, mousePoint.X - dragOffset.X);
Canvas.SetTop(ellipse, mousePoint.Y - dragOffset.Y);
}

Execute a aplicação. Você deve conseguir clicar na elipse para começar a arrastá-la, mas há dois
problemas a resolver. O primeiro e mais óbvio é que a operação arrastar continua depois que
você solta o mouse. Para corrigir isso, adicione o seguinte código ao manipulador
MouseLeftButtonUp:

dragInProgress = false;

O segundo problema, mais sutil, é que se você mover o mouse rápido demais, ele pode deixar a
elipse para trás. A razão disso é que uma vez que o mouse deixa a elipse, o manipulador
MouseMove para de ser chamado. Para corrigir isso, adicione uma chamada para
ellipse.CaptureMouse() dentro do manipulador MouseLeftButtonDown – isso faz com que a
elipse continue a ter eventos de mouse durante o tempo em que o mouse estiver dentro do
plug-in do Silverlight, não importa se já tiver passado a elipse. Adicione também uma chamada
para ellipse.ReleaseMouseCapture() no manipulador de eventos MouseLeftButtonUp.

33
Execute a aplicação. Você deve conseguir mover o mouse mais rápido agora sem problemas,
contanto que permaneça dentro da área ocupada pelo controle do Silverlight.

A seguir você vai adicionar manipulação de teclado. Adicione um manipulador para o evento
KeyDown do Page: use essa palavra-chave para se referir ao objeto atual, assim:

this.KeyDown += new KeyEventHandler(Page_KeyDown);

Adicione um código que altere o preenchimento da elipse com base na tecla pressionada:

void Page_KeyDown(object sender, KeyEventArgs e)


{
Color c;
switch (e.Key)
{
case Key.R:
c = Colors.Red;
break;

case Key.G:
c = Colors.Green;
break;

case Key.B:
c = Colors.Blue;
break;

default:
return;
}

ellipse.Fill = new SolidColorBrush(c);


e.Handled = true;
}

Execute a aplicação. Quando clicar em algum lugar dentro do controle, ela responderá às teclas
pressionadas.

Roteamento de Eventos e Fontes de Eventos

Muitos eventos no Silverlight 2 “se propagam”, assim como fazem no Silverlight 1.0 e no WPF.
Isso permite anexar um único manipulador de eventos que recebe notificações de eventos de
múltiplos elementos. No Silverlight 1.0 isso era um pouco limitado, já que não havia uma forma
de descobrir de qual elemento um determinado evento tinha se propagado. O Silverlight 2 tem
suporte para a propriedade Source no objeto de argumento do evento, o que resolve esse
problema.

34
Vamos colocar tudo o que aprendemos até agora em prática para criar um mini-jogo divertido
chamado “PopTheBubble”. Nesse jogo, vamos desenhar novas “bolhas” (elipses) na tela a cada
½ segundo, e o jogador deve removê-las o mais rápido possível clicando nelas.

1. Crie um novo projeto do Silverlight no Visual Studio chamado PopTheBubble. Deixe que o
Visual Studio crie uma Web associada para hospedar o conteúdo do Silverlight.

2. Mude o elemento Grid padrão (chamado LayoutRoot) para um Canvas.

Abra o arquivo code-behind Page.xaml.cs. Nós precisamos que um evento de marcação (tick)
dispare a cada ½ segundo. Podemos fazer isso usando um DispatcherTimer, que dispara um
evento no thread da interface de usuário a um intervalo específico. O DispatcherTimer reside no
namespace System.Windows.Threading, portanto vamos adicioná-lo à nossa classe colocando a
seguinte cláusula no topo do projeto:

using System.Windows.Threading;

Agora vamos adicionar dois campos à classe, imediatamente após a declaração da classe e antes
do construtor:

public partial class Page : UserControl


{
DispatcherTimer timer;
Random rng;

No construtor, após a chamada para InitializeComponent, vamos inicializar o temporizador e o


gerador de número aleatório:

rng = new Random();

timer = new DispatcherTimer();


timer.Interval = new TimeSpan(0, 0, 0, 0, 500);

A classe TimeSpan representa um período de tempo (aqui, 0 dias, 0 horas, 0 minutos, 0


segundos, 500 milissegundos).

Vamos criar um manipulador de eventos para o evento Tick que o objeto temporizador vai
disparar. Faça com que o Visual Studio gere um método chamado timer_Tick que vai manipular
o evento.

timer.Tick += new EventHandler(timer_Tick);

35
No método Timer_Tick, vamos ter uma elipse criada de tamanho e cor aleatórios que será
adicionada ao canvas:

void timer_Tick(object sender, EventArgs e)


{
byte[] colors = new byte[3];
Ellipse el = new Ellipse();
Point c1 = new Point(rng.NextDouble() * Width, rng.NextDouble() *
Height);
Point c2 = new Point(rng.NextDouble() * Width, rng.NextDouble() *
Height);
Rect bounds = new Rect(c1, c2);
el.Width = bounds.Width;
el.Height = bounds.Height;
Canvas.SetLeft(el, bounds.Left);
Canvas.SetTop(el, bounds.Top);
rng.NextBytes(colors);
Color c = Color.FromArgb(255, colors[0], colors[1], colors[2]);
el.Fill = new SolidColorBrush(c);
LayoutRoot.Children.Add(el);
}

Por último, precisamos iniciar o temporizador. Adicione a seguinte linha como a última instrução
do método InitializeComponent():

timer.Start();

Vamos construir e executar a aplicação. Você deve ver algumas elipses dispersas
aleatoriamente, aparecendo a cada meio segundo. Se as elipses aparecerem no mesmo lugar,
verifique se você mudou o elemento Grid no XAML para um Canvas (passo 2).

Em seguida, você vai adicionar código para manipular os cliques do mouse em qualquer uma
dessas Ellipses. No construtor, anexe um manipulador ao evento de botão esquerdo do mouse
pressionado no controle, e crie um manipulador de eventos de stub, conforme descrevemos no
início desta seção.

A propagação de eventos significa que os eventos MouseLeftButtonDown gerados pelas Ellipses


vão se propagar para o nível da página. É claro que o argumento ‘sender’ do manipulador
sempre vai se referir ao elemento ao qual você anexou o manipulador – o Page neste caso – mas
o Silverlight 2 tem agora suporte para a propriedade Source no argumento do evento, como o
WPF. Isso permite descobrir de onde o evento veio. Então podemos descobrir se o evento veio
de uma das elipses que adicionamos substituindo a posição NotImplementedException() no
manipulador de eventos de mouse pelo seguinte código:

Ellipse target = e.Source as Ellipse;


if (target != null)
{
}

36
Se você não estiver familiarizado com o código .NET, a primeira instrução converte e.Source
(que é do tipo object) para o tipo Ellipse. Se a fonte não for realmente uma elipse (por exemplo,
você clica no segundo plano da página), o target será definido como nulo. Isso é diferente na
escrita: Ellipse target = (Ellipse) e.Source; (outra forma de converter que usamos anteriormente
no laboratório), já que o método gera uma exceção se o objeto não corresponder ao tipo target
(alvo).

Dentro do bloco if, vamos adicionar uma única instrução para remover a elipse em que o usuário
clicou da coleção Canvas pai. Isso vai apagá-la da tela e liberar os recursos que ela estava
usando.

if (target != null)
{
LayoutRoot.Children.Remove(target);
}

Execute a aplicação – você deve conseguir estourar as bolhas clicando nelas.

(Opcional). Use essa aplicação para testar mais o Silverlight. Aqui estão algumas idéias:

a. Adicionar um TextBlock à tela que conta o número de bolhas estouradas.

b. Use um segundo DispatcherTimer para dar ao jogador um limite de tempo para


estourar o máximo de bolhas possível. Após (digamos) 30 segundos, exiba uma
mensagem na tela que mostra quantas bolhas foram estouradas, junto com um
botão que permite ao jogador recomeçar o jogo.

c. Use o evento MouseLeftButtonDown para testar se o jogador “perdeu” uma bolha


clicando no segundo plano da página em vez de clicar na bolha. (Dica, a conversão
para Ellipse vai falhar porque o Target será de um tipo diferente). Se ele perder uma
bolha, exiba uma mensagem de “fim de jogo”.

d. Adicione um controle deslizante de dificuldade à tela para controlar o intervalo de


aparecimento das bolhas.

e. Verifique se o jogador conseguiu remover todas as bolhas com sucesso, e exiba a


mensagem “você venceu”.

f. Use o Expression Blend para projetar uma “bolha” mais bonita em vez da Ellipse de
cor sólida. Substitua a elipse pela nova bolha baseada em XAML.

g. Use as técnicas da próxima seção do laboratório para permitir que o jogo seja
jogado em modo de tela inteira.

37
Seção 5: Modo de Tela Inteira
Anteriormente você viu como fazer seu conteúdo do Silverlight preencher completamente a
janela do navegador. No entanto, às vezes é útil ir um passo além disso, e fazer seu conteúdo
preencher a tela inteira. Os passos a seguir mostram como usar o suporte do Silverlight para
operações de tela inteira.

1. Crie um novo projeto do Silverlight chamado FullScreen. Deixe que o Visual Studio crie uma
Web associada para hospedar o conteúdo do Silverlight.

2. Abra o Page.xaml. Remova as propriedades Width e Height do UserControl raiz.

3. Substitua o conteúdo por isso:

<Grid x:Name="LayoutRoot" >


<Rectangle x:Name="toggleFullScreen" Fill="Blue"
RadiusX="30" RadiusY="30" />
</Grid>

Uma pequena peculiaridade: note que o designer não mostrará o conteúdo nesse ponto, porque
nenhum tamanho é especificado no retângulo e portanto o designer dimensiona a grade de
acordo com seu conteúdo, que é 0px de largura e 0px de altura. O retângulo será exibido no
tempo de execução porque o container HTML para o controle do Silverlight fará com que o
conteúdo seja dimensionado de modo a caber no container (isto é, a largura e a altura da tela).

4. Adicione um manipulador de eventos para o evento MouseLeftButtonDown no Rectangle


usando a técnica descrita anteriormente.

5. Neste manipulador, você precisa recuperar o objeto ‘conteúdo’ do plug-in do Silverlight,


pois esse é o objeto que controla se o plug-in está no modo de tela inteira no momento:

System.Windows.Interop.Content contentObject =
Application.Current.Host.Content;

6. Para fazer o plug-in mudar de tela inteira para o modo normal quando esse elemento for
clicado, adicione o seguinte código no manipulador:

contentObject.IsFullScreen = !contentObject.IsFullScreen;

7. Execute a aplicação. Quando você clicar no retângulo azul, a aplicação deve mudar para o
modo de tela inteira. Quando você clicar novamente ela voltará par o modo normal.

8. Note que quando for para a tela inteira, aparecerá uma mensagem indicando que o usuário
pode reverter para o modo normal pressionando Esc. (Essa mensagem é fornecida
automaticamente pelo Silverlight. Você não pode desativá-la – ela faz parte do design, para
impedir que um código mal-intencionado crie uma imagem falsa de estação de trabalho,

38
com aparência plausível, para enganar o usuário e fazê-lo digitar uma senha). Isso significa
que a aplicação pode sair do modo de tela inteira sem que seu código seja envolvido. Para
essa aplicação em particular, isso não é problema – a única coisa interessante que acontece
quando vamos para a tela inteira é que o retângulo se redimensiona, e isso acontece
automaticamente graças ao elemento Grid raiz. Entretanto, uma aplicação mais útil pode
ter que fazer algo quando transitar entre o modo de tela inteira e o modo normal. Para lidar
com isso, adicione código ao construtor para anexar um manipulador ao evento
FullScreenChanged do objeto de conteúdo:

Application.Current.Host.Content.FullScreenChanged +=
new EventHandler(Content_FullScreenChanged);

9. No manipulador desse evento, escreva o seguinte código para mudar a cor do retângulo a
fim de indicar o modo:

void Content_FullScreenChanged(object sender, EventArgs e)


{
toggleFullScreen.Fill = Application.Current.Host.Content.IsFullScreen ?
new SolidColorBrush(Colors.Red) :
new SolidColorBrush(Colors.Blue);
}

10. Execute a aplicação. Note que o retângulo será vermelho quando for tela inteira e azul
quando for modo normal. A volta para a cor azul acontece mesmo que o modo normal entre
pela tecla Escape.

39
B- Animação no Silverlight
Primeiros Passos na criação e no uso de animações

Neste laboratório, você terá uma visão geral da API de Animação no Silverlight, vai se familiarizar com
as ferramentas de design visual para animação disponíveis no Blend e executar uma aplicação bastante
simples que usa código de procedimento para gerar e reutilizar Storyboards. E no final aprenderá a usar
um DispatcherTimer.

40
Seção 1: Uma olhada na API de
Animação

Animação no contexto do Silverlight pode ser resumida em uma linha – mudar o valor de uma
propriedade de um objeto com o tempo (variando de instantâneo a infinito). Criar animações é na
verdade algo bastante simples de aprender. Basicamente, você cria um Storyboard que executa uma ou
mais Animações quando o Storyboard é começado. A parte difícil das animações é saber quando e onde
usá-las e o que animar.

Após este Laboratório, você terá uma boa idéia de como animar propriedades. Com relação a quando e
onde usá-las, pode não ser tão difícil quanto mencionamos acima. Há milhões de exemplos (bons e
maus (e péssimos)) de animações em sites, interfaces de usuário de aplicações, e ocultos naqueles GIFs
animados que você adiciona à sua assinatura de e-mail e encaminha a todos os seus amigos.

Agora vamos nos divertir um pouco e ver como os Storyboards e as Animações funcionam, depois
vamos levá-los para um teste no Expression Blend.

Storyboards

A classe Storyboard é o container e a interface de suas Animações. Storyboards contêm uma ou mais
Animações e as controlam com base em métodos simples de transporte como Begin, Pause, Stop,
Resume, Seek e SkipToFill. Os quatro primeiros são auto-explicativos (Começar, Pausar, Parar e
Continuar), mas Seek significa mover para um ponto específico na linha do tempo e SkipToFill move a
linha do tempo dos Storyboards para o final de seu período ativo.

Os storyboards também têm propriedades que podem modificar radicalmente seu comportamento.
 AutoReverse determina se um Storyboard deve ser reproduzido inversamente após concluir a
iteração de avanço.
 BeginTime pode ser usado para atrasar a reprodução.
 Duration define a duração total do Storyboard.
 FillBehavior determina se o novo valor deve ser mantido quando o storyboard for concluído.
 RepeatBehavior determina quanto tempo ou quantas vezes o Storyboard deve ser repetido.

Os storyboards têm um único evento chamado Completed que dispara quando todas as iterações de
avanço, inversas e repetições do Storyboard tiverem sido executadas até o final. A manipulação desse
evento permite decidir qual código executar, se houver algum, após a animação, o que poderia iniciar
outro Storyboard. Ou você pode modificar o Storyboard e as Animações e executá-lo novamente.

41
Animações

Todas as Animações e Storyboards herdam da classe Timeline que fornece a funcionalidade baseada em
tempo mencionada acima. Quando um Storyboard ou uma Animação iteram, quanto tempo, quantas
vezes, etc. O que torna cada classe de Animação única é o tipo de propriedade animada e a
interpolação entre os valores atuais e de destino.

Primeiro vamos ver Animações simples que têm propriedades From, To e By.
 From é opcional e define quando a animação começa o que o valor de partida é. Se a From não
for definida o valor de partida será o valor atual da propriedade.
 To define o valor final da propriedade.
 By aumenta o valor da propriedade animada com seu valor. Por exemplo, se você tem um
Rectangle com um Canvas.Left definido em 0 e o anima com By=50 e RepeatBehavior definido
em 2x no final da Animação o Canvas.Left do Rectangle seria igual a 100.

Em uma animação simples, a interpolação entre os valores durante a transição é linear- uma mudança
tranqüila e constante. Os três tipos de Animações simples incluem:

ColorAnimation
Usando um exemplo de animação da cor de um Traço (Stroke)
de Retângulo, você pode ver que a cor mudou de azul escuro
para vermelho.

DoubleAnimation
Neste exemplo o deslocamento do Gradiente Stop de azul
mais escuro foi animado para dar uma sensação de
sobreposição do Retângulo.

PointAnimation
Aqui o StartPoint e o EndPoint do LinearGradientBrush foram
animados para dar um Preenchimento radicalmente
diferente para o Retângulo.

Animações de Quadro Chave

As animações de quadro chave permitem definir múltiplos valores e controlar a interpolação entre
valores iniciais e finais. Os KeyFrames (quadros chave) são adicionados à Animação e cada um define
um Valor e um KeyTime usados durante a animação. A interpolação é controlada definindo a
propriedade KeySpline que só está disponível no tipo SplineKeyFrame. Animações de Quadro Chave
possibilitam animações muito mais orgânicas, envolvendo aceleração e desaceleração.

42
Cada tipo de animação simples tem um tipo de animação KeyFrame correspondente, incluindo
ColorAnimationUsingKeyFrames, DoubleAnimationUsingKeyFrames, PointAnimationUsingKeyFrames e
ObjectAnimationUsingKeyFrames.

Em animações simples nós explicamos que a interpolação Linear é tranqüila e constante do valor inicial
ao valor final. Quando você começa a usar AnimationUsingKeyFrames, pode tirar proveito da
interpolação baseada em Spline.

Aqui estão alguns exemplos para ilustrar o conceito de KeyFrames e KeySplines. Sinta-se à vontade para
seguir estes exemplos no Blend ou no Visual Studio para ver as Animações se moverem em tempo real.

A seguinte animação simples da propriedade Canvas.Left de nosso objeto OrangeShip mudará o valor de
0 para 500 em um segundo:

<Storyboard x:Name="SplineTest">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="OrangeShip"
Storyboard.TargetProperty="(Canvas.Left)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="500"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>

Note que o KeySpline à direita (representado pela ferramenta visual do Blend) está definido com o valor
padrão, que é equivalente à interpolação Linear. A linha verde abaixo representa a taxa de mudança
conforme a nave desliza pela tela. Ela vai de forma suave e estável.

Agora vamos modificar o KeySpline:

<Storyboard x:Name="SplineTest">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="OrangeShip"
Storyboard.TargetProperty="(Canvas.Left)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="500">
<SplineDoubleKeyFrame.KeySpline>
<KeySpline ControlPoint1="1,0" ControlPoint2="1,1"/>
</SplineDoubleKeyFrame.KeySpline>
</SplineDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>

43
Com o KeySpline definido assim, a nave cobre pouco espaço no início mas acelera bastante até o final.

Vamos adicionar outro KeyFrame e mover o KeySpline:

<Storyboard x:Name="SplineTest">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="OrangeShip"
Storyboard.TargetProperty="(Canvas.Left)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="200">
<SplineDoubleKeyFrame.KeySpline>
<KeySpline ControlPoint1="0,1" ControlPoint2="1,1"/>
</SplineDoubleKeyFrame.KeySpline>
</SplineDoubleKeyFrame>
<SplineDoubleKeyFrame KeyTime="00:00:03" Value="500" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>

A nave agora acelera até 200 e depois vai deslizando daí para 500. Embora seja um pouco mais
complexa de ver em XAML, Animações baseadas em Quadro Chave podem ser muito potentes e
bastante simples de criar no Blend.

Animações de Objeto

Mencionadas rapidamente acima, as Animações de Objeto são novas no Silverlight 2 beta 2 e permitem
definir valores de Objetos que não são Doubles, Colors ou Points. As Animações de Objeto só podem ser
usadas com KeyFrames e com a Interpolação Discrete. Discrete significa completamente alterada no
final da duração. Olhando o exemplo abaixo você pode entender o porquê. Seria muito difícil
(impossível) interpolar entre dois valores Enumeration (Enumeração).

44
Animar a propriedade Visibility é um caso de uso comum, especialmente quando você considera que ia
usar Opacity em vez de Visibility, mesmo com uma opacidade 0 os eventos de mouse ainda são
capturados.

<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="OrangeShip"
Storyboard.TargetProperty="(FrameworkElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:01">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>

45
Seção 2: Usando o Blend para Projetar
Animações

Ferramenta Visual que tem Suporte para a API

O Blend é a ferramenta visual para layout interatividade e criação de gráficos básicos para aplicações do
WPF e do Silverlight. As tecnologias WPF trazem novos paradigmas de programação para
desenvolvedores, mas também trazem inovações para os designers. No contexto da Animação, o Blend
fornece um conjunto bastante robusto de ferramentas. Abaixo está um passo a passo sobre como usar
cada uma das Ferramentas de Animação e como elas se relacionam ao conceito de API acima.

 Abra o SimpleAnimation.sln no Blend e abra o Page.xaml.

Agora você tem um objeto interessante para manipular com um segundo plano bastante contrastante.

Criando uma animação

 Selecione o objeto OrangeShip.


 Encontre o painel Objetos e Linha do Tempo e
selecione o botão “+” para adicionar um novo
storyboard.
 Dê ao Storyboard o nome que desejar.

A Interface do Blend muda um pouco quando um Storyboard é carregado.

 Note que o Storyboard é exibido agora por nome na área superior do painel de Objetos.
 A Linha do Tempo é mostrada à direita da árvore de Objetos.
 Finalmente a superfície de design tem um contorno vermelho, notificando que você está em
modo de gravação. Edições no objeto serão agora acompanhadas no Storyboard.

Você pode ligar e desligar a gravação clicando no círculo vermelho, que vai alternar para cinza quando
estiver desligado.

O padrão do Expression Blend é usar KeyFrames, já que visualmente esse é o modo mais fácil de
projetar animações complexas. Olhando a Linha do Tempo você pode ver o playhead, que é a linha
vertical amarela com a seta no topo.

46
 Acima do marcador de 0 segundos você verá o botão Record KeyFrame , com o OrangeShip
selecionado, clique nesse botão e grave um KeyFrame no marcador de 0 segundos.
 Arraste o playhead para o marcador de 1 segundo.
 Arraste o OrangeShip de onde está para um novo local.
 Clique no botão Record KeyFrame novamente.
 Acima da linha do tempo você encontrará controles de transporte que permitem pré-visualizar
sua animação. Clique no botão Play.
 Bela animação! Muito suave e linear.

Inspecionando propriedades animadas

Você pode inspecionar quais propriedades está animando visualmente expandindo os objetos no Painel
de Objetos que têm o ícone vermelho selecionado. É claro que também pode mudar para a exibição
XAML ou abrir o arquivo no Visual Studio para ver as propriedades alvo.

Editando o storyboard

Se seu Storyboard não estiver mais aberto ou você quiser selecionar um diferente, clique no botão
que vai mostrar a lista dos Storyboards existentes. Com um Storyboard selecionado, você vai ver as
propriedades do Storyboard no Painel de Propriedades. Aqui você pode editar o AutoReverse e o
RepeatBehavior.

Editando KeySplines
Com um Storyboard aberto, selecione um KeyFrame na área da Linha do Tempo. Note que agora o
painel de Propriedades mudou para trazer acima o editor do KeySpline, bem como uma forma de inserir
um Valor para um KeyFrame. Desse modo, se você não puder arrastar algo para o ponto exato que
deseja, pode apenas digitar o valor exato.

Resumo
Em alto nível, a criação de Storyboards pode ser realizada simplesmente movendo o playhead para
tempos diferentes e modificando qualquer propriedade de seu objeto. Lembre-se que um Storyboard
pode conter mais que uma animação e pode ser reutilizado com alguma ajuda do code-behind.

47
Seção 3: Reutilizando Storyboards

Geração de Storyboard de Procedimento

A habilidade de criar e projetar storyboards visualmente dentro do Blend é muito potente, mas em
algum ponto você pode precisar entrar em seu código para manipular ou reutilizar o storyboard com
base em dados dinâmicos ou entrada do usuário. Vamos usar a Animação Simples para ver um exemplo
básico de como fazer isso.

 Abra o SimpleAnimation.sln no Blend e abra o Page.xaml.cs.


 Adicione o seguinte código ao seu Loaded EventHandler.

BasicAnimation = new Storyboard();


da = new DoubleAnimation();
da.To = 350;
da.Duration = TimeSpan.FromMilliseconds(800);
Storyboard.SetTarget(da, OrangeShip);
Storyboard.SetTargetProperty(da, new PropertyPath("(Canvas.Left)"));
BasicAnimation.Children.Add(da);

Aqui estamos usando código de procedimento para instanciar um novo Storyboard. Depois
instanciamos uma DoubleAnimation para animar a propriedade Canvas.Left do objeto OrangeShip para
350 pixels em 800 milissegundos.

 Usando o código acima como um modelo, tente adicionar outra DoubleAnimation ao mesmo
Storyboard que anima a propriedade Canvas.Top do OrangeShip.
 Agora faça a chamada para Begin BasicAnimation.
 Execute a aplicação para ver o OrangeShip voar pela tela.

Reutilizando o Storyboard gerado

Agora vamos modificar e executar novamente o Storyboard com base na entrada do usuário.

 Abra o Page.xaml ou Page.xaml.cs para adicionar um EventHandler a MouseLeftButtonDown.


 Dentro do EventHandler, adicione o seguinte código:

da = BasicAnimation.Children[0] as DoubleAnimation;
da.To = e.GetPosition(LayoutRoot).X - OrangeShip.Width;
da = BasicAnimation.Children[1] as DoubleAnimation;
da.To = e.GetPosition(LayoutRoot).Y - OrangeShip.Height / 2;
BasicAnimation.Begin();

48
Como o Storyboard foi armazenado em uma variável de nível de classe, nós podemos manipulá-lo. Esse
código passa pela árvore de Filhos do Storyboard e redefine as propriedades To com base na posição do
mouse e no tamanho da nave.

Esse tipo de acesso a Storyboards possibilita uma maior flexibilidade, como a habilidade de modificar os
Storyboards existentes projetados dentro do Blend no tempo de execução para corresponder à sua
interação ou seus dados de tempo de execução.

49
Seção 4: Usando o DispatchTimer para
Animação de Procedimento

A construção do Storyboard é uma forma muito útil de animar sua interface de usuário, mas há alguns
casos em que você pode querer um evento de marcação de jogo para atualizar manualmente os objetos
usando seu próprio código. Abaixo você usará a classe DispatcherTimer para criar um efeito de chama
bastante simples para sua nave conduzida pela entrada do mouse.

 No Loaded EventHandler, adicione o seguinte código:

DispatcherTimer timer = new DispatcherTimer();


timer.Interval = TimeSpan.FromMilliseconds(100);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();

Essa é uma instanciação simples da classe DispatcherTimer. Uma vez que o método Start é chamado, o
evento Tick começa a disparar com base no Intervalo definido.

 Adicione este código ao seu Tick EventHandler:

Canvas.SetLeft(Tail, ((Canvas.GetLeft(OrangeShip) - Canvas.GetLeft(Tail) –


16) / 2) + Canvas.GetLeft(Tail));
Canvas.SetTop(Tail, ((Canvas.GetTop(OrangeShip) - Canvas.GetTop(Tail) + 45) /
2) + Canvas.GetTop(Tail));

Agora o objeto Tail vai se reposicionar para ficar um pouco mais próximo à nave a cada Tick. Ao
executar a aplicação, você verá algo como a imagem abaixo. Note a saída da cauda azul atrás do motor.

50
C- Integração de Navegador
do Silverlight
Tornando o Silverlight um cidadão de navegador de primeira classe

Introdução

O Silverlight 2 fornece uma funcionalidade que lhe permite ser um cidadão de primeira classe em uma
página HTML dentro do navegador. Ele possibilita uma série de recursos, incluindo (mas não limitado a):

 Funções de código .NET podem ser expostas ao navegador e chamadas a partir do JavaScript
 A árvore de renderização XAML pode ser exposta ao navegador e manipulada a partir de
JavaScript
 O código JavaScript dentro do HTML pode ser chamado de dentro do code-behind do .NET

Neste Laboratório você vai explorar cada um desses recursos, e as classes dentro do namespace
System.Windows.Browser que habilita essa funcionalidade. Você vai trabalhar com os conceitos por
trás de cada um deles, usando o Microsoft Virtual Earth como base, antes de explorar como eles são
usados dentro da aplicação de exemplo, a Margie’s Travel.

As aplicações de exemplo são chamadas de Sample 1, Sample 2 e Sample 3. Elas têm cópias com os
sufixos ‘Start’ e ‘Completed’. Você deve começar com as versões ‘Start’ e adicionar código a elas
conforme as instruções. Se tiver problemas, as versões de trabalho concluídas são fornecidas.

51
Seção 1 – Expondo Funções .NET ao
Navegador.
Dentro do diretório dos Laboratórios você encontrará um projeto chamado Sample 1. Abra a versão do
diretório Sample 1 – Completed e execute-a. Você verá uma tela como a da Figura 1. O objeto bege no
topo à esquerda é um objeto do Silverlight, abaixo dele estão três controles <Input> HTML e à direita
está um mapa do Virtual Earth. Se pressionar os botões, o conteúdo do Silverlight será atualizado com
as cidades do país selecionado.

Nós vamos construir a partir desse exemplo conforme avançarmos, então feche esse projeto e abra o do
diretório Sample 1 – Start.

Figura 1. Executando o Projeto Sample 1

Entendendo o XAML e o Código .NET do Silverlight

O conteúdo do Silverlight na Figura 1 usa XAML que contém um ItemsControl que contém um TextBlock,
que está ligado ao nome de uma cidade (‘CityName’).

52
<ItemsControl x:Name="_cities">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock FontSize="14" Height="30" Text="{Binding CityName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

O Code-Behind contém uma Classe usada para representar Cidades, e contém um elemento membro
chamado CityName.

public class CityData


{
public string CityName { get; set; }
public double Latitude{get;set;}
public double Longitude{get;set;}

public CityData(string strCityName, double nLatitude,


double nLongitude)
{
CityName = strCityName;
Latitude = nLatitude;
Longitude = nLongitude;
}
}

Observação: Os valores Latitude e Longitude serão usados em um outro exemplo.

A função de membro getCities vai retornar um List<CityData> contendo várias cidades de um


determinado país. Isso é embutido em código para fins de demonstração.

Veja um exemplo da construção de cidades para o ‘uk’.

switch (strCountry)
{
case "uk":
{
ret.Add(new CityData("London", 51.5, 0));
ret.Add(new CityData("Stratford-Upon-Avon", 52.3, -1.71));
ret.Add(new CityData("Edinburgh", 55.95, -3.16));
break;
}

Para ligar esses resultados ao ItemsControl, basta construir o List<CityData> e defini-lo de acordo
com a propriedade ItemsSource do ItemsControl. (‘_cities’ é o nome do ItemsControl)

Aqui está uma função que consegue isso. Adicione-a à página de code-behind do Page.xaml.cs:

public void upDateCities(string strCountry)


{
myCities = getCities(strCountry);

53
_cities.ItemsSource = myCities;
}

Essa função está disponível para o código .NET, mas como podemos expor ao navegador? Você verá
isso na próxima seção.

Expondo um método .NET ao JavaScript

Para expor seu código .NET ao navegador, você primeiro terá que fazer referência ao
System.Windows.Browser dentro de seu código no topo de seu code behind.

using System.Windows.Browser;

Em seguida você terá que adicionar um evento Loaded e um manipulador de eventos à sua página. Você
pode fazer isso dentro do construtor Page(), portanto adicione esta linha ao construtor Page():

this.Loaded += new RoutedEventHandler(Page_Loaded);

Agora você vai implementar o manipulador de eventos Page_Loaded e usá-lo para registrar seu controle
do Silverlight como um objeto “scriptável”. Nesse caso você vai registrar o objeto scriptable (não o
confunda com o objeto do Silverlight propriamente dito) como MySilverlightObject

Talvez você tenha notado, enquanto estava digitando a linha anterior, que o Visual Studio podia gerar
automaticamente um manipulador de eventos Page_Loaded para você. Se ele o fez, edite-o para ficar
como o código abaixo, caso contrário apenas adicione este código ao code-behind de seu Page.xaml.cs.

void Page_Loaded(object sender, RoutedEventArgs e)


{
HtmlPage.RegisterScriptableObject("MySilverlightObject", this);
}

Quando tiver feito isso, você poderá registrar seus métodos Public para serem “scriptáveis”, usando o
atributo [ScriptableMember]. Veja como expor o método upDateCities que vimos anteriormente
para que ele possa ser chamado do navegador, então lembre-se de adicionar o atributo, assim:

[ScriptableMember]
public void upDateCities(string strCountry)
{
myCities = getCities(strCountry);
_cities.ItemsSource = myCities;
}

54
Na próxima seção você verá como chamar isso a partir do JavaScript.

Chamando o Método .NET a partir do JavaScript.

A primeira coisa da qual deve se certificar é se o objeto do Silverlight tem um ID. Isso é necessário para
que o JavaScript possa ter uma referência a ele.

<object data="data:application/x-silverlight,"
type="application/x-silverlight-2-b2"
width="300" height="400" id="slControl">

...

</object>

Agora o JavaScript pode ter uma referência ao seu controle do Silverlight com base neste ID:

var slPlugin = document.getElementById("slControl");

Para chamar o método baseado em .NET você usa a sintaxe

<PluginID>.content.<ScriptableObjectName>.method(parameters)

Neste caso ela fica assim:

slPlugin.content.MySilverlightObject.upDateCities("uk");

No Projeto Sample 1 você precisará de uma função do JavaScript chamada doCities que pega um
parâmetro e o usa para chamar o código .NET. Aqui está a função - adicione-a a um bloco do script em
Sample1TestPage.html dentro do projeto Sample1Web:

function doCities(country)
{
var slPlugin = document.getElementById("slControl");
slPlugin.content.MySilverlightObject.upDateCities(country);
}

Os botões de Entrada de HTML chamam isso e passam o devido país. Aqui está sua declaração, você
pode vê-la no final do Sample1TestPage.html:

<input id="bUK" type="button" value="uk" onclick="doCities('uk');" />

<input id="bGermany" type="button" value="germany"


onclick="doCities('germany');"/>

55
<input id="bFrance" type="button" value="france" onclick="doCities('france');"
/>

O resultado final é que quando o usuário pressiona um botão de HTML, a função doCities do JavaScript é
executada. Isso chama o objeto scriptável do Silverlight passando a ele o parâmetro que recebe. Agora o
código .NET assume e usa esse parâmetro para criar uma List<CityData> de cidades para o país
selecionado, que será então ligada ao Items Presenter.

56
Seção 2 – Manipulando a Árvore de
Renderização XAML a partir do
Navegador
No exemplo anterior você viu como pode acessar o código .NET dentro do JavaScript, mas além disso
você também pode manipular o XAML que o Silverlight renderiza usando o JavaScript no navegador.
Você tem várias opções para fazer isso.

Atualizando elementos XAML existentes

Primeiro, se você tem um elemento XAMl existente, pode encontrá-lo usando o método findName do
controle do Silverlight, e definir seus conteúdos.

Abra o projeto Sample2 do diretório Sample2 – Completed e execute-o. Você pode ver isso na Figura 2.

Figura 2. O Projeto Sample 2

57
Você pode ver que isso atualiza o Sample 1 adicionando o nome do país ao topo do conteúdo do
Silverlight. Feche isso e abra o projeto Sample2 – Start, que você usará para trabalhar.

O XAML precisa ser atualizado com um novo TextBlock, então adicione o XAML destacado ao Page.xaml
no lugar correto.

<UserControl x:Class="Sample1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontFamily="Trebuchet MS" FontSize="11"
Width="300" Height="400">
<Grid x:Name="LayoutRoot" Background="Beige">
<StackPanel x:Name="stk">
<TextBlock FontSize="40" Foreground="Red"
x:Name="txtCountry">Country</TextBlock>
<ItemsControl x:Name="_cities">

</ItemsControl>
</StackPanel>
</Grid>
</UserControl>

No JavaScript, se você tiver uma referência ao objeto do Silverlight chamado slControl, pode corrigir os
conteúdos do TextBlock usando o seguinte JavaScript:

slPlugin.content.findName("txtCountry").text = "germany";

Dê uma olhada no Sample2TestPage.html, e encontre a função do JavaScript chamada ‘doCities’. Você


verá que ela está vazia, então atualize-a com o seguinte código:

function doCities(country)
{
var slPlugin = document.getElementById("slControl");
slPlugin.content.MySilverlightObject.upDateCities(country);

slPlugin.content.findName("txtCountry").text = country;
}

Agora o efeito é que quando você pressiona os botões de HTML, a lista de cidades do Silverlight se
atualiza com as cidades desse país, e o título do país é definido de acordo.

Criando novos Elementos XAML

O Silverlight não se restringe ao XAML que é definido como parte do projeto Visual Studio / Blend. Ele
pode ser adicionado dinamicamente no tempo de execução dentro da ferramenta do JavaScript. Neste
exemplo simples você verá como fazer isso.

58
Se você consultar a listagem do XAML anterior, verá que o StackPanel que contém a lista de cidades é
chamada de ‘stk’. Você vai adicionar o novo XAML a esse elemento como filhos desse elemento.

Veja um exemplo:

var xamlFragment =
'<TextBlock FontSize="20" Foreground="Blue">' + Date() + '</TextBlock>';

tb = slPlugin.content.createFromXaml(xamlFragment,false);
slPlugin.content.findName("stk").children.add(tb);

Você pode adicionar esse código à função doCities do outro exemplo, e sempre que o usuário clicar no
botão um novo TextBlock do XAML será adicionado ao StackPanel, e esse TextBlock vai conter a Data e o
Horário atuais. Aqui está o código que faz isso:

function doCities(country)
{
var slPlugin = document.getElementById("slControl");
slPlugin.content.MySilverlightObject.upDateCities(country);

slPlugin.content.findName("txtCountry").text = country;

var xamlFragment =
'<TextBlock FontSize="20" Foreground="Blue">' + Date() + '</TextBlock>';
tb = slPlugin.content.createFromXaml(xamlFragment,false);
slPlugin.content.findName("stk").children.add(tb);
}

O resultado dessa execução e de clicar em vários botões é mostrado na Figura 3.

Figura 3. Adicionando novo XAML

59
Lembre-se de que não é um XAML existente que você está manipulando. Você está adicionando um
novo XAML à árvore de renderização no tempo de execução. Isso torna o Silverlight bastante flexível
para atender as necessidades de suas aplicações conectadas.

Mais Estudo

Além de manipular XAML existente e adicionar novo XAML, você também pode remover o XAML.
Quando tiver uma referência a um elemento de container (como ‘stk’), você pode usar Remove ou
RemoveAt para remover elementos. Além disso, pode fazer referência a itens nos filhos de um container
usando getItem(index). Aqui há um bom material: http://msdn.microsoft.com/en-
us/library/bb980118(VS.95).aspx

60
Seção 3 – Chamando o Script do
Navegador a partir de .NET

Agora nós vimos como você pode chamar o código .NET a partir do JavaScript dentro do navegador –
mas e a outra maneira? Nesta seção você verá como fazer exatamente isso. Como exemplo, o SDK do
Virtual Earth é baseado em JavaScript, então, em vez de construir seu próprio host do Silverlight para o
Virtual Earth, é muito mais fácil fazer o Silverlight chamar o navegador e o JavaScript chamar os serviços
do Virtual Earth em seu nome.

Carregue o projeto ‘Sample3’ do diretório Sample3 – Completed e execute-o. Anteriormente você viu
que pressionar os botões de HTML que representam os diferentes países fará com que as cidades desses
países sejam carregadas para o conteúdo do Silverlight. O projeto Sample3 contribui para isso – quando
você seleciona o nome de uma cidade (lembre-se disso na área do Silverlight), será feita uma chamada
para o navegador, para uma função do JavaScript que localiza a cidade selecionada no mapa com base
em sua latitude e longitude.

Você pode ver isso na Figura 4.

61
Figura 4. Chamando o navegador a partir do Silverlight para usar o SDK do Virtual Earth

Feche esse projeto e abra o projeto Sample3 – Start, que você usará para construir a funcionalidade
completa.

Primeiro, note que o XAML precisa ser atualizado para que o ItemsControl se ligue a CityName,
Longitude e Latitude. Os últimos elementos ficam ‘ocultos’ se você definir sua altura em ‘0’. Você terá
que atualizar o XAML para adicionar o seguindo código ao DataTemplate.

Veja como seu XAML deve ficar:

<ItemsControl x:Name="_cities">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
MouseLeftButtonUp="StackPanel_MouseLeftButtonUp">
<TextBlock FontSize="14" Height="30" Text="{Binding CityName}" />
<TextBlock Height="0" Text="{Binding Latitude}" />
<TextBlock Height="0" Text="{Binding Longitude}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Note também que o manipulador de eventos de Mouse é conectado ao StackPanel que contém os
elementos TextBlock. Isso significa que o evento será capturado pelo container, e os valores de cada um
de seus filhos podem ser derivados, permitindo receber o Name, a Latitude e a Longitude da cidade
atual.

Vamos dar uma olhada nesse manipulador de eventos. Adicione este código ao Page.xaml.cs quando
estiver trabalhando.

Primeiro, vamos converter o ‘sender’ a um StackPanel, e seus filhos a TextBlocks:

private void StackPanel_MouseLeftButtonUp(object sender,


MouseButtonEventArgs e)
{
StackPanel s = sender as StackPanel;
TextBlock t0 = s.Children[0] as TextBlock;
TextBlock t1 = s.Children[1] as TextBlock;
TextBlock t2 = s.Children[2] as TextBlock;

Agora podemos receber o nome da cidade, sua latitude e longitude a partir destes objetos:

strCurrentCity = t0.Text;
nCurrentLatitude = Convert.ToDouble(t1.Text);
nCurrentLongitude = Convert.ToDouble(t2.Text);

É aqui que começa a diversão. Lembre-se primeiro de adicionar uma referência a


System.Windows.Browser no topo da página de código. Isso nos dá uma referência às classes de

62
que precisaremos: A classe HtmlPage nos permite ter uma referência a uma função do JavaScript
invocando seu método Window.GetProperty e passando a ele o nome da função. Isso tem que ser
convertido como um ScriptObject para poder ser chamado:

ScriptObject myScriptMethod =
(ScriptObject) HtmlPage.Window.GetProperty("plotCity");

Agora você pode chamar a função usando InvokeSelf e passando a ela os


parâmetros:

myScriptMethod.InvokeSelf(nCurrentLatitude, nCurrentLongitude);

É claro que a função plotCity tem que existir na página, e tem que corresponder à assinatura que está
sendo usada para chamá-la.

Aqui está a função do JavaScript que pega a latitude e a longitude e centraliza o mapa do Virtual Earth
conforme essas coordenadas. Adicione isso ao Sample3TestPage.html:

function plotCity(latitude, longitude)


{
tourMap.LoadMap(new VELatLong(latitude, longitude),8);
}

*Note que ‘8’ é o nível de zoom. Veja o SDK do Virtual Earth para mais detalhes:
http://dev.live.com/virtualearth/sdk/]

Mais Estudo

Além de chamar Funções do JavaScript, você também pode manipular elementos HTML.

Você faz isso atribuindo um HtmlElement aos resultados de uma chamada de função
HtmlPage.Document.GetElementByID(‘elementName’).

Depois você pode manipular isso chamando o método ‘setAttibute’ da instância de HtmlElement
passando a ele um nome e um valor de atributo como esse element.setAttribute(“attributeName”,
value).

63
D- Personalizando a Aparência
Modelagem de Controle, Styling (Estilo), Visual State Manager, controles de
Subclasse.

Introdução

O Silverlight oferece muitas opções diferentes para personalizar sua interface de usuário e torná-la
atraente:

 Você pode simplesmente definir propriedades em cada controle (usando código ou atributos
XAML) e ter um nível básico de personalização.
 Você também pode usar o Styling, que oferece o mesmo nível de personalização (definição de
valores em propriedades existentes) de modo mais reutilizável e amigável para o designer.
 Finalmente, a modelagem de controle o libera para redefinir a aparência de um controle
substituindo suas partes.

Todos esses recursos podem ser combinados e correspondidos para ter a maior flexibilidade e
maximizar a reutilização de seus ativos.

Objetivos do Laboratório:

Neste laboratório você vai aprender sobre recursos, estilo e modelagem de controle. Vamos criar um
controle deslizante personalizado para a aplicação Voyager.

64
Exercício 1: Criando o controle deslizante do Voyager

Neste exercício você vai usar o Expression Blend para criar um controle deslizante com certa
personalização para ser usado no Voyager.

O controle deslizante tem esta aparência:

Aqui estão as personalizações do controle


deslizante:

No Exercício 1, você vai usar o Expression Blend para desativar os RepeatButtons e criar a aparência
personalizada para o elevador. Você não vai escrever nenhum código neste exercício.

65
Tarefa 1: Montando seu controle deslizante

1. Inicie o Expression Blend e crie uma nova Aplicação do Silverlight 2. O nome do projeto não
importa, já que você vai criar XAML e pode então recortar e colar na aplicação do Voyager.

2. Arraste e solte um controle deslizante na superfície de design de nosso Page.xaml.


[Dica: Você pode encontrar o controle deslizante (Slider) na Caixa de Ferramentas, na mesma
seção em que estão Button e outros controles, ou selecionando o botão ‘>>’ na parte inferior da
Caixa de Ferramentas e depois ‘Slider’ na biblioteca de ativos].

3. Clique com o botão direito em seu controle deslizante e selecione ‘Edit a Copy’ para Editar o
Modelo.

66
Editar o Modelo dessa forma nos permitirá personalizar o Modelo de Controle padrão para
controle deslizante (podemos adicionar/remover partes, mudar as transições de estado, etc.).

A caixa de diálogo Edit Template vai pedir o nome e um escopo para nosso modelo.

4. Digite VoyagerSlider no nome.

5. Deixe “This document” marcado como o alvo para essa definição.


Significa que o estilo (gerado pelo Blend) e o Modelo desse controle só estarão disponíveis
dentro do Page.xaml. Se tivéssemos escolhido App.xaml, ele estaria disponível em qualquer
Página da aplicação.

67
6. Clique na exibição SplitView ou XAML para analisar o que o Blend acabou de fazer por você:

O Blend extraiu o Estilo padrão (e o Modelo de Controle para o Controle Deslizante) e o colocou na
coleção UserControl.Resources para que você possa reutilizá-lo dentro da página.

68
Tarefa 2: Personalizando o controle deslizante

Se você olhar o ControlTemplate para controle deslizante, verá que há dois Grids principais dentro do
modelo: “HorizontalTemplate” e “VerticalTemplate”. Essas grades encapsulam a aparência padrão do
controle deslizante quando ele está na respectiva Orientação. Este exercício vai focar apenas na
personalização do Modelo Horizontal (Horizontal Template).

O HorizontalTemplate tem:

 dois controles RepeatButton,


 um Rectangle (como faixa) e
 Um Thumb (Elevador).

Se você estiver na exibição de Design, pode vê-los na exibição de Objetos e Linha do Tempo – assim:

No controle deslizante do Voyager, não há nenhuma faixa visível, então comece tornando a “Track”
(Faixa) transparente.

1. Modifique o elemento [Rectangle]para que seu Traço e Preenchimento fiquem transparentes


[Dica, você faz isso no Blend clicando com o botão direito no editor de pincel e selecionando
“Reset”+

Agora, desative os RepeatButtons para que eles não interfiram em nossos controles deslizantes
sobrepostos.

2. Selecione HorizontalTrackLargeDecreaseRepeatButton na Guia Objects and Timeline

Após selecioná-lo, a janela de propriedades mostra todas as propriedades para


HorizontalTrackLargeDecreaseRepeatButton.

69
3. Mude a propriedade IsHitTestVisible para “False” (ou Unchecked).
[Dica: você pode usar a Janela de Pesquisa na Guia de propriedades; digite “IsH” (menos as
aspas) e o Filtro vai trazer IsHitTestVisible].

Você está mudando IsHitTestVisible para falso em oposição a configurar a Visibilidade como
recolhida porque a Visibilidade afeta o layout e o Controle Deslizante está alongando/reduzindo
o botão para alterar a posição do Elevador.

Você não mudou “IsEnabled” para falso apenas por causa de um problema conhecido na beta2.
Para o RTM, IsEnabled deve resolver o problema.

4. Agora selecione HorizontalTrackLargeDecreaseRepeatButton e mude IsHitTestVisible para


“False” também.

Ótimo! Agora nosso trabalho no controle deslizante está pronto, mas precisamos modificar o
modelo do Elevador dentro do controle deslizante.

70
Tarefa 3: Personalizando o Elevador

Agora vamos personalizar o modelo do Elevador no controle deslizante.

5. Selecione HorizontalThumb no HorizontalTemplate e Edit the Template.

6. Quando nome e local forem solicitados, insira “SliderThumbStyle” no nome e o escopo para
definição está dentro de This document.

71
Note como o Modelo do elevador tem muitos elementos: ThumbOurterBorderFill,
ThumbOuterRoundBorder, etc. Em nosso caso, queremos uma aparência completamente
diferente/nova, então podemos excluir todos eles.

7. Selecione todos os elementos em VoyagerThumbStyle e exclua todos eles, exceto o Grid na Raiz.

Agora, você pode definir sua própria aparência para o elevador.

8. Pegue a ferramenta Pen no Blend e desenhe o formato para seu elevador.


[Dica, certifique-se de que tem o elemento [Grid] selecionado quando pegar a Pen, pois você
desenhará na grade].
Use a criatividade (sinta-se à vontade para fazer um triângulo, uma estrela, o que quiser). O
elevador padrão no Voyager é assim:

72
OBSERVAÇÃO: Para tornar meu desenho mais fácil, usei o zoom de 800%, tinha Snaplines sendo
exibidos e “Snap to grid” estava ativo também.

73
Tarefa 4: Testando nosso controle deslizante.

Essas são todas as alterações necessárias para personalizar o Controle Deslizante e o Elevador no
Voyager. Agora vamos ver se funciona.

9. Dentro do Blend, clique em Test Solution (no Menu Project) para ver como é seu controle
deslizante. Não se preocupe se não for muita coisa, ele vai se integrar bem ao controle de linha
do tempo de seu Voyager.

Tarefa 5: Exercícios para o usuário.


Durante as Tarefas 1-4 você se concentrou no Modelo Horizontal do controle deslizante. Você deve
voltar e aplicar mudanças similares ao modelo vertical.

Resumo desse Exercício:


Embora não tenha escrito nenhum código, você redefiniu a aparência do elevador e alterou
radicalmente a aparência e o comportamento do controle deslizante. Você não escreveu código nesse
exercício, cada mudança foi feita com o Blend de uma forma amigável para o designer.

74
Apêndices

Exercício 1: Respostas das Tarefas

Tarefa 2: Personalizando o controle deslizante.

Seu estilo dentro da seção UserControl.Resources deve ficar assim:

<Style x:Key="VoyagerSlider" TargetType="Slider">


<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Slider">
<Grid x:Name="Root">
<Grid.Resources>
<ControlTemplate
x:Key="RepeatButtonTemplate">
<Grid x:Name="Root"
Background="Transparent" Opacity="0"/>
</ControlTemplate>
</Grid.Resources>

<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup
x:Name="CommonStates">

<vsm:VisualStateGroup.Transitions>

<vsm:VisualTransition Duration="0"/>

</vsm:VisualStateGroup.Transitions>
<vsm:VisualState
x:Name="Normal"/>
<vsm:VisualState
x:Name="MouseOver"/>
<vsm:VisualState
x:Name="Disabled">
<Storyboard>

<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Root"
Storyboard.TargetProperty="(UIElement.Opacity)">

<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.5"/>

</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup
x:Name="FocusStates">

75
<vsm:VisualStateGroup.Transitions>

<vsm:VisualTransition Duration="0"/>

</vsm:VisualStateGroup.Transitions>
<vsm:VisualState
x:Name="Unfocused"/>
<vsm:VisualState
x:Name="Focused">
<Storyboard>

<DoubleAnimationUsingKeyFrames Storyboard.TargetName="FocusVisual"
Storyboard.TargetProperty="(UIElement.Opacity)">

<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>

</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>

</vsm:VisualStateManager.VisualStateGroups>
<Grid
x:Name="HorizontalTemplate">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="Auto"/>
<ColumnDefinition
Width="Auto"/>
<ColumnDefinition
Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle Height="3"
Margin="5,0,5,0" Grid.Column="0" Grid.ColumnSpan="3" StrokeThickness="0.5"/>
<RepeatButton
IsTabStop="False" Template="{StaticResource RepeatButtonTemplate}"
x:Name="HorizontalTrackLargeChangeDecreaseRepeatButton" Grid.Column="0"
IsHitTestVisible="False"/>
<Thumb Height="18"
x:Name="HorizontalThumb" Width="11" Grid.Column="1"/>
<RepeatButton
IsTabStop="False" Template="{StaticResource RepeatButtonTemplate}"
x:Name="HorizontalTrackLargeChangeIncreaseRepeatButton" Grid.Column="2"
IsHitTestVisible="False"/>
</Grid>
<Grid x:Name="VerticalTemplate"
Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition
Height="*"/>
<RowDefinition
Height="Auto"/>
<RowDefinition
Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle
Margin="0,5,0,5" Width="3" Grid.Row="0" Grid.RowSpan="3" Fill="#FFE6EFF7"
Stroke="Black" StrokeThickness="0.5"/>
<RepeatButton
IsTabStop="False" Template="{StaticResource RepeatButtonTemplate}"
x:Name="VerticalTrackLargeChangeDecreaseRepeatButton" Grid.Row="2"/>

76
<Thumb Height="11"
x:Name="VerticalThumb" Width="18" Grid.Row="1"/>
<RepeatButton
IsTabStop="False" Template="{StaticResource RepeatButtonTemplate}"
x:Name="VerticalTrackLargeChangeIncreaseRepeatButton" Grid.Row="0"/>
</Grid>
<Rectangle x:Name="FocusVisual"
Stroke="#666666" StrokeDashArray=".2 5" StrokeDashCap="Round"
IsHitTestVisible="false" Opacity="0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Tarefa 3: Personalizando o Elevador


Aqui está o UserControl.Resources com o Elevador e com o Controle Deslizante.

<UserControl.Resources>
<Style x:Key="VoyagetThumbStyle" TargetType="Thumb">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Grid>
<Grid.Resources>
<SolidColorBrush
x:Key="Background" Color="#FF003255"/>
<Color
x:Key="ThumbOuterBorderFillColor1">#FFF9FAFA</Color>
<Color
x:Key="ThumbOuterBorderFillColor2">#FFEDF1F4</Color>
<Color
x:Key="ThumbOuterBorderFillColor3">#FFE2E8EF</Color>
<Color
x:Key="ThumbOuterBorderFillColor4">#FFAFB9C1</Color>
<SolidColorBrush
x:Key="ThumbInnerRoundBorderBrush" Color="#FFFFFFFF"/>
<SolidColorBrush
x:Key="ThumbOuterRoundBorderBrush" Color="#FF000000"/>
<Color
x:Key="ThumbInnerBorderFillColor1">#CDFFFFFF</Color>
<Color
x:Key="ThumbInnerBorderFillColor2">#45FFFFFF</Color>
<Color
x:Key="MouseOverThumbOuterBorderFillColor1">#FFEAF0F0</Color>
<Color
x:Key="MouseOverThumbOuterBorderFillColor2">#FFDCE5EC</Color>
<Color
x:Key="MouseOverThumbOuterBorderFillColor3">#FFD5DDE6</Color>
<Color
x:Key="MouseOverThumbOuterBorderFillColor4">#FF798893</Color>
<Color
x:Key="MouseOverThumbInnerBorderFillColor1">#CDFFFFFF</Color>
<Color
x:Key="MouseOverThumbInnerBorderFillColor2">#45FFFFFF</Color>
<Color
x:Key="PressedThumbOuterBorderFillColor1">#FFEAF0F0</Color>
<Color
x:Key="PressedThumbOuterBorderFillColor2">#FFDCE5EC</Color>

77
<Color
x:Key="PressedThumbOuterBorderFillColor3">#FFD5DDE6</Color>
<Color
x:Key="PressedThumbOuterBorderFillColor4">#FF798893</Color>
<Color
x:Key="PressedThumbInnerBorderFillColor1">#CDFFFFFF</Color>
<Color
x:Key="PressedThumbInnerBorderFillColor2">#45FFFFFF</Color>
<Color
x:Key="DisabledThumbOuterBorderFillColor1">#FFF9FAFA</Color>
<Color
x:Key="DisabledThumbOuterBorderFillColor2">#FFEDF1F4</Color>
<Color
x:Key="DisabledThumbOuterBorderFillColor3">#FFE2E8EF</Color>
<Color
x:Key="DisabledThumbOuterBorderFillColor4">#FFC3C9CD</Color>
<Color
x:Key="DisabledThumbInnerBorderFillColor1">#CDFFFFFF</Color>
<Color
x:Key="DisabledThumbInnerBorderFillColor2">#45FFFFFF</Color>
</Grid.Resources>

<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup
x:Name="CommonStates">

<vsm:VisualStateGroup.Transitions>

<vsm:VisualTransition Duration="0:0:0.1" To="MouseOver"/>

<vsm:VisualTransition Duration="0:0:0.1" To="Pressed"/>

<vsm:VisualTransition Duration="0:0:0.1" To="Disabled"/>

</vsm:VisualStateGroup.Transitions>
<vsm:VisualState
x:Name="Normal"/>
<vsm:VisualState
x:Name="MouseOver">
<Storyboard/>
</vsm:VisualState>
<vsm:VisualState
x:Name="Pressed">
<Storyboard/>
</vsm:VisualState>
<vsm:VisualState
x:Name="Disabled">
<Storyboard/>
</vsm:VisualState>
</vsm:VisualStateGroup>

</vsm:VisualStateManager.VisualStateGroups>
<Path
HorizontalAlignment="Stretch" Margin="-0.625,-0.5,-0.75,0.125"
VerticalAlignment="Stretch" Stretch="Fill" StrokeThickness="1" Data="M-
0.12461852,-0.0002709807 L11.250342,-0.0002709807 L11.249977,9.5004301
L6.0003605,17.375883 L0.00038105118,9.7503767 z" Stroke="#FF696A6E">
<Path.Fill>

<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">


<GradientStop
Color="#FF53050E" Offset="1"/>

78
<GradientStop
Color="#FFFFFFFF" Offset="0.002"/>

</LinearGradientBrush>
</Path.Fill>
</Path>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="VoyagerSlider" TargetType="Slider">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Slider">
<Grid x:Name="Root">
<Grid.Resources>
<ControlTemplate
x:Key="RepeatButtonTemplate">
<Grid x:Name="Root"
Background="Transparent" Opacity="0"/>
</ControlTemplate>
</Grid.Resources>

<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup
x:Name="CommonStates">

<vsm:VisualStateGroup.Transitions>

<vsm:VisualTransition Duration="0"/>

</vsm:VisualStateGroup.Transitions>
<vsm:VisualState
x:Name="Normal"/>
<vsm:VisualState
x:Name="MouseOver"/>
<vsm:VisualState
x:Name="Disabled">
<Storyboard>

<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Root"
Storyboard.TargetProperty="(UIElement.Opacity)">

<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.5"/>

</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup
x:Name="FocusStates">

<vsm:VisualStateGroup.Transitions>

<vsm:VisualTransition Duration="0"/>

</vsm:VisualStateGroup.Transitions>
<vsm:VisualState
x:Name="Unfocused"/>
<vsm:VisualState
x:Name="Focused">
<Storyboard>

79
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="FocusVisual"
Storyboard.TargetProperty="(UIElement.Opacity)">

<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>

</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>

</vsm:VisualStateManager.VisualStateGroups>
<Grid
x:Name="HorizontalTemplate">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="Auto"/>
<ColumnDefinition
Width="Auto"/>
<ColumnDefinition
Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle Height="3"
Margin="5,0,5,0" Grid.Column="0" Grid.ColumnSpan="3" StrokeThickness="0.5"/>
<RepeatButton
IsTabStop="False" Template="{StaticResource RepeatButtonTemplate}"
x:Name="HorizontalTrackLargeChangeDecreaseRepeatButton" Grid.Column="0"
IsHitTestVisible="False"/>
<Thumb Height="18"
x:Name="HorizontalThumb" Style="{StaticResource VoyagetThumbStyle}" Width="11"
Grid.Column="1"/>
<RepeatButton
IsTabStop="False" Template="{StaticResource RepeatButtonTemplate}"
x:Name="HorizontalTrackLargeChangeIncreaseRepeatButton" Grid.Column="2"
IsHitTestVisible="False"/>
</Grid>
<Grid x:Name="VerticalTemplate"
Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition
Height="*"/>
<RowDefinition
Height="Auto"/>
<RowDefinition
Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle
Margin="0,5,0,5" Width="3" Grid.Row="0" Grid.RowSpan="3" Fill="#FFE6EFF7"
Stroke="Black" StrokeThickness="0.5"/>
<RepeatButton
IsTabStop="False" Template="{StaticResource RepeatButtonTemplate}"
x:Name="VerticalTrackLargeChangeDecreaseRepeatButton" Grid.Row="2"/>
<Thumb Height="11"
x:Name="VerticalThumb" Width="18" Grid.Row="1"/>
<RepeatButton
IsTabStop="False" Template="{StaticResource RepeatButtonTemplate}"
x:Name="VerticalTrackLargeChangeIncreaseRepeatButton" Grid.Row="0"/>
</Grid>
<Rectangle x:Name="FocusVisual"
Stroke="#666666" StrokeDashArray=".2 5" StrokeDashCap="Round"
IsHitTestVisible="false" Opacity="0"/>
</Grid>
</ControlTemplate>

80
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>

81
E- Layout Personalizado no
Silverlight
Criando Painéis de Layout Personalizado

Introdução

A maioria das aplicações web podem se beneficiar com um layout dinâmico que pode se ‘redimensionar’
para tirar proveito do estado real disponível na tela.

No Silverlight2, os ‘containers’ usados para Layout são chamados de Panels (painéis) porque são classes
que geralmente herdam da classe Panel.

O Silverlight vem com três painéis de layout:

 Canvas possibilita um posicionamento absoluto de seus elementos Filhos. Você define um


Canvas.Left e Canvas.Top em um Elemento de interface de usuário, e o Canvas o coloca no
deslocamento Top, Left (Superior, Esquerda) apropriado a partir de sua posição 0,0.
 StackPanel é um painel simples que pode ser orientado em uma direção horizontal ou vertical e
coloca seus filhos um ao lado do outro nessa direção.
 Grid é o container de layout mais potente do Silverlight. Ele tem um comportamento de tabela
em que os itens podem ser posicionados explicitamente em uma combinação de
Linhas/Colunas. O Grid tem suporte para:
o “Starsizing” ou uso de uma porcentagem do estado real disponível na tela.
o ColumnSpan/RowSpan para elementos de interface de usuário que querem usar mais
que uma única Linha ou Coluna.
o Margem e Preenchimento.

Se você precisar de um layout diferente dos que vêm com o produto, é fácil criar um painel
personalizado. O Silverlight, como o WPF, faz o que é chamado de layout em dois passos:

1. Um container vai chamar Measure () em cada um de seus filhos. Durante o passo Measure, o
container está dizendo aos filhos quanto elemento está disponível.
a. Durante o Measure, cada Filho define sua propriedade DesiredSize para que o container
saiba de quanto espaço o elemento Filho vai precisar.

82
2. Quando o container tiver medido seus filhos, ele os organiza (Arrange()) nos lugares adequados.
Organizá-los significa posicionar no deslocamento certo a partir da coordenada 0,0 no Painel.

Objetivos do Laboratório:

Neste laboratório você vai além dos painéis básicos que vêm com o Silverlight 2, criando vários painéis
personalizados. Você vai construir:

 Um WrapPanel básico para se familiarizar com os fundamentos do Layout


 Um painel personalizado chamado “TimelinePanel” para ser usado na aplicação Voyager.

83
Seção 1: Criando um WrapPanel
Personalizado

Neste exercício você vai construir um WrapPanel simples.

Um WrapPanel posiciona seus filhos em um layout de fluxo seqüencial, da esquerda para a direita.
Se o painel alcançar o final do lado direito, ele começará uma nova linha e continuará dispondo os itens
da esquerda para a direita na linha seguinte.

Veja um exemplo de um WrapPanel dispondo alguns filhos.

Para criar um WrapPanel você vai herdar de Panel; ele dá a você a coleção Children (Filhos), e as funções
Measure/Arrange (Medir/Organizar) necessárias; você vai substituí-los para personalizar o layout.

84
Tarefa 1: Crie uma classe WrapPanel

Crie uma nova aplicação do Silverlight, e chame-a de MargiesTravel.Controls.

[Se o Visual Studio pedir para que você crie uma aplicação web para o projeto, fique à vontade para
dizer Não e apenas diga ao VS para criar uma página HTML para teste].

1. Adicione uma nova classe, chamada WrapPanel para o projeto MargiesTravel.Controls.


2. Faça sua classe WrapPanel herdar de Panel

public class WrapPanel : Panel { }

85
Tarefa 2: Meça seus Filhos para encontrar seu Tamanho
Desejado.

O WrapPanel herda uma coleção de Children (Filhos) de Panel, e é assim que vai acessar itens nela.

Para descobrir o DesiredSize (Tamanho Desejado) de cada um de seus filhos, o WrapPanel terá que
chamar Measure() em cada filho. Você pode realizar isso implementando (ou substituindo) a função
MeasureOverride do Panel. Essa função será chamada sempre que ocorrer um passo de layout (e antes
de Arrange).

Nós não fazemos nenhum trabalho além de Medir (Measure) os itens (isto é, não os posicionamos
durante um passo de medida).

1. Substitua a classe MeasureOverride de seu WrapPanel e chame Measure() para cada um dos
Children do painel.

protected override Size MeasureOverride(Size availableSize)


{
foreach (UIElement child in Children)
{
child.Measure(availableSize);
}
}

Note que o parâmetro availableSize que você está passando para Measure é o tamanho disponível para
nosso WrapPanel. Passando esse Size (Tamanho) para cada filho, o WrapPanel está dizendo a cada Child
(Filho) que esse espaço está disponível. Se o DesiredSize (tamanho desejado) do filho for maior que o
availableSize (tamanho disponível), ele será recortado. Por exemplo, se o DesiredSize.Width do Child
for 500, mas o availableSize.Width do WrapPanel for 400, apenas os primeiros 400 pixels estarão
visíveis. Os outros 100 serão recortados.

86
Tarefa 3: Organize nossos filhos

Quando você mediu as dimensões dos filhos, cada um deles definiu sua propriedade DesiredSize com o
tamanho que queria ter; agora você precisa ler esse tamanho e posicioná-los dentro do espaço do Panel.

Substitua o método ArrangeOverride na classe de nosso Panel.

protected override Size ArrangeOverride(Size finalSize)


{

Dentro de ArrangeOverride vamos organizar – Arrange() – ou posicionar os itens. Usaremos o


availableWidth do Panel para dispor os itens da esquerda para a direita, sem perder de vista cada Left
(esquerda) do painel, e quando chegarmos ao final da linha, iniciaremos uma nova.

1. Crie um loop (for ou foreach) para iterar nos filhos


2. Chame Arrange () nos Children () dando a ele um Rect () de onde precisa estar.

O código está abaixo, mas ele é pré-comentado, então você pode comentar as linhas de que precisa.
(Sou astuto, não?)

//protected override Size ArrangeOverride(Size finalSize)


//{
// double currentLeft = 0;
// double currentTop = 0;
// double currentRowHeight = 0 ;

// foreach (UIElement child in Children)


// {
// // Verifique se ele não cabe na linha atual
// if ((currentLeft + child.DesiredSize.Width) >
finalSize.Width)
// {
// //Inicie uma nova linha redefinindo nossas coisas
// currentLeft = 0;
// currentTop += currentRowHeight;
// currentRowHeight = 0;
// }
// // isto faz o posicionamento do filho
// child.Arrange(new Rect(currentLeft, currentTop,
child.DesiredSize.Width, child.DesiredSize.Height));
// // mude para o próximo espaço disponível.
// currentLeft += child.DesiredSize.Width ;
// currentRowHeight = Math.Max( currentRowHeight,
child.DesiredSize.Height );
// }
// return finalSize;

87
Tarefa 4: Exercitando nosso Painel

O WrapPanel está bastante básico agora, ele não manipula Margem ou Visibilidade, mas é bom o
suficiente para ser exibido. Vamos entrar no Page.xaml e criar uma instância do WrapPanel e dar a ele
alguns filhos.

1. No Page.xaml, na declaração UserControl, adicione uma declaração xmlns para seu namespace

<UserControl x:Class="MargiesTravel.Controls.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MargiesTravel.Controls"
Width="400" Height="300">

2. Dentro do Grid, insira um WrapPanel e lembre-se de colocar nele um prefixo com o namespace
xml que você declarou no passo acima (se copiou e colou, o namespace é lcl).

<lcl:WrapPanel>

3. Agora, dentro do WrapPanel, apenas digite Elementos XAML válidos (Button, Rectangle,
TextBlock, etc.). Já que nosso WrapPanel é burro e não faz o alongamento, lembre-se de
especificar para todos os seus itens uma Width e uma Height. Novamente, se você passar do
espaço disponível seus Filhos serão recortados.
[Dica, olhe as Respostas para ver sugestões sobre inserção de Filhos]

88
Seção 2 – Implementando um
TimelineStackPanel para a aplicação do
Voyager

O WrapPanel é fácil porque não há nenhum metadado fornecido dentro do Panel. Isso é bastante
incomum. Se reparar nos outros containers de layout (como Grid e Canvas), você anexou propriedades
como Canvas.Left e Canvas.Top ou Grid.Row e Grid.Column que os itens inseridos nesses painéis usam
como dicas do painel em que precisam estar.

Desta vez, para sua aplicação de viagem, vamos criar um TimelinePanel básico.
Será um Panel que organiza seus Children com base em datas, cada Child diz a ele qual é sua data inicial
e final e a partir daí o Panel vai dizer a ele onde ele precisa estar.

Por exemplo, um painel Timeline com esses filhos:

<lcl:TimelineStackPanel x:Name="timepanel" Height="60" >


<Rectangle Fill="Red" lcl:TimelineStackPanel.StrBegin="6/11/2008"
lcl:TimelineStackPanel.StrEnd="6/12/2008" />
<Rectangle Fill="Yellow"
lcl:TimelineStackPanel.StrBegin="6/13/2008"
lcl:TimelineStackPanel.StrEnd="6/15/2008" />
<Rectangle Fill="Green" lcl:TimelineStackPanel.StrBegin="6/16/2008"
lcl:TimelineStackPanel.StrEnd="6/19/2008" />
</lcl:TimelineStackPanel>

Isso será realizado:

 Ele vai particionar seu espaço disponível em 8 dias (6/19 – 6/11).


 Vai dispor seu primeiro filho de modo a ocupar 1/8 do espaço, já que tem a duração de um dia.
 Vai dispor o segundo filho em 2/8 do espaço (dois dias de duração) e ele será posicionado no
deslocamento de largura de 2/8 a partir da posição 0,0 do Panel, já que a data Begin (Inicial) é
dois dias a partir da data mais baixa no painel.
 E assim por diante. Resumindo, a disposição dos filhos ficará assim:

89
[se você pensar bem, Viagem é baseada em tempo, aviões partem e pousam em horários específicos,
você faz reservas em hotéis para dias, etc., então o painel será útil para dispor as coisas com base no
tempo].

90
Tarefa 1: Crie uma classe TimelineStackPanel

1. Adicione uma nova classe ao nosso projeto Silverlight.Controls e chame-a de TimelineStackPanel


2. Faça a classe herdar de Panel

public class TimelineStackPanel : Panel


{
}

Tarefa 2: Implementando Measure (Medida) em nosso TimelineStackPanel

Se você se lembrar do WrapPanel, nós implementamos MeasureOverride para fazer os Filhos calcularem
seu DesiredSize. Na verdade nós NÃO temos que fazer isso para TimelineStackPanel porque ele não
respeita o DesiredSize dos Filhos; ele calcula seu tamanho com base em sua Data Inicial/Final.

91
Tarefa 3 Adicionando Propriedades de Dependência Anexadas ao
TimelineStackPanel

O TimelineStackPanel precisa saber onde colocar seus filhos; os Filhos precisarão definir sua
Propriedade Begin/End para que o TimelineStackPanel saiba onde colocá-los.
Os filhos, no entanto, são controles e UIElements existentes (como Rectangle), então você não pode
voltar a eles e adicionar uma propriedade Begin/End. Propriedades Anexadas salvam o dia; o
TimelineStackPanel vai expor uma propriedade Begin/End que os Filhos poderão definir.

O modo mais fácil de criar uma propriedade anexada no editor de Código do Visual Studio é através de
trechos de código.

Se você digitar propa, pressionar Enter e depois Tab, o gerenciador de trechos vai criar uma Propriedade
de Dependência Anexada. Isso, infelizmente, usa a sintaxe do WPF, que é um pouco diferente do
Silverlight mas ainda ajuda bastante. Tudo o que você tem que fazer é mudar o último parâmetro na
propriedade de dependência anexada para que seja nulo, em vez de uma nova UIPropertyMetadata (0).

A Propriedade de Dependência anexada para uma propriedade chamada Begin deve ficar assim:

public static DateTime GetBegin(DependencyObject obj)


{
return (DateTime)obj.GetValue(BeginProperty);
}

public static void SetBegin(DependencyObject obj, DateTime value)


{
obj.SetValue(BeginProperty, value);
}

// Usar uma DependencyProperty como armazenamento de backup para Begin.


Isso habilita a animação, o estilo, a ligação, etc...
public static readonly DependencyProperty BeginProperty =
DependencyProperty.RegisterAttached("Begin", typeof(DateTime),
typeof(TimelineStackPanel), null );

Vamos ao trabalho:

3. Adicione uma Propriedade de Dependência Anexada ao nosso TimelineStackPanel para Begin, o


tipo é DateTime
[Dica, você pode fazer isso recortando e colando o texto acima]
4. Adicione uma Propriedade de Dependência Anexada para nosso TimelineStackPanel para End, o
tipo é DateTime

92
Tarefa 4: Notificando o Panel quando um Begin ou End for alterado

Imagine que os itens são inseridos no TimelineStackPanel, o painel está fazendo a Organização de
nossos itens, se uma propriedade Begin ou End mudar ou for modificada, o Panel deve invalidar seu
layout e Reorganizar os filhos apropriadamente. Infelizmente, na declaração das propriedades na Tarefa
3, nós não vimos uma Change Notification Callback (Chamada de Notificação de Mudança).

5. Volte às Propriedades Begin/End e na declaração RegisterAttached (), onde você põe nulo no
último parâmetro, mude-a para uma instância de PropertyMetaData com um Manipulador
PropertyChangedCallback, e isso será chamado sempre que Begin ou End forem modificados. O
nome da função chamada será OnBeginEndChanged.

É assim que a EndProperty fica, com as alterações destacadas.

public static readonly DependencyProperty EndProperty =


DependencyProperty.RegisterAttached("End", typeof(DateTime),
typeof(TimelineStackPanel),
new PropertyMetadata(new
PropertyChangedCallback(OnBeginEndChanged)));

6. Defina a função OnBeginEndChanged. A assinatura é assim

protected static void OnBeginEndChanged(DependencyObject obj,


DependencyPropertyChangedEventArgs args){ }

Note que a assinatura é Estática, ela tem que ser assim porque é chamada a partir de RegisterAttached,
que também é estático.

Agora OnBeginEndChanged não está fazendo nada, e ele precisa avisar o TimelineStackPanel que este
deve invalidar o layout. Como podemos fazer isso?

7. Dentro de OnBeginEndChanged, converta o objeto Parameter para um FrameworkElement


8. Veja se a propriedade Framework Parent está definida [esse será um TimelineStackPanel]
9. Veja se o Parent (Pai) é uma instância de TimelineStackPanel
Se o pai for um TimelineStackPanel, vamos invalidar seu Layout.

Este é o código para 7-9 acima, comentados novamente.

//protected static void OnBeginEndChanged(DependencyObject obj,


DependencyPropertyChangedEventArgs args)
//{
// FrameworkElement fe = obj as FrameworkElement;
// if (fe != null)

93
// {
// TimelineStackPanel parent = fe.Parent as TimelineStackPanel;
// if (parent != null)
// {
// parent.InvalidateArrange();
// }
// else
// fe.InvalidateArrange();
// }

//}

94
Tarefa 5: Adicionando Propriedades de Dependência Low e High ao nosso
Painel.

Este passo poderia ser opcional, mas é necessário para a aplicação do Voyager e faz sentido até para o
Panel. Vamos dar a ele uma propriedade Low (Baixa) e High (Alta) para seu layout. Imagine por exemplo
que o TimelineStackPanel está plotando um vôo. O vôo pode ser de 9:45 a 12:15 mas nós queremos
plotá-lo como um dia inteiro, ou ao menos como 9 da manhã a 1 da tarde. É isso que a Low e a High
farão; elas definem o intervalo.

Low e High não são propriedades de dependência anexadas. Elas são propriedades de dependência
regulares, pois são definidas na instância do Panel. É como definir Width/Height ou qualquer outra
propriedade anexada.

Similar ao trecho propa, há um trecho propdp que podemos usar para criar nossas propriedades no
Editor de Código.

Digite propdp, pressione Enter e depois Tab no editor do VS, dentro de nossa classe TimelineStackPanel.
Você vai precisar substituir novamente o último parâmetro já que são trechos do WPF.

Para uma propriedade DateTime chamada Low, possuída pelo TimelineStackPanel, ficará assim:

public DateTime Low


{
get { return (DateTime)GetValue(LowProperty); }
set { SetValue(LowProperty, value); }
}

// Using a DependencyProperty as the backing store for Low. This


enables animation, styling, binding, etc...
public static readonly DependencyProperty LowProperty =
DependencyProperty.Register("Low", typeof(DateTime),
typeof(TimelineStackPanel), null );

10. Adicione uma DependencyProperty Low ao nosso TimelineStackPanel

Dica:

public DateTime Low


{
get { return (DateTime)GetValue(LowProperty); }
set { SetValue(LowProperty, value); }
}

public static readonly DependencyProperty LowProperty =


DependencyProperty.Register("Low", typeof(DateTime),
typeof(TimelineStackPanel), null );

95
11. Adicione uma DependencyProperty High ao nosso TimelineStackPanel (igual ao passo acima mas
com Low em vez de High).

96
Tarefa 6: Notificando o Panel que as propriedades Low/High mudaram

Similar a quando uma propriedade do Filho mudou, o TimelineStackPanel precisa invalidar seu layout
quando suas propriedades Low e High mudaram.

12. Adicione um PropertyChangedCallback à Propriedade Low. Chame-o de OnRangeChanged

[O trecho abaixo mostra a sintaxe; em destaque está o que mudou na declaração LowProperty].

public static readonly DependencyProperty LowProperty =


DependencyProperty.Register("Low", typeof(DateTime),
typeof(TimelineStackPanel),
new PropertyMetadata ( new PropertyChangedCallback
(OnRangeChanged)));

13. Repita o passo acima e adicione OnRangeChanged como notificação, mas desta vez para a
Propriedade High.

[Igual ao passo acima]


14. Crie a função OnRangeChanged:

protected static void OnRangeChanged(DependencyObject dep,


DependencyPropertyChangedEventArgs args)

Embora a assinatura para PropertyChangedCallback seja a mesma das propriedades de dependência


anexadas anteriores, desta vez a instância passada para os parâmetros é um pouco diferente porque
essa não é uma Propriedade Anexada. Neste caso dep, o parâmetro DependencyObject, é uma
instância da classe TimelineStackPanel. Você não tem que detectar o Pai.

15. Converta o parâmetro dep passado para um TimelineStackPanel e

Chame InvalidateArrange no painel.

protected static void OnRangeChanged(DependencyObject dep,


DependencyPropertyChangedEventArgs args)
{

TimelineStackPanel panel = dep as TimelineStackPanel;


panel.InvalidateArrange();
}

97
Tarefa 7: Implementando o Arrange (Organizar)

Agora que o TimelineStackPanel tem um Low e um High para o intervalo, é hora de Organizar nossos
Filhos com base em sua linha do tempo. Aqui está tudo o que temos que fazer; a maior parte é lógica de
negócios, então o código está incluído.

16. Implemente ArrangeOverride


17. Dentro de ArrangeOverride, cheque primeiro para ver se as funções Low e High foram definidas.
Se não, itere em todos os filhos para ter um intervalo das datas.

Note que eu fiz algo engraçado e converti as datas para o dobro. Fica mais fácil comparar e
calcular larguras...

double min = 0, max = 0;


if (ReadLocalValue(LowProperty) != DependencyProperty.UnsetValue)
{
min = Low.ToOADate();
}
if (ReadLocalValue(HighProperty) != DependencyProperty.UnsetValue)
{
max = High.ToOADate();
}

if (min == 0 || max == 0)
{
GetMinMax(ref min, ref max);
}

GetMinMax é simplesmente uma iteração de todos os filhos para capturar a Min


StartDate e a Max EndDate.

protected bool GetMinMax(ref double min, ref double max)


{
min = double.MaxValue;
max = double.MinValue;

DateTime mindate = DateTime.MaxValue;


DateTime maxdate = DateTime.MinValue;
foreach (UIElement e in Children)
{
double begin = GetBegin(e).ToOADate();
double end = GetEnd(e).ToOADate();

min = Math.Min(begin, min);


max = Math.Max(end, max);
}
return true;
}

98
18. Agora que temos um intervalo de datas, pegue o espaço disponível e divida-o pelo
intervalo para calcular um multiplicador para onde colocar os itens.

double range = (max - min);


double multiplier = finalSize.Width / (max - min);

19. Finalmente, organize os filhos de modo similar ao que fizemos antes no WrapPanel.

foreach (UIElement e in Children)


{
try
{
double itembegin = GetBegin(e).ToOADate();
double itemend = GetEnd(e).ToOADate();
double left = Math.Max(0, multiplier * (itembegin - min));
double height = Math.Max(0, multiplier * (itemend - itembegin));
e.Arrange(new Rect(left, 0, height, finalSize.Height));
}
finally { }

}
return finalSize;

99
Tarefa 8: Resolvendo um problema de Conversor

[Esse passo é opcional]. Você implementou cada coisa necessária para a aplicação do Voyager. Mas
para testar de forma fácil o laboratório você deve querer fazê-lo em XAML, certo??

Na beta2, há um problema em aberto: nós não podemos anexar um Conversor de DateTime to String a
uma propriedade anexada. Sem um conversor, não seria fácil definir a propriedade Begin/End a partir
do XAML. Como solução temporária, você pode anexar a propriedade de Dependências Anexadas (como
na Tarefa 3), mas desta vez do tipo string. O TimelineStackPanel vai conectar essas propriedades às
propriedades Begin/End originais.

A tarefa é apenas uma repetição de tudo que fizemos antes (Tarefa 3).

20. Adicione Propriedades de Dependência Anexadas ao TimelineStackPanel de tipo string


(seqüência de caracteres), chamando-as de StrBegin e StrEnd.
21. Manipule o PropertyChangedCallback para essas propriedades.
22. Dentro do callback, leia o valor de StrBegin/StrEnd respectivamente, converta-o a um DateTime
e defina a propriedade Begin ou End correspondente.

Você pode recortar e colar o código do link da resposta abaixo.

100
Tarefa 9: Testando nosso TimelineStackPanel a partir do código

O Voyager é controlado por dados, então vamos usar o TimelineStackPanel a partir do código. Portanto
você podia ter pulado o passo 8. Para testar a partir do código vamos ter que:

23. Voltar ao Page.xaml, onde testamos nosso WrapPanel, e substituir a declaração WrapPanel por
uma declaração TimelineStackPanel. [Certifique-se de dar a ela um x:Name no XAML para que
possamos acessá-la a partir do código].

<lcl:TimelineStackPanel x:Name="timepanel" Height="60" >

24. Entre no Page.xaml.cs e no construtor adicione um manipulador para o evento Loaded


25. Dentro do evento Loaded, execute algum código fictício que insira itens em nosso StackPanel.
Lembre-se de fazer o seguinte:
a. Defina uma Data Inicial (Begin)
b. Defina uma Data Final (End)

O End deve ser maior que o Begin porque não há verificação de erros em nosso Panel.

void Page_Loaded(object sender, RoutedEventArgs e)


{

timepanel.Low = DateTime.Today;
timepanel.High = DateTime.Today.AddDays(30);
for (int x = 0; x < 5; x++)
{
Rectangle rect = new Rectangle();
rect.Fill = new SolidColorBrush(Color.FromArgb((byte)255,
(byte)(x * 70), (byte)(x * 70), (byte)(x * 70)));
TimelineStackPanel.SetBegin(rect, DateTime.Today.AddDays((4 *
x)));
TimelineStackPanel.SetEnd(rect, DateTime.Today.AddDays((4 * x)
+ 1));
timepanel.Children.Add(rect);
}
}

101
Tarefa 10: Testando nosso TimelineStackPanel a partir do XAML

Se você implementou o passo 8, pode testar nosso painel no XAML. Basta entrar na declaração
TimelineStackPanel no XAML e inserir qualquer tipo de UIElement no painel. Lembre-se de colocar datas
StreBegin e StrEnd no item. Essas são propriedades anexadas, então têm esta sintaxe atribuída (para
uma declaração de namespace de lcl)

<Rectangle Fill="Red" lcl:TimelineStackPanel.StrBegin="6/11/2008"


lcl:TimelineStackPanel.StrEnd="6/12/2008" />

Tarefa 11: Exercícios para o usuário

Divirta-se um pouco com o painel. Faça combinações e correspondências, inserindo por exemplo itens a
partir do XAML e do Código.
Adicione um botão ao seu código ou algum controle de datas que altere as datas no vôo para poder ver
o painel de pilha se reorganizar conforme o necessário.

102
Apêndices

Seção 1: Respostas das Tarefas

Tarefa 1: Crie uma classe WrapPanel

//É assim que fica o WrapPanel inteiro. Em destaque está o código que você teria adicionado.

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace MargiesTravel.Controls
{
public class WrapPanel : Panel
{

}
}

Tarefa 2: Medindo nossos filhos

//Dentro da classe WrapPanel, adicione o seguinte método

protected override Size MeasureOverride(Size availableSize)


{
foreach (UIElement child in Children)
{
child.Measure(availableSize);
}
}

Tarefa 3: Passo 1 – Substituindo o Arrange

protected override Size ArrangeOverride(Size finalSize)


{

103
Tarefa 3: Passo 2 -- Implementando o Arrange

protected override Size ArrangeOverride(Size finalSize)


{
double currentLeft = 0;
double currentTop = 0;
double currentRowHeight = 0;

foreach (UIElement child in Children)


{
// Check if it does not fit into current row
if ((currentLeft + child.DesiredSize.Width) > finalSize.Width)
{
//Start a new row by resetting our stuff
currentLeft = 0;
currentTop += currentRowHeight;
currentRowHeight = 0;
}
// this does the positioning of the child
child.Arrange(new Rect(currentLeft, currentTop,
child.DesiredSize.Width, child.DesiredSize.Height));
// move to next available space..
currentLeft += child.DesiredSize.Width;
currentRowHeight = Math.Max(currentRowHeight,
child.DesiredSize.Height);
}
return finalSize;

Tarefa 4: Exercitando nosso WrapPanel.

Escolha entre estas soluções:

<UserControl x:Class="MargiesTravel.Controls.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MargiesTravel.Controls"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<lcl:WrapPanel>
<Rectangle Width="200" Height="50" Fill="Red" > </Rectangle>
<Rectangle Width="100" Height="20" Fill="Yellow" ></Rectangle>
<Rectangle Width="70" Height="70" Fill="Green" ></Rectangle>
<Rectangle Width="100" Height="50" Fill="Red" ></Rectangle>
<Rectangle Width="100" Height="100" Fill="Yellow" ></Rectangle>
<Rectangle Width="400" Height="70" Fill="Green" ></Rectangle>
<Rectangle Width="500" Height="70" Fill="Red" ></Rectangle>
</lcl:WrapPanel>
</Grid>
</UserControl>
OU
<UserControl x:Class="MargiesTravel.Controls.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

104
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MargiesTravel.Controls"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<lcl:WrapPanel>
<Button Content="button" Width="200" Height="50" />
<TextBlock Text="TextBlock" Width="150" Height="30"/>
<Slider Width="300" Value="6" Height="50"/>
<TextBox Width="200" Height="30" />
<TextBlock Text="Autosize text" />
<CheckBox Content="checkbox" Width="140" />
</lcl:WrapPanel>
</Grid>
</UserControl>

Exercício 2: Respostas das Tarefas

Tarefa 1: Criando um TimelineStackPanel

public class TimelineStackPanel : Panel


{
}

Tarefa 3: Adicionando propriedades de dependência anexadas para Begin e


End

// Esta resposta inclui as propriedades e a declaração de classe propriamente


dita.

public class TimelineStackPanel : Panel


{

public static DateTime GetBegin(DependencyObject obj)


{
return (DateTime)obj.GetValue(BeginProperty);
}

public static void SetBegin(DependencyObject obj, DateTime value)


{
obj.SetValue(BeginProperty, value);
}

// Usar uma DependencyProperty como o armazenamento de backup para


Begin. Isso habilita a animação, o estilo, a ligação, etc...
public static readonly DependencyProperty BeginProperty =
DependencyProperty.RegisterAttached("Begin", typeof(DateTime),
typeof(TimelineStackPanel), null );

public static DateTime GetEnd(DependencyObject obj)


{
return (DateTime)obj.GetValue(EndProperty);

105
}

public static void SetEnd(DependencyObject obj, DateTime value)


{
obj.SetValue(EndProperty, value);
}

// Usar uma DependencyProperty como o armazenamento de backup para End.


Isso habilita a animação, o estilo, a ligação, etc...
public static readonly DependencyProperty EndProperty =
DependencyProperty.RegisterAttached("End", typeof(DateTime),
typeof(TimelineStackPanel), null)
}

Tarefa 4: Notificando o Panel quando um Begin ou End for alterado

//Esta tarefa mostra em destaque as alterações que você teve que fazer para conectar a notificação de
Alteração.
//Abaixo disso está o código para OnBeginEndChanged. É ‘código novo’.

public static readonly DependencyProperty EndProperty =


DependencyProperty.RegisterAttached("End", typeof(DateTime),
typeof(TimelineStackPanel),
new PropertyMetadata(new
PropertyChangedCallback(OnBeginEndChanged)));

public static readonly DependencyProperty BeginProperty =


DependencyProperty.RegisterAttached("Begin", typeof(DateTime),
typeof(TimelineStackPanel),
new PropertyMetadata(new
PropertyChangedCallback(OnBeginEndChanged)));

protected static void OnBeginEndChanged(DependencyObject obj,


DependencyPropertyChangedEventArgs args)
{
FrameworkElement fe = obj as FrameworkElement;
if (fe != null)
{
TimelineStackPanel parent = fe.Parent as TimelineStackPanel;
if (parent != null)
{
parent.InvalidateArrange();
}
else
fe.InvalidateArrange();
}

106
Tarefa 5: Adicionando Propriedades de Dependência Low e High ao nosso
Painel.

public DateTime Low


{
get { return (DateTime)GetValue(LowProperty); }
set { SetValue(LowProperty, value); }
}

// Usar uma DependencyProperty como o armazenamento de backup para Low.


Isso habilita a animação, o estilo, a ligação, etc...
public static readonly DependencyProperty LowProperty =
DependencyProperty.Register("Low", typeof(DateTime),
typeof(TimelineStackPanel), null );

public DateTime High


{
get { return (DateTime)GetValue(HighProperty); }
set { SetValue(HighProperty, value); }
}

// Usar uma DependencyProperty como o armazenamento de backup para


High. Isso habilita a animação, o estilo, a ligação, etc...
public static readonly DependencyProperty HighProperty =
DependencyProperty.Register("High", typeof(DateTime),
typeof(TimelineStackPanel), null);

Tarefa 6: Notificando o Panel que as propriedades Low/High mudaram

public static readonly DependencyProperty LowProperty =


DependencyProperty.Register("Low", typeof(DateTime),
typeof(TimelineStackPanel),
new PropertyMetadata ( new PropertyChangedCallback
(OnRangeChanged)));

public static readonly DependencyProperty HighProperty =


DependencyProperty.Register("High", typeof(DateTime),
typeof(TimelineStackPanel),
new PropertyMetadata(new PropertyChangedCallback(OnRangeChanged)));

protected static void OnRangeChanged(DependencyObject dep,


DependencyPropertyChangedEventArgs args)
{

TimelineStackPanel panel = dep as TimelineStackPanel;


panel.InvalidateArrange();
}

107
Tarefa 7: Implementando o Arrange

protected bool GetMinMax(ref double min, ref double max)


{
min = double.MaxValue;
max = double.MinValue;

DateTime mindate = DateTime.MaxValue;


DateTime maxdate = DateTime.MinValue ;
foreach (UIElement e in Children)
{
double begin = GetBegin(e).ToOADate();
double end = GetEnd(e).ToOADate();

min = Math.Min(begin, min);


max = Math.Max(end, max);
}
return true;
}
protected override Size ArrangeOverride(Size finalSize)
{

double min = 0, max = 0 ;


if (ReadLocalValue(LowProperty) != DependencyProperty.UnsetValue)
{
min = Low.ToOADate();
}
if (ReadLocalValue(HighProperty) != DependencyProperty.UnsetValue)
{
max = High.ToOADate();
}

if ( min == 0 || max == 0 )
{
GetMinMax ( ref min, ref max );
}

double range = ( max - min);


double multiplier = finalSize.Width /( max - min );

foreach ( UIElement e in Children )


{
double itembegin = GetBegin (e).ToOADate ();
double itemend = GetEnd(e).ToOADate();

double left = Math.Max(0, multiplier * (itembegin - min ));


double height = Math.Max(0, multiplier * ( itemend -
itembegin));
e.Arrange ( new Rect ( left , 0, height , finalSize.Height))
;

}
return finalSize;
}

108
Tarefa 8: Resolvendo um problema de Conversor

public static string GetStrBegin(DependencyObject obj)


{
return (string)obj.GetValue(StrBeginProperty);
}

public static void SetStrBegin(DependencyObject obj, string value)


{
obj.SetValue(StrBeginProperty, value);
}

public static string GetStrEnd(DependencyObject obj)


{
return (string)obj.GetValue(StrEndProperty);
}

public static void SetStrEnd(DependencyObject obj, string value)


{
obj.SetValue(StrEndProperty, value);
}

// Usar uma DepStrEndencyProperty como o armazenamento de backup para


StrEnd. Isso habilita a animação, o estilo, a ligação, etc...
public static readonly DependencyProperty StrEndProperty =
DependencyProperty.RegisterAttached("StrEnd", typeof(string),
typeof(TimelineStackPanel),
new PropertyMetadata(new
PropertyChangedCallback(OnStrBeginStrEndChanged)));

public static readonly DependencyProperty StrBeginProperty =


DependencyProperty.RegisterAttached("StrBegin", typeof(string),
typeof(TimelineStackPanel),
new PropertyMetadata(new
PropertyChangedCallback(OnStrBeginStrEndChanged)));

protected static void OnStrBeginStrEndChanged(DependencyObject obj,


DependencyPropertyChangedEventArgs args)
{
UIElement e = obj as UIElement;

DateTime dte = DateTime.Parse((string)args.NewValue);


if (args.Property == TimelineStackPanel.StrBeginProperty)
{
TimelineStackPanel.SetBegin(obj, dte);
}
else
{
TimelineStackPanel.SetEnd(obj, dte);
}
}

109
Tarefa 9: Testando nosso TimelineStackPanel a partir do código

void Page_Loaded(object sender, RoutedEventArgs e)


{

timepanel.Low = DateTime.Today;
timepanel.High = DateTime.Today.AddDays(30);
for (int x = 0; x < 5; x++)
{
Rectangle rect = new Rectangle();
rect.Fill = new SolidColorBrush(Color.FromArgb((byte)255,
(byte)(x * 70), (byte)(x * 70), (byte)(x * 70)));
TimelineStackPanel.SetBegin(rect, DateTime.Today.AddDays((4 *
x)));
TimelineStackPanel.SetEnd(rect, DateTime.Today.AddDays((4 * x)
+ 1));
timepanel.Children.Add(rect);
}
}

Tarefa 10: Testando nosso TimelineStackPanel a partir do XAML

<UserControl x:Class="MargiesTravel.Controls.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MargiesTravel.Controls"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<lcl:TimelineStackPanel x:Name="timepanel" >
<Rectangle Fill="Red" lcl:TimelineStackPanel.StrBegin="6/11/2008"
lcl:TimelineStackPanel.StrEnd="6/12/2008" />
<Rectangle Fill="Yellow"
lcl:TimelineStackPanel.StrBegin="6/13/2008"
lcl:TimelineStackPanel.StrEnd="6/15/2008" />
<Rectangle Fill="Green" lcl:TimelineStackPanel.StrBegin="6/17/2008"
lcl:TimelineStackPanel.StrEnd="6/18/2008" />
</lcl:TimelineStackPanel>
</Grid>
</UserControl>

Tarefa 11: Exercícios para o usuário


Veja o SilverlightUserControl1 na solução.

110
F- Usando Dados nas Aplicações do
Silverlight
Ligação de dados a objetos, coleções e o DataGrid.

Introdução

A ligação de dados é o processo de estabelecer uma conexão, ou ligação, entre a interface de usuário
(UI) e um objeto CLR para permitir que os dados fluam entre os dois: Se uma ligação Bidirecional é
estabelecida e os dados mudam, os elementos da interface de usuário ligados aos dados podem refletir
as mudanças automaticamente. Similarmente, as mudanças feitas através da interface de usuário são
refletidas no objeto de dados.

Há dois objetos que participam de uma ligação: a Fonte e o Destino:


 A fonte (Source) pode ser qualquer objeto CLR ou uma coleção de objetos
 O destino (Target) é uma classe que herda de FrameworkElement

O Silverlight oferece diferentes Modos de ligação


 As ligações Únicas atualizam o destino com os dados da fonte quando a ligação é criada. Não
atualizam mais do que isso.
 As ligações Unidirecionais atualizam o destino com os dados da fonte quando a ligação é criada
e a qualquer mudança dos dados.
 As ligações Bidirecionais atualizam tanto o destino quanto a fonte quando o outro muda.

A notificação das mudanças da Fonte ao Destino é feita via interface INotifyPropertyChanged.


Para coleções, a notificação de mudanças é implementada usando INotifyCollectionChanged.

A Validação de Dados tem suporte em Ligações Bidirecionais. Ela é ‘roteada’ através do


BindingValidationError.

Objetivos do Laboratório:

Na Tarefa 1, você vai aprender os fundamentos da ligação de dados.

 Implementando InotifyPropertyChanged para Notificar Mudanças

111
 Ligando uma Coleção a uma ListBox e criando Datatemplates
 Criando uma Exibição Mestre/Detalhes

No Exercício 2 (laboratório separado), você vai implementar a ligação de dados necessária para a
aplicação do Voyager.

 Criando Ligações via XAML


 Herdando DataContext
 Criando e usando conversores de Tipo

112
Tarefa 1: Criando uma Ligação Simples usando XAML e
DataContext

Abra o DataLab.sln na pasta Data_Starter.


O Page.xaml tem uma Interface de Usuário pré-definida que representa um Pacote de remessa.
Há também um objeto de negócios chamado TestData. Se você fosse conectar esses dois sem a ligação
de dados, teria que escrever código para carregar os dados para a interface de usuário e depois ler de
novo a partir da interface de usuário para salvá-los em seu objeto de negócios TestData. Em vez de
todo esse trabalho, você pode deixar a ligação de dados fazer tudo por você.

1.A partir do code-behind, no construtor Page(), após a chamada InitalizeComponent, defina o valor
de packageGrid.DataContext para TestData.package.

2.Agora edite o XAML para que ele ligue a Interface de Usuário às propriedades de
TestData.Package.

Elemento x:Name Caminho da Propriedade


lblTrackingNo TrackingNo
txtName Address.Name
txtCity Address.City
txtCountry Address.Country
txtZip Address.ZipCode
txtShipped ShippedDate

3. Execute sua aplicação e note que a interface de usuário exibe agora todos os dados de
TestData.Package.

Nós definimos o DataContext para o packageGrid e todos os elementos da interface de usuário


abaixo dele herdaram automaticamente o DataContext e por isso as ligações funcionaram.

Exercício para o usuário:


Tente entrar de novo em qualquer um de nossos blocos de texto e ‘substitua’ a propriedade
DataContext por um Valor local como este:

DataContext="{Binding Address}" Text="{Binding Street}"

Note que o DataContext pode ser substituído localmente, e também que um DataContext local pode ser ligado ao
DataContext herdado de um elemento.

113
Tarefa 2: Ligando a uma Coleção (usando um
DataTemplate)

Ligar a um item foi ótimo para testar, mas é necessário neste caso ligar a uma coleção (a propriedade
TestData.Packages).

Há ao menos duas opções:

 Pegue o XAML na Página 1 e transforme-o em um UserControl; depois ligue os dados desse User
Control, dessa forma você acaba tendo um controle reutilizável para pôr em nossas coleções.
 Crie um DataTemplate, que também dá a você um modelo reutilizável e não incorre na
sobrecarga de criar o UserControl (o que não é necessário neste caso).

Para implementar a última opção:

4.Crie um DataTemplate na seção UserControl.resources em Page.xaml.


Lembre-se que já que é um recurso, você precisa dar a ele uma chave.
Além disso, já que os UIElements no modelo de dados estão se referindo a recursos e os
staticResources devem ser definidos antes de serem usados, lembre-se de declarar o
DataTemplate no final da coleção de Recursos, perto da marca de fechamento de
</UserControl.Resources>

<DataTemplate x:Key=” PackageTemplate”>


</DataTemplate>
<UserControl.Resources>

5.Agora recorte e cole tudo do packageGrid – incluindo o próprio elemento <Grid>, no


DataTemplate...
6. Adicione uma ListBox a LayoutRoot em page.xaml. Use a propriedade ItemTemplate para dizer a
ela que use o DataTemplate que você acabou de criar.

<ListBox x:Name="packagesListbox" Width="400" ItemTemplate="{StaticResource


PackageTemplate}" />

7.Atualize o construtor em Page.xaml.cs para que o ItemsSource da ListBox seja a coleção


TestData.Packages.

this.packagesListbox.ItemsSource = TestData.Packages;

114
8.Execute a aplicação para ver se a Listbox funciona.

Exercício para o usuário:


Note que a listbox funciona, mas não é uma navegação muito boa. Detalhes demais para o usuário.
Como exercício, substitua o DataTemplate por algo mais adequado, como um modelo menor com
apenas Name?
[Se você fizer o exercício, lembre-se de copiar o modelo e criar um novo, não modifique o atual pois ele
será usado mais tarde].

115
Tarefa 3: Substituindo a Listbox por um DataGrid

Uma alternativa à substituição do modelo da caixa de listagem é usar um controle diferente, como o
DataGrid. A grade, com sua habilidade de exibir colunas, seria ótima para vermos todas as colunas de
uma vez em um formato tabular.

9. No projeto DataLab, adicione uma Referência à montagem System.Windows.Controls.Data.


[Isso precisa ser feito porque o DataGrid não está nas montagens básicas do Silverlight].

<UserControl x:Class="DataLab.Task3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300"
xmlns:data="clr-
namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data">

10. Agora adicione uma instância do DataGrid para substituir a Listbox (em LayoutRoot)

<data:DataGrid AutoGenerateColumns="True" x:Name="packagesListBox">

11. Selecione Auto-Gerar Colunas para não termos que fazer todo o trabalho.

12. Execute a aplicação e veja o DataGrid em ação.

[Não era bem o que eu esperava... teria ficado melhor se Package fosse um objeto plano, você
não acha?? Bem, é tarde demais para mudar o modelo do objeto, então vamos corrigir as
Colunas no DataGrid 

116
Tarefa 4: Personalizando as colunas no DataGrid

É claro que o DataGrid também usa o DataTemplate para personalizar suas colunas.
Infelizmente não podemos reutilizar o Modelo de Dados que usamos para a ListBox, pois aquele é para
todos os dados e aqui no DataGrid queremos criar um por coluna.

A sintaxe para definir as colunas do DataTemplate fica assim:

<data:DataGrid.Columns>
<data:DataGridTemplateColumn Header=”Tracking No.” >
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding TrackingNo}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>

1. Substitua quantas colunas do DataGrid quiser para navegar em seus dados.


 Sinta-se à vontade para brincar. Note que o Header em DataGridTemplateColumn é um
ContentControl, então ele pode ter mais que texto; pode ter UIElements.
 O mesmo vale para os DataTemplates para cada coluna, você pode fazer o que quiser.
Por exemplo, eu faço do DeliveredDate um DateTimePicker.

117
Tarefa 5 – Exibição Mestre/Detalhes usando Ligações a
partir do código

Agora que o DataGrid está mostrando os dados em linhas, você pode trazer de volta o modelo Package
único e usá-lo como uma Exibição de Detalhes para um pacote – aquele selecionado na grade.
Vamos primeiro adicionar a interface de usuário para os “detalhes”.

1.Atualize o LayoutRoot (o Grid) para ter duas linhas de altura igual

<Grid x:Name="LayoutRoot" Background="White">


<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

2. Adicione uma propriedade anexada à grade de dados para estar no Grid.Row=”0”.

<data:DataGrid x:Name="packagesListBox" AutoGenerateColumns="False" Width="600"


Grid.Row="0">

3. Adicione um ContentControl com Grid.Row=“1” que usa o DataTemplate criado anteriormente.

<ContentControl x:Name=”details” ContentTemplate="{StaticResource


PackageTemplate}" Grid.Row="1" />

4. Nesse ponto o ContentControl está pronto mas precisa de dados.


Os dados precisam ser o SelectedItem do DataGrid. Infelizmente, no Silverlight 2, você não pode
ligar dados de um UIElement a outro, então precisamos de um objeto fictício no meio. Você
pode ligar ambos os elementos da interface de usuário a esse objeto fictício - vamos chamá-lo
de ViewModel - e dessa forma uma atualização da interface de usuário ao ViewModel será
carregada à outra interface de usuário também.

5. Adicione uma classe simples que expõe uma propriedade do tipo objeto, o nome da propriedade
é SelectedItem. A classe deve implementar INotifyPropertyChanged para notificação de
mudanças.

[Você pode ver a resposta para ter uma dica, mas tente fazer, pois você vai implementar
INotifyPropertyChanged muito quando fizer dados com o Silverlight]

Agora que o objeto de negócios ViewModel está pronto, precisamos criar uma Ligação. Neste caso
vamos fazer isso a partir do código, já que todas as nossas outras ligações foram feitas a partir do

118
XAML.

6. Adicione um manipulador de eventos Loaded ao construtor de sua Página, logo depois do lugar
em que estava definindo o itemsSource do DataGrid.

this.Loaded += new RoutedEventHandler(Task5_Loaded);

7. No manipulador de eventos Loaded, instancie uma instância do ViewModel. Isso pode ser feito
em escopo local.

ViewModel v = new ViewModel();

8. Agora crie uma Ligação a partir do código.

A fonte será o objeto ViewModel v criado no passo anterior. O destino será o DataGrid (ainda
chamado de packagesListBox) e a ligação será bidirecional. O código comentado abaixo deve dar
uma dica a você.

//Binding b = new Binding("SelectedItem");


//b.Source = v;
//b.Mode = BindingMode.TwoWay;
//packagesListBox.SetBinding(DataGrid.SelectedItemProperty, b);

Isso liga o selectedItem do DataGrid ao nosso objeto fictício, mas nós precisamos desse mesmo objeto
para guiar nosso ContentPresenter de detalhes.

9. Crie uma ligação com v como a Source, e nosso ContentControl de “detalhes” como o
SelectedItem como a fonte, mas desta vez para a Propriedade Content de detalhes.

10. Agora você pode executar a aplicação e conforme mudar a seleção no DataGrid, seu
Apresentador de conteúdos deve ser atualizado também.

119
Tarefa 6: Exercício para o usuário

Note que a seleção do DataGrid funciona bem para atualizar detalhes, mas se você fizer alterações nos
campos de detalhes (como mudar o endereço), elas não serão propagadas para a grade. Por quê??
Obviamente porque o Package não implementou ainda o INotifyPropertyChanged. Vá em frente e
implemente-o para poder ver a atualização das mudanças.

120
Apêndice

Seção 1: Respostas das Tarefas

Tarefa 1: Criando uma Ligação Simples usando XAML e DataContext


// No construtor de classes

this.packageGrid.DataContext = TestData.Package;

<!—Updated XAML -->

<TextBlock Style="{StaticResource leftFE}" Grid.Column="1"


Grid.ColumnSpan="3" Grid.Row="0" Text="{Binding TrackingNo}" />

<TextBox Style="{StaticResource leftFE}" Grid.Column="1" Text="{Binding


Address.Name}" TextWrapping="Wrap" Grid.ColumnSpan="3" x:Name="txtName"
Grid.Row="1"/>
<TextBox Style="{StaticResource leftFE}" Grid.Row="2" Grid.Column="1"
Text="{Binding Address.Street}" Grid.ColumnSpan="3" x:Name="txtAddress"/>
<TextBox Style="{StaticResource leftFE}" Grid.Row="3" Grid.Column="1"
Text="{Binding Address.City}" Grid.ColumnSpan="3" x:Name="txtCity"/>

<TextBox Style="{StaticResource leftFE}" Grid.Row="4" Grid.Column="1"


Text="{Binding Address.ZipCode}" x:Name="txtZip"/>
<TextBox Style="{StaticResource leftFE}" Grid.Row="4" Grid.Column="3"
Text="{Binding Address.Country}" x:Name="txtCountry"/>
<TextBox Style="{StaticResource leftFE}" Text="{Binding ShippedDate}"
x:Name="txtShipped" Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="3"/>

Tarefa 2: Ligando a uma Coleção (usando um DataTemplate)

O código para ligar a caixa de listagem é:

this.packagesListbox.ItemsSource = TestData.Packages;

O XAML fica como o de abaixo, embora sua x:Class possa ser chamada de Page em vez de Task2.

<UserControl x:Class="DataLab.Task2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300" xmlns:DataLab="clr-namespace:DataLab"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<UserControl.Resources>

121
<Style TargetType="TextBlock" x:Key="rightLabel">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="Margin" Value="0,2,5,2" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Foreground" Value="#FF264EA7" />

</Style>

<Style TargetType="FrameworkElement" x:Key="leftFE">


<Setter Property="Margin" Value="5,2,0,2" />
</Style>

<DataTemplate x:Key="PackageTemplate">
<Grid Height="256" Width="336" x:Name="packageGrid" >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.286*"/>
<ColumnDefinition Width="0.238*"/>
<ColumnDefinition Width="0.254*"/>
<ColumnDefinition Width="0.222*"/>
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource rightLabel}" Grid.Column="0"
Grid.ColumnSpan="1" Grid.Row="0" Text="Tracking No." Width="Auto" Margin="0,2,-
5,2" />
<TextBlock Style="{StaticResource rightLabel}" Grid.Column="0"
Grid.ColumnSpan="1" Grid.Row="2" Text="Address" x:Name="lblAddress"/>
<TextBlock Style="{StaticResource rightLabel}"
Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="1" Text="Name" x:Name="lblName"/>

<TextBlock Style="{StaticResource rightLabel}"


Text="City" Grid.Row="3" x:Name="lblCity"/>
<TextBlock Style="{StaticResource rightLabel}"
Grid.Row="4" Text="ZipCode" x:Name="lblZip"/>
<TextBlock Style="{StaticResource rightLabel}"
Text="Country" Grid.Row="4" d:LayoutOverrides="Width" Grid.Column="2"
x:Name="lblCountry"/>
<TextBlock Style="{StaticResource rightLabel}"
Text="Shipped" x:Name="lblShipped" Grid.Row="5"/>

<TextBlock Style="{StaticResource leftFE}" Grid.Column="1"


Grid.ColumnSpan="3" Grid.Row="0" Text="{Binding TrackingNo}" />
<TextBox Style="{StaticResource leftFE}" Grid.Column="1" Text="{Binding
Address.Name}" TextWrapping="Wrap" Grid.ColumnSpan="3" x:Name="txtName"
Grid.Row="1"/>
<TextBox Style="{StaticResource leftFE}" Grid.Row="2"
Grid.Column="1" Text="{Binding Address.Street}" Grid.ColumnSpan="3"
x:Name="txtAddress"/>
<TextBox Style="{StaticResource leftFE}" Grid.Row="3"
Grid.Column="1" Text="{Binding Address.City}" Grid.ColumnSpan="3"
x:Name="txtCity"/>

122
<TextBox Style="{StaticResource leftFE}" Grid.Row="4" Grid.Column="1"
Text="{Binding Address.ZipCode}" x:Name="txtZip"/>
<TextBox Style="{StaticResource leftFE}" Grid.Row="4" Grid.Column="3"
Text="{Binding Address.Country}" x:Name="txtCountry"/>

<TextBox Style="{StaticResource leftFE}" Text="{Binding


ShippedDate}" x:Name="txtShipped" Grid.Row="5" Grid.Column="1"
Grid.ColumnSpan="3"/>

</Grid>

</DataTemplate>

</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" >

<ListBox x:Name="packagesListbox" Width="400" ItemTemplate="{StaticResource


PackageTemplate}" />

</Grid>
</UserControl>

123
Tarefa 3: Substituindo a Listbox por um DataGrid
<!— É assim que fica o LayoutRoot no Page.xaml -->

<Grid x:Name="LayoutRoot" Background="White" >


<data:DataGrid AutoGenerateColumns="True" x:Name=" packagesListbox">
</data:DataGrid>
</Grid>

124
Tarefa 4: Personalizando as colunas no DataGrid
<!— é assim que fica o XAML para o Grid do LayoutRoot inteiro -->

<Grid x:Name="LayoutRoot" Background="White">

<data:DataGrid x:Name="packagesListBox" AutoGenerateColumns="False"


Width="600">
<data:DataGrid.Columns>
<data:DataGridTemplateColumn >
<data:DataGridTemplateColumn.Header>
<StackPanel>
<TextBlock Foreground="Red" Text="Tracking No" />
</StackPanel>
</data:DataGridTemplateColumn.Header>
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding TrackingNo}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>

<data:DataGridTemplateColumn Header="Name">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Address.Name}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>

<data:DataGridTemplateColumn Header="Address">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Address.Street}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>

<data:DataGridTemplateColumn Header="City">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Address.City}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>

<data:DataGridTemplateColumn Header="Delivered">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<extended:DatePicker SelectedDate="{Binding DeliveredDate}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
</data:DataGrid.Columns>
</data:DataGrid>
</Grid>

125
Tarefa 5: Implementando InotifyPropertyChanged (Passo 17)

public class ViewModel : INotifyPropertyChanged


{
protected object _selected;
public object SelectedItem
{
get
{
return _selected;
}
set
{
object old = _selected;
_selected = value;
if (_selected != old)
{
OnPropertyChanged("SelectedItem");
}
}
}

protected void OnPropertyChanged(string name)


{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(name));
}

#region INotifyPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;

#endregion
}

126
Tarefa 6 - Implementando INotifyPropertyChanged (Passo 9).

b = new Binding("SelectedItem");
b.Source = v;
b.Mode = BindingMode.TwoWay;
details.SetBinding(ContentControl.ContentProperty, b);

127
G- Construindo um Media
Player Simples
Primeiros Passos na Construção de um Media Player no Silverlight 2

Neste laboratório você vai construir um media player simples usando o controle <MediaElement> do
Silverlight. Você verá como usar a funcionalidade Play (Executar), Stop (Parar) e Pause (Pausar), bem
como manipular o progresso, o buffering e os marcadores de mídia.

128
Seção 1: Criando uma Página de Mídia
no Silverlight

Usando o Visual Studio, crie um novo projeto do Silverlight e chame-o de MediaLab. Lembre-se de
adicionar um Site (padrão MediaPageWeb) à solução.

 Adicione o ‘Bear.wmv’ da pasta de vídeos do Windows Vista em seu PC ao projeto do Silverlight.


Adicione-o também ao projeto da Web no diretório ‘ClientBin’.
 Edite o Page.xaml para adicionar um novo <MediaElement> à página:

<MediaElement Source="Bear.wmv" Width="400" Height="300"


x:Name="mel"></MediaElement>

 No Site, certifique-se de que o MediaLabTestPage.aspx está definido como a página padrão para
o site. Execute a aplicação e você verá algo como a Figura 1.

129
Figura 1. O Media Player Simples

 Você pode ver que o vídeo está sendo reproduzido sem problemas. No próximo passo, você vai
adicionar alguns Controles Play, Stop e Pause simples a ele.

130
Seção 2: Adicionando controles de
Reprodução à sua Mídia

O elemento de mídia do Silverlight fornece controles Play, Stop e Pause que podem ser usados para
controlar a reprodução de vídeo. Neste passo você vai adicionar uma interface de usuário simples que
sobrepõe o vídeo com os controles 'Play', 'Stop' e 'Pause', e você escreverá os manipuladores de eventos
para eles.

 Adicione este XAML à interface de usuário. (Note que isso deve ser em um Canvas e não o Grid
padrão. Veja se você pode descobrir como fazer isso!). Ponha-o no lugar apropriado para que os
controles sejam escritos no vídeo. Se você digitar este código, notará que quando especifica os
eventos ‘MouseLeftButtonUp’, o Visual Studio pede que você selecione um novo manipulador
de eventos. Vá em frente e aceite, e os stubs do código serão escritos para você.

<StackPanel Orientation="Horizontal"
Canvas.Top="260" Width="400" Background="Beige">
<TextBlock x:Name="tPlay" Text="Play "
MouseLeftButtonUp="tPlay_MouseLeftButtonUp" />
<TextBlock x:Name="tPause" Text="Pause "
MouseLeftButtonUp="tPause_MouseLeftButtonUp"/>
<TextBlock x:Name="tStop" Text="Stop "
MouseLeftButtonUp="tStop_MouseLeftButtonUp"/>
</StackPanel>

 Agora vá ao code-behind (Page.xaml.cs). Se o VS tiver criado os stubs de evento para você,


basta editá-los, caso contrário aqui está um exemplo de um:

private void tPlay_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)


{

 Chame o método apropriado no MediaElement (se chama ‘mel’) para os manipuladores de


eventos Play, Stop e Pause. Veja um exemplo para o Play:

private void tPlay_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)


{
mel.Play();
}

 Quando tiver terminado, execute a aplicação e você poderá executar, parar ou pausar o vídeo.
Você pode ver como isso ficaria na Figura 2.

131
Figura 2. Media Player com controles simples de reprodução

132
Seção 3: Usando o Progresso de
Download e o Progresso de Buffer

O Media Element permite acompanhar o progresso de download e de buffering atuais da mídia.

Eles são acessados usando os eventos DownloadProgressChanged e BufferingProgressChanged.

Você pode conectá-los facilmente. Vamos primeiro ver o DownloadProgressChanged.

 Adicione este atributo ao seu Media Element:

DownloadProgressChanged="mel_DownloadProgressChanged"

 Adicione este TextBlock ao mesmo <StackPanel> dos blocos de texto Play, Stop e Pause:

<TextBlock x:Name="tStatus" Text=""></TextBlock>

 Vá ao code behind e encontre o stub de evento ‘mel_DownloadProgressChanged’. Se ele não


estiver lá, basta adicionar este código:

private void mel_DownloadProgressChanged(object sender, RoutedEventArgs e)


{
tStatus.Text = (e.Source as MediaElement).DownloadProgress.ToString();

 A fonte do evento é o MediaElement, então você pode converter o e.Source e ter a propriedade
DownloadProgress como é mostrado.
 O DownloadProgress contém um número entre 0 e 1, com 1 sendo 100% completo. Se você
executá-lo, verá o status ‘piscar’ rapidamente pois ele está fazendo o download muito rápido a
partir de seu disco rígido. Ao acessar o vídeo pela web, não será tão rápido.
 Você pode formatar o valor em uma porcentagem de forma fácil – numérica ou visualmente! 
 Similarmente, você pode conectar o BufferingProgressChanged e visualizar a propriedade
BufferingProgress. Isso é útil se você tiver um vídeo baixado progressivo.

133
Seção 4: Gerenciando Marcadores de
Mídia

Neste passo vamos ver o uso do Expression Encoder para adicionar marcadores ao vídeo. Vamos
também capturar esses marcadores em nosso código do Silverlight.

Abra o Expression Encoder 2 e adicione o Bear Video (Vídeo do Urso) a ele. Você pode ver isso na Figura
3.

 Selecione a guia ‘MetaData’ e abra a exibição ‘Markers’.


 Arraste o playhead laranja pela linha do tempo do vídeo (você pode vê-lo logo abaixo da pré-
visualização do vídeo) e pare-o em torno da marca de 2 segundos. Não tem que ser exatamente
aí.
 Clique no botão ‘Add’ na exibição de Marcadores. Você verá que um marcador é adicionado
com um Valor Desconhecido
 Digite ‘Urso Fareja Água’

134
 Em torno da marca de 4,2 segundos, adicione um novo marcador e dê a ele o valor ‘Urso Fareja
Gaivota’.
 Ao redor da marca de 8,6 segundos, adicione um novo marcador e dê a ele o valor ‘Urso decide
comer gaivota’.
 Ao redor da marca de 8,9 segundos, adicione um novo marcador e dê a ele o valor ‘Gaivota
decide voar para longe’.
 Em torno da marca de 11,5 segundos, adicione um novo marcador e dê a ele o valor ‘Urso pensa
‘Droga’’.
 Clique no botão ‘Encode’ na parte inferior da tela.
 O arquivo irá para a pasta Documents\Expression\Expression Encoder.
 Substitua o Bear.wmv em seu projeto pelo que você acabou de codificar.
 Em seu projeto do Silverlight, adicione este Atributo ao seu Media Element

MarkerReached="mel_MarkerReached"

 Em seu code-behind você pode agora capturar os marcadores que acabou de adicionar ao vídeo,
e escrevê-los para o status do vídeo – veja o código:

private void mel_MarkerReached(object sender,


TimelineMarkerRoutedEventArgs e)
{
tStatus.Text = e.Marker.Text;
}

Você pode ver os resultados aqui:

135
H- Silverlight e Aplicações
Conectadas
Conectando suas Aplicações do Silverlight a serviços de Dados e Web

Introdução

O Silverlight 2 fornece uma funcionalidade que permite conectar sua aplicação, conforme ela é
executada no navegador, a recursos baseados em servidor. Há várias maneiras em que o Silverlight pode
se integrar a eles, desde uma simples solicitação HTTP usando um WebClient a solicitações HTTP mais
complexas usando as classes WebRequest/WebResponse, e assim por diante, até classes proxy para
consumo de serviços da Web WCF bem como serviços de dados ADO.

Neste laboratório, você vai construir aplicações que fazem o seguinte

 Usam um Cliente da Web para obter Dados de Cidades de uma página da web que serão
plotados em um Mapa do Virtual Earth
 Usam uma interação Web Request / Web Response para ver como os dados HTTP podem ser
postados de volta a um servidor Web
 Constroem um serviço da Web WCF que fornece dados, e usam um proxy gerado no Silverlight
para consumi-lo
 Constroem um serviço de Dados ADO que fornece dados, e o consomem dentro do Silverlight

A terceira seção, usando WCF, é particularmente especial – você verá como o WCF e o Silverlight
trabalham ‘melhor juntos’ para dar a você uma experiência muito melhor como desenvolvedor na
criação da aplicação ligada a dados! Quando comparado ao uso de WebRequest/WebResponse, você
tem muito mais facilidade, e escreve menos de 1/3 do código para fazer a mesma coisa.

136
Seção 1: Usando o WebClient para Ler
Dados Remotos de Ligação para o
Silverlight

Neste exercício você vai construir um serviço POX (Plain Old XML) simples que fornece dados de cidades
quando chamado, e vai ligar conteúdos a esses dados dentro do Silverlight.

Você verá as seguintes tecnologias:

 WebClient que é usado para solicitações HTTP assíncronas simples.


 XDocument, XmlWriter e XmlSerializer para gerenciar Dados XML
 O ItemsControl XML para ligação de dados

Tarefa 1: Crie uma aplicação do Silverlight e adicione a Classe


CityData

Crie uma nova aplicação do Silverlight e chame-a de Sample1. Lembre-se de selecionar que quer um Site
como parte da solução quando o fizer.

3. Adicione uma nova Classe, chamada CityData.cs à parte de site de sua solução.
4. Adicione variáveis de membro para CityName (seqüência de caracteres) Latitude (dobro) e
Longitude (dobro).
5. Mantenha o construtor padrão (sem parâmetros)
6. Adicione outro construtor que pega uma seqüência de caracteres e dois dobros e inicializa as
variáveis de membro

Se estiver com dificuldades, verifique os Apêndices para ver o código completo.

137
Tarefa 2: Crie o Serviço XML

Nesta tarefa você vai adicionar um manipulador genérico e fazê-lo retornar dados XML fictícios ao
chamador.

 Adicione um novo manipulador Genérico à sua aplicação Web e chame-o de GetData.ashx


 O manipulador Genérico precisará de algumas referências adicionadas a ele, portanto adicione o
seguinte código no topo da página de código Ele deve ser adicionado diretamente abaixo de
‘using System.Web’

using System.Linq;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Serialization;

 Seus dados de cidades serão mantidos em um objeto List<T>, então adicione a declaração a ele
no topo da classe de manipulador genérico

List<CityData> myCities = new List<CityData>();

 O manipulador Genérico processará uma solicitação de entrada usando o código declarado na


função ProcessRequest. Essa função deve criar algumas novas instâncias de CityData e adicioná-
las à lista myCities. Aqui estão alguns exemplos de cidades com longitude e latitude { (London,
51.5,0), (Stratford-upon-Avon, 52.3, -1.71), (Edinburgh, 55.95, -3.16)}. Veja se consegue escrever
o código para elas.
 A seguir, você escreverá o código que serializa a List<T> em XML e faz um writeback como uma
resposta ao chamador. Aqui está o código:

XmlSerializer ser = new XmlSerializer(typeof(List<CityData>));

using (XmlWriter writer = XmlWriter.Create(context.Response.OutputStream))


{
context.Response.ContentType = "text/xml";
ser.Serialize(writer, myCities);

Se você construiu corretamente o manipulador Genérico, pode agora chamá-lo e ver os resultados como
os da Figura 1.

138
Você pode notar que o nó ArrayOfCityData foi gerado para você pelo XmlWriter. Também foi dado a ele
um namespace e um XSD padrão.

 Exercício: Veja se você consegue substituir os namespaces padrão.

139
Tarefa 3: Crie o XAML ao qual fará ligações
O ItemsControl do XAML permite definir como renderizar dados ligados de acordo com um
DataTemplate. Você pode ver aqui como especificar um XAML que defina um único TextBlock que se
liga ao campo CityName. [Isto deve estar no arquivo Page.xaml em seu projeto do Silverlight]

<ItemsControl x:Name="_cities">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock FontSize="14" Height="30" Text="{Binding CityName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

 Veja se consegue atualizar isso para ter 3 TextBlocks, com os outros dois estando ligados a
Latitude e Longitude respectivamente

140
Tarefa 4: Crie a solicitação e o retorno de chamada do
WebClient

Antes de codificar o WebClient, é uma boa idéia definir uma porta estática para seu projeto da Web ser
executado. Para isso, selecione o projeto da Web no Solution Explorer e pressione F4 para abrir a janela
de propriedades.

Encontre a entrada ‘Use Dynamic Ports’ e defina-a como ‘False’. Salve tudo, encontre a configuração '
Port Number' e coloque ‘8001’.

Agora o URI para o manipulador que retorna XML para você pode ser determinado para estar em:

http://localhost:8001/Sample1Web/GetData.ashx

141
 Olhe o code-behind do Page.xaml.cs para o projeto do Silverlight. Você verá um construtor
Page() que tem uma única linha (‘InitializeComponent()’). Adicione novo código a ele para criar
uma nova instância da classe WebClient e instrua-o a fazer download de uma seqüência de
caracteres do URI acima, bem como conectar um retorno de chamada completado do
manipulador de eventos.

142
Tarefa 5: Ligando os Dados no Retorno de Chamada
O ItemsControl que você criou na Tarefa 3 se ligará a um tipo de IEnumerable, então você precisa obter
os resultados do retorno de chamada do serviço de dados e formatá-los apropriadamente.

Quando você especificou o retorno de chamada DownloadStringCompleted na Tarefa 3, o Visual Studio


deve ter criado um manipulador de eventos clichê para você. Se não o fez, aqui está uma cópia dele
para você:

void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs


e)
{
}

Enquanto fizer esta Tarefa, adicione o código que vir a esta função.

Você notará que ela usa um DownloadStringCompletedEventArgs como parâmetro. Os dados da


seqüência de caracteres serão armazenados na propriedade Result, então você pode carregá-los a um
XDocument usando

XDocument xReturn = XDocument.Parse(e.Result);

*Note que você precisará fazer uma referência a System.Xml.Linq e adicionar um ‘using System.Xml.Linq’
ao topo de sua página de código]

Antes de continuar você precisa de uma declaração da classe CityData dentro de seu projeto do
Silverlight também, então agora é um bom momento para adicionar uma e torná-la idêntica à da Tarefa
1. Um dos novos recursos de linguagem do .NET e do Silverlight é a LINQ, que traz alguma programação
funcional ao Silverlight.

Veja como você pode usá-la para criar um IEnumerable de CityData a partir do XML retornado.

IEnumerable<CityData> cities = from city in xReturn.Descendants("CityData")


select new CityData
{
CityName = city.Element("CityName").Value,
Latitude = Convert.ToDouble(city.Element("Latitude").Value),
Longitude = Convert.ToDouble(city.Element("Longitude").Value)
};

Isso vai criar um IEnumerable de objetos CityData com n objetos, onde n é determinado pelo número de
nós de CityData dentro do XML. O XML está embutido em código com 3 elementos, então esse código
data a você 3 objetos CityData dentro do IEnumerable, e suas propriedades CityName, Latitude e
Longitude serão baseadas nos valores dentro dos elementos XML.

Agora que você tem seu IEnumerable, basta ligá-lo ao ItemsControl que, se você se lembra, se chama
‘_cities’.

143
_cities.ItemsSource = cities;

Agora você pode executar o projeto e verificar os resultados! 

Mais Estudo
Neste exemplo seu ASHX forneceu dados embutidos em código para 1 cidade. Você consegue construí-
lo de modo que aceite parâmetros na seqüência de caracteres URI (isto é,
http://localhost:8001/Sample1Web/GetData.ashx?city=whatever)

144
Seção 2: Usando WebRequest /
WebResponse para obter Dados

Nesta seção do laboratório vamos ver como usar as classes WebRequest e WebResponse para ter um
controle mais refinado sobre a comunicação entre o cliente do Silverlight e o servidor Web. No exemplo
anterior você viu como o WebClient foi usado para chamar uma aplicação de servidor usando seu URI.
Você poderia passar parâmetros a ele na querystring de URI, o que seria um método perfeitamente
válido, mas para o propósito deste exercício, você usará um HTTP-POST para passar os parâmetros como
parte dos cabeçalhos HTTP, e a aplicação de servidor vai abri-los, inspecioná-los e retornar uma
resposta.

Você passará um parâmetro ‘CountryName’, que o servidor vai usar para derivar cidades nesse país e
retorná-las ao chamador.

145
Tarefa 1: Crie o Projeto e instale a aplicação de servidor
Crie uma nova aplicação do Silverlight chamada Sample 2. Lembre-se de selecionar que quer um Site
criado como parte dela.

 Adicione uma nova classe CityData, exatamente o mesmo que fez na Seção 1.
 Adicione também um Manipulador Genérico e chame-o de GetData.ashx
 Certifique-se de que tem as seguintes instruções using no topo do GetData.ashx

using System;
using System.Web;
using System.Linq;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Serialization;

 Adicione uma declaração para uma nova List<T> de CityData

List<CityData> myCities = new List<CityData>();

 O “burro de carga” dessa função é a função ProcessRequest, então o próximo passo é adicionar
uma verificação de que estamos usando um HTTP-POST

if (context.Request.HttpMethod == "POST")
{}

 O HTTP-POST é baseado na comunicação de Formulários da web (não confundir com .NET


WebForms), portanto a coleção de parâmetros é armazenada em campos de Formulários. Para
obter o valor de um campo chamado ‘country’ (país), use o seguinte código:

strCountry = context.Request.Form["country"].ToString();

 Escreva uma função que pegue um país e construa uma List<CityData> de várias cidades para
esse país. Aqui estão algumas cidades e suas latitudes e longitudes:

("Paris", 48.87, 2.33));


("Lourdes", 43.1, 0.05));
("Toulouse", 43.6, 1.38));
("London", 51.5, 0));
("Stratford-Upon-Avon", 52.3, -1.71));
("Edinburgh", 55.95, -3.16));
("Berlin", 52.52, 13.42));
("Munich", 48.13, 11.57));
("Hamburg", 53.58, 9.98));

A função está disponível nos apêndices se estiver encontrando problemas.

146
 Finalmente, crie um serializador XML e faça um writeback da List<CityData> para o fluxo de
resposta. É idêntico ao que você fez na Seção 1. O código complete está disponível nos
apêndices.

Finalmente, defina as propriedades do projeto da Web para que ele use uma porta estática em vez de
portas dinâmicas, e atribua a ele a porta ‘8002’. Se você não souber fazer isso, verifique a Seção 1.

147
Tarefa 2: Crie a interface de usuário do Silverlight
Vamos criar uma interface de usuário simples que enviará um parâmetro a esse serviço, e obterá os
resultados da List<CityData> para depois ligá-los à interface de usuário.

 Adicione um ItemsControl chamado ‘_cities’ que contém um DataTemplate que liga três
controles de TextBlock a CityName, Latitude e Longitude, respectivamente.
 Adicione três botões, chamados bUk, bFrance e bGermany respectivamente, com ‘Reino Unido’,
‘França’ e ‘Alemanha’ como suas legendas.
 Adicione um manipulador de eventos Click a cada um desses botões

O XAMl completo está disponível nos apêndices para referência.

148
Tarefa 3: Escreva a lógica da interface de usuário e da Ligação
de Dados
Antes de continuar, certifique-se de que tem as referências certas:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO;
using System.Xml;
using System.Xml.Linq;

Certifique-se também de que adicionou uma referência a ‘System.Net’ e ‘System.Xml.Linq’ usando ‘Add
Reference’ no Solution Explorer.

 A seguir você vai escrever uma função getCitiesFromService() que chama o serviço e faz um
HTTP-POST do país para obter a List<CityData> apropriada
Aqui está o código:

private void getCitiesfromService()


{
Uri uri = new Uri("http://localhost:8002/Sample2Web/GetData.ashx");
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);

//WebRequest request = WebRequest.Create(uri);


request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.BeginGetRequestStream(new AsyncCallback(RequestProceed), request);
}

 Aqui você instalou o objeto request, e ele está chamando o serviço GetData.ashx. Depois você
começa o fluxo de solicitação (request) e especifica que quando ele estiver pronto você chamará
a função RequestProceed em um retorno de chamada.
Agora vamos ver como construí-lo.
Primeiro, a assinatura do método. Um retorno de chamada dessa natureza é vazio, e aceita um
parâmetro IASyncResult.

void RequestProceed(IAsyncResult asyncResult)


{
}

 Você pode ter uma referência ao seu objeto de solicitação a partir deste asyncResult assim:

149
HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;

 Em seguida você deve criar um escritor de fluxo que escreva para esse fluxo de solicitação, e
escreva um valor para o parâmetro ‘country=’. Fechar o fluxo faz o writeout.

StreamWriter postDataWriter = new


StreamWriter(request.EndGetRequestStream(asyncResult));
postDataWriter.Write("country=" + strCountry);
postDataWriter.Close();

 Finalmente, você deve definir um retorno de chamada para processar a resposta do serviço,
para poder capturar os dados e ligá-los à interface de usuário.

request.BeginGetResponse(new AsyncCallback(ResponseProceed), request);

 Agora você precisa construir o manipulador de resposta. Isso tem a mesma assinatura de
método do manipulador de resposta que você viu anteriormente.

void ResponseProceed(IAsyncResult asyncResult)


{ }

 A primeira coisa que você terá que fazer nessa função é ter uma referência ao seu objeto de
solicitação novamente:

HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;

 E então precisará de uma referência à sua resposta (response).

HttpWebResponse response =
(HttpWebResponse)request.EndGetResponse(asyncResult);

 A resposta é codificada em XML em uma seqüência de caracteres, então você vai ler o fluxo em
uma seqüência de caracteres:

StreamReader responseReader = new StreamReader(response.GetResponseStream());


string responseString = responseReader.ReadToEnd();

 Como é XML, você vai carregar isso para um XDocument

XDocument xReturn = XDocument.Parse(responseString);

 Para ligar esses dados ao ItemsSource eles precisam ser carregados para um IEnumerable.
Podemos usar a LINQ para fazer isso com facilidade:

IEnumerable<CityData> cities = from city in xReturn.Descendants("CityData")


select new CityData
{

150
CityName = city.Element("CityName").Value,
Latitude = Convert.ToDouble(city.Element("Latitude").Value),
Longitude = Convert.ToDouble(city.Element("Longitude").Value)

};

 Finalmente, você precisa ligá-los ao ItemsControl chamado _cities. Tente isto:

_cities.ItemsSource = cities;

Mas isso não funcionaria! A razão é que o WebRequest/WebResponse atua em um thread de segundo
plano e não no thread da interface de usuário, portanto a interface de usuário não seria atualizada.

 Felizmente, há uma solução simples usando o Dispatcher. Este código vai funcionar:

Dispatcher.BeginInvoke(() => _cities.ItemsSource = cities);

 Para seu exercício final, escreva os manipuladores de eventos para os botões para que quando o
usuário clicar neles, a função getCitiesfromService seja chamada e os dados apropriados sejam
retornados.

151
Seção 3: Construindo e Ligando a um
Serviço WCF

Nas seções anteriores, você viu primeiro como construir um serviço POX simples que era acessado via
um WebClient, e depois como estendê-lo para aceitar verbos HTTP-POST usando
WebRequest/WebResponse assíncrono.

Agora você vai ver como o WCF pode ser usado para implementar esse serviço, simplificando-o e
permitindo que você tenha os aspectos seguros, transacionáveis e confiáveis do WCF, bem como a
facilidade de gerar automaticamente classes proxy cliente que manipulam a ‘conexão’ da comunicação
para você.

Além disso, você notará que os dois exemplos anteriores precisaram do modelo de dados para ser
sincronizados entre o cliente e o servidor – isto é, você precisou de uma classe CityData em ambos, e
qualquer diferença entre o código nessas classes faria sua aplicação ser interrompida.

Ao construir o serviço no WCF você vai atribuir seu serviço e suas classes de dados, e a geração do proxy
vai mantê-los em sincronia.

152
Tarefa 1: Crie o Projeto do Silverlight e adicione o Serviço WCF

 Comece criando um novo projeto do Silverlight da forma usual e chame-o de Sample 3.


 Adicione uma nova classe CityData ao seu projeto da Web e codifique-o da mesma forma que
fez nas seções anteriores.
 Em seguida você vai adicionar os atributos WCF a essa classe, permitindo que o WCF a entenda,
e gerar os proxies de cliente (que você verá depois). No WCF, a declaração de classe deve ser
atribuída como [DataContract] e cada uma das variáveis de membro deve ser atribuída
como [DataMember]. Você pode encontrar a classe completa nos apêndices.
 O próximo passo é adicionar um novo Serviço WCF ao seu projeto da Web e chamá-lo de
GetCities.svc
 Você notará que isso adiciona três arquivos: GetCities.svc, GetCities.cs e IGetCities.cs
 O IGetCities.cs define a interface de seu serviço. Abra-o e você verá uma interface padrão
contendo um único método clichê. Mude-o para corresponder ao seguinte:

[ServiceContract]
public interface IGetCities
{
[OperationContract]
List<CityData> getCities(string strCountry);
}

 De forma similar aos contratos de Dados que você viu em sua classe, isso especifica os contratos
de operação para seu serviço.
 Agora é hora de editar o código do serviço. Vá à classe GetCities.cs e escreva uma função que
retorna uma List<CityData> para um parâmetro ‘country’ de entrada. Você já fez isso (como
uma função auxiliar) no Cenário 2. O código completo está nos apêndices se você precisar.
 O último passo é mudar seu projeto da web que usa portas dinâmicas para o uso de uma porta
estática em 8003. Faça isso, clique com o botão direito no arquivo SVC e visualize-o no
navegador para se certificar de que funciona. Note que é uma boa idéia copiar o URI para a área
de transferência, já que você precisará dele para criar o cliente de serviço na próxima tarefa.

153
Tarefa 2: Crie o Cliente do Silverlight e Ligue-o ao Serviço

Nesta tarefa você vai criar o mesmo cliente do Silverlight que criou na seção anterior, mas agora em vez
de escrever todo o código para manipular o WebRequest e o WebResponse, o gerador de proxy do WCF
fará isso por você! Você também verá como é mais fácil ligar o conteúdo do Silverlight aos dados
retornados, porque em vez de serializar e desserializar o XML, a List<CityData> é passada diretamente
ao chamador.

 Em seu projeto do Silverlight, adicione uma nova referência de Serviço e aponte-a ao URI do
serviço que criou na Tarefa 1. Dê a ela o nome de CitiesService.
 Instale o XAML para que sua interface de usuário tenha exatamente o mesmo XAML que você
usou na Seção 2, contendo um ItemsControl e 3 botões para Reino Unido, França e Alemanha.
 Vá ao seu arquivo Page.xaml.cs e adicione as 3 instruções a seguir no topo da página de código -
os 2 primeiros são para o WCF, o terceiro é para seu proxy de serviço. Neste caso o namespace
Sample3 foi usado. Se você chamou o projeto de algo diferente de Sample 3, use esse nome no
lugar:

using System.ServiceModel;
using System.ServiceModel.Channels;
using Sample3.CitiesService;

 Codifique os manipuladores de eventos para Clicar nos Botões. Aqui está o de ‘uk’ como
exemplo. Você deve conseguir copiar isto para os outros:

private void bUk_Click(object sender, RoutedEventArgs e)


{

getCitiesfromService("uk");
}

 Isso chama uma função chamada getCitiesfromService, passando a ela uma seqüência de
caracteres. Seu próximo passo é escrever essa função, que usa o proxy de serviço para obter a
List<CityData> de volta do serviço. Como ela está usando o proxy, é muito simples - aqui está o
código:

private void getCitiesfromService(string strCountry)


{
GetCitiesClient myCitiesClient = new GetCitiesClient();
// Note que as três próximas linhas foram truncadas pelo
// Word. Elas deveriam ser uma única linha
myCitiesClient.getCitiesCompleted +=
new EventHandler<getCitiesCompletedEventArgs>
(myCitiesClient_getCitiesCompleted);
myCitiesClient.getCitiesAsync(strCountry);
}

154
 Esse código é bastante fácil de entender. Você cria primeiro uma instância da classe proxy,
depois especifica o retorno de chamada, e então chama a função getCitiesAsync passando a ela
a seqüência de caracteres.
 O retorno de chamada receberá a List<CityData> de volta, que você pode ligar ao seu
ItemsControl. Aqui está o método completo:

void myCitiesClient_getCitiesCompleted(object sender,


getCitiesCompletedEventArgs e)
{
_cities.ItemsSource = e.Result;

155
Apêndices

Seção 1: Respostas das Tarefas

Tarefa 1: Classe CityData


Aqui está o código para a Classe CityData

public class CityData


{

public string CityName { get; set; }


public double Latitude { get; set; }
public double Longitude { get; set; }

public CityData(string strCityName, double nLatitude,


double nLongitude)
{
CityName = strCityName;
Latitude = nLatitude;
Longitude = nLongitude;
}

public CityData()
{

Tarefa 2: Manipulador Genérico

Aqui está o código para o Manipulador Genérico

<%@ WebHandler Language="C#" Class="GetData" %>

using System;
using System.Web;
using System.Linq;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Serialization;
public class GetData : IHttpHandler {

List<CityData> myCities = new List<CityData>();

public void ProcessRequest (HttpContext context) {


myCities.Add(new CityData("London", 51.5, 0));
myCities.Add(new CityData("Stratford-Upon-Avon", 52.3, -1.71));
myCities.Add(new CityData("Edinburgh", 55.95, -3.16));
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlSerializer ser = new XmlSerializer(typeof(List<CityData>));

156
using (XmlWriter writer =
XmlWriter.Create(context.Response.OutputStream))
{
context.Response.ContentType = "text/xml";
ser.Serialize(writer, myCities,ns);
}

public bool IsReusable {


get {
return false;
}
}

Tarefa 3: XAML Ligado

Aqui está o XAML completo que descreve 2 TextBlocks ligáveis

<ItemsControl x:Name="_cities">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="14" Height="30" Text="{Binding CityName}" />
<TextBlock FontSize="14" Height="0" Text="{Binding Latitude}" />
<TextBlock FontSize="14" Height="0" Text="{Binding Longitude}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Note que a altura dos TextBlocks de Latitude e Longitude foi ocultada para mantê-los invisíveis na
renderização.

Tarefa 4: Criando a chamada para o Serviço POX


Aqui está o código para implementar a Tarefa 4.

public Page()
{
InitializeComponent();
WebClient wc = new WebClient();
wc.DownloadStringAsync(new
Uri("http://localhost:8001/Sample1Web/GetData.ashx"));
wc.DownloadStringCompleted += new
DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
}

157
Tarefa 5: Ligando os Dados no Retorno de Chamada

void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs


e)
{
XDocument xReturn = XDocument.Parse(e.Result);

IEnumerable<CityData> cities = from city in xReturn.Descendants("CityData")


select new CityData
{
CityName = city.Element("CityName").Value,
Latitude = Convert.ToDouble(city.Element("Latitude").Value),
Longitude = Convert.ToDouble(city.Element("Longitude").Value)

};

_cities.ItemsSource = cities;
}

Seção 2:

Tarefa 1: Função GetCities


Aqui está o código completo para a função getCities:

private List<CityData> getCities(string strCountry)


{
List<CityData> ret = new List<CityData>();

switch (strCountry)
{
case "france":
{
ret.Add(new CityData("Paris", 48.87, 2.33));
ret.Add(new CityData("Lourdes", 43.1, 0.05));
ret.Add(new CityData("Toulouse", 43.6, 1.38));
break;
}
case "uk":
{
ret.Add(new CityData("London", 51.5, 0));
ret.Add(new CityData("Stratford-Upon-Avon", 52.3, - .71));
ret.Add(new CityData("Edinburgh", 55.95, -3.16));
break;
}
case "germany":
{
ret.Add(new CityData("Berlin", 52.52, 13.42));
ret.Add(new CityData("Munich", 48.13, 11.57));
ret.Add(new CityData("Hamburg", 53.58, 9.98));
break;
}
default:
{
ret.Add(new CityData("London", 51.5, 0));
ret.Add(new CityData("Stratford-Upon-Avon", 52.3, -1.71));
ret.Add(new CityData("Edinburgh", 55.95, -3.16));

158
break;
}
}
return ret;
}

Esta é a função GetData ProcessRequest:

List<CityData> myCities;

public void ProcessRequest (HttpContext context) {


string strCountry = "";
if (context.Request.HttpMethod == "POST")
{
strCountry = context.Request.Form["country"].ToString();
myCities = getCities(strCountry);
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlSerializer ser = new XmlSerializer(typeof(List<CityData>));
using (XmlWriter writer =
XmlWriter.Create(context.Response.OutputStream))
{
context.Response.ContentType = "text/xml";
ser.Serialize(writer, myCities, ns);
}
}

Tarefa 2: XAML da interface de usuário

Este é o XAML para a interface de usuário.

<UserControl x:Class="Sample2.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontFamily="Trebuchet MS" FontSize="11"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel Orientation="Vertical">
<ItemsControl x:Name="_cities">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="14" Height="30" Text="{Binding
CityName}" ></TextBlock>
<TextBlock FontSize="14" Height="0" Text="{Binding
Latitude}" ></TextBlock>
<TextBlock FontSize="14" Height="0" Text="{Binding
Longitude}" ></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button x:Name="bUk" Content="UK" Click="bUk_Click" />

159
<Button x:Name="bFrance" Content="France" Click="bFrance_Click" />
<Button x:Name="bGermany" Content="Germany" Click="bGermany_Click"
/>
</StackPanel>
</Grid>
</UserControl>
Tarefa 3: Código da Página
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO;
using System.Xml;
using System.Xml.Linq;

namespace Sample2
{
public partial class Page : UserControl
{
IEnumerable<CityData> cities;
string strCountry = "uk";
public Page()
{
InitializeComponent();

void RequestProceed(IAsyncResult asyncResult)


{
HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
StreamWriter postDataWriter = new
StreamWriter(request.EndGetRequestStream(asyncResult));
postDataWriter.Write("country=" + strCountry);
postDataWriter.Close();
request.BeginGetResponse(new AsyncCallback(ResponseProceed), request);
}

void ResponseProceed(IAsyncResult asyncResult)


{
HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
HttpWebResponse response =
(HttpWebResponse)request.EndGetResponse(asyncResult);
StreamReader responseReader = new
StreamReader(response.GetResponseStream());
string responseString = responseReader.ReadToEnd();

XDocument xReturn = XDocument.Parse(responseString);

IEnumerable<CityData> cities =
from city in xReturn.Descendants("CityData")
select new CityData
{
CityName = city.Element("CityName").Value,
Latitude = Convert.ToDouble(city.Element("Latitude").Value),
Longitude = Convert.ToDouble(city.Element("Longitude").Value)

160
};

Dispatcher.BeginInvoke(() => _cities.ItemsSource = cities);


}

private void getCitiesfromService()


{
Uri uri = new Uri("http://localhost:8002/Sample2Web/GetData.ashx");
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);

//WebRequest request = WebRequest.Create(uri);


request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.BeginGetRequestStream(
new AsyncCallback(RequestProceed), request);
}
private void bUk_Click(object sender, RoutedEventArgs e)
{
strCountry = "uk";
getCitiesfromService();
}
private void bFrance_Click(object sender, RoutedEventArgs e)
{
strCountry = "france";
getCitiesfromService();
}

private void bGermany_Click(object sender, RoutedEventArgs e)


{
strCountry = "germany";
getCitiesfromService();
}
}
}

Seção 3

Tarefa 1: Classe de Dados de Cidades


Esta é a fonte completa para a classe CitiesData com a atribuição do WCF.

using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Runtime.Serialization;

[DataContract]
public class CityData
{
[DataMember]

161
public string CityName { get; set; }
[DataMember]
public double Latitude { get; set; }
[DataMember]
public double Longitude { get; set; }

public CityData(string strCityName, double nLatitude, double nLongitude)


{
CityName = strCityName;
Latitude = nLatitude;
Longitude = nLongitude;
}

public CityData()
{
}
}

Classe de Serviço GetCities


Este é o código para a classe de implementação GetCities.cs para o serviço.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

// OBSERVAÇÃO: Se você mudar o nome da classe “GetCities” aqui, deve atualizar


também a referência a “GetCities” no Web.config.
public class GetCities : IGetCities
{
public List<CityData> getCities(string strCountry)
{
List<CityData> ret = new List<CityData>();

switch (strCountry)
{
case "france":
{
ret.Add(new CityData("Paris", 48.87, 2.33));
ret.Add(new CityData("Lourdes", 43.1, 0.05));
ret.Add(new CityData("Toulouse", 43.6, 1.38));
break;
}
case "uk":
{
ret.Add(new CityData("London", 51.5, 0));
ret.Add(new CityData("Stratford-Upon-Avon", 52.3, -1.71));
ret.Add(new CityData("Edinburgh", 55.95, -3.16));
break;
}
case "germany":
{
ret.Add(new CityData("Berlin", 52.52, 13.42));
ret.Add(new CityData("Munich", 48.13, 11.57));
ret.Add(new CityData("Hamburg", 53.58, 9.98));
break;
}

162
default:
{
ret.Add(new CityData("London", 51.5, 0));
ret.Add(new CityData("Stratford-Upon-Avon", 52.3, -1.71));
ret.Add(new CityData("Edinburgh", 55.95, -3.16));
break;
}
}
return ret;
}
}

163

You might also like