Este artigo foi traduzido por máquina.

Wicked Code

3 Dicas importantes para o desenvolvimento do Silverlight

Jeff Prosise

Download do código disponível na Galeria de código do MSDN
Procure o código on-line

Conteúdo

Carregamento de assembly sob demanda
Processamento Just-in-Time
Evitar dependências regionais
Ativar uma nova página

Como escrevo isso, 2 do Silverlight é hot desativar a pressiona e desenvolvedores estão ficando primeiro examinar o que muitos acreditam representa o futuro da programação Web. Se você estiver um proponent do Silverlight ou localizar mais allure em tecnologias concorrentes, como o Adobe flexível, é interessante ver alternativas para HTML, JavaScript, e o AJAX surgir e obter mindshare para criar aplicativos da Web. E com a Microsoft já rígido no trabalho no Silverlight 3, o futuro tem nunca parecia mais clara.

Como é verdade para qualquer plataforma, trânsito para se tornar um desenvolvedor do Silverlight não é sem poucas potholes. Você sabia, por exemplo, que muitas chamadas para XamlReader.Load que testam bem em computadores nos Estados Unidos falhará em computadores em outros países? Você fez percebe que mecanismo de processamento do Silverlight está intimamente ligado o segmento de interface do usuário e que esse fato profoundly pode afetar a estrutura do seu código? Você sabia que você pode reduzir o tamanho dos arquivos XAP por dinamicamente carregar módulos (assemblies), mas o que fazê-lo sem perder a vantagem de digitação de alta segurança exige conhecimento de internos do CLR? Se isso intrigues você, leia em. Tenho algumas dicas e truques para compartilhar que tornará a vida com o Silverlight um pouco menos bumpy — e torná-lo uma melhor e mais informado programador do Silverlight, muito.

Para obter mais informações sobre compactação de conteúdo do Silverlight para entrega mais rápido, consulte oEdição de janeiro de 2009 de Cutting Edge.

Carregamento de assembly sob demanda

Uma dos hallmarks de um aplicativo bem projetado do Silverlight é um pequeno arquivo XAP, formalmente conhecido como um pacote de aplicativos. XAP arquivos tamanhos de todos os demais geralmente swell para impossível de gerenciar como resultado de recursos incorporados (especialmente imagens) e referências de assembly. Quanto maior o arquivo XAP, mais tempo demora para fazer o download, e se ele ficar muito extenso, o Silverlight talvez não é possível carregar a ele.

Mesmo grandes aplicativos podem ser empacotados em pequenos arquivos XAP se você for cuidadoso incluir os recursos e os assemblies que o aplicativo usa e mantenha os que pode ser carregado com atraso ou baixado por demanda no servidor Web. Você pode usar WebClient ou outras classes na pilha de rede do Silverlight para baixar os recursos adicionais e módulos (assemblies) depois que o pacote de aplicativos foi baixado. É geralmente preferível para obter a interface do usuário o aplicativo backup e executar rapidamente e solicitações de rede assíncrona de inicialização para os ativos adicionais que precisar que lançar um XAP 100 MB de arquivos e forçar o usuário a gastar cinco minutos aguardar um indicador de progresso atingir 100 %.

Recursos on-demand carregando no Silverlight tende a ser simples e direto. O trecho de código na Figura 1 , por exemplo, downloads de uma imagem JPEG implantado no site de origem e exibe-a, atribuindo os bits baixados a uma imagem XAML chamada MyImage.

Figura 1 o download de imagens do site de origem

WebClient wc = new WebClient();
wc.OpenReadCompleted +=
    new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
wc.OpenReadAsync(new Uri("JetCat.jpg", UriKind.Relative));
  ...
void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    if (e.Error == null)
    {
        BitmapImage bi = new BitmapImage();
        bi.SetSource(e.Result);
        MyImage.Source = bi;
    }
}

On-demand assembly carregar tende a ser mais difícil, no entanto. A princípio parece fácil: usar o WebClient para baixar o assembly e AssemblyPart.Load carregá-lo para o appdomain. O problema é que o compilador do JIT do Silverlight pode obter da forma, à esquerda muitos desenvolvedores para acreditar que é impossível fazer o download assemblies por demanda e aproveitar os benefícios de alta segurança digitando, muito. Na realidade, no entanto, poderá fazer ambos. Mas você precisa saber o que você está fazendo e ter um entendimento básico de como o carregamento de assembly funciona no CLR.

Para demonstrar, considere o seguinte código:

private void CreateCalendarButton_Click(object sender, RoutedEventArgs e)
{
    Calendar cal = new Calendar();
    cal.Width = 300.0;
    cal.Height = 200.0;
    cal.SelectedDatesChanged += new
        EventHandler<SelectionChangedEventArgs>(cal_SelectedDatesChanged);
    LayoutRoot.Children.RemoveAt(0);
    LayoutRoot.Children.Add(cal);
}

fig02.gif

A Figura 2 mantendo um conjunto do XAP

É um botão click manipulador que dinamicamente cria um controle de calendário e o adiciona à cena XAML. (Ele também exclui o botão que acionou o evento, que é considerado o item 0th coleção de filhos do LayoutRoot.) Como o calendário é implementado em System.Windows.Controls.dll, que não é incorporada a Silver­light plug-in, mas em vez disso, é parte da BCL estendido, esse código funciona muito bem desde que você adicionar uma referência a System.Windows.Controls.dll ao seu projeto. A referência faz System.Windows.Controls.dll sejam incluídos no arquivo XAP e carregado automaticamente o appdomain.

Agora suponha que você deseja ser inteligentes e somente a carga System.Windows.Controls.dll se ela é necessária — ou seja, se o usuário clicar no botão. Portanto, uma referência a System.Windows.Controls.dll você adicionar ao projeto para atender o compilador (caso contrário, o compilador não compila uma referência ao calendário porque o compilador não tem nenhuma idéia que o tipo de calendário é) e, na janela Visual Studio Properties, você defina propriedade Copy Local da System.Windows.Controls.dll como false para impedir que ele seja incorporada no arquivo XAP (como fiz na Figura 2 ).

Em seguida, você pode implantar uma cópia do System.Windows.Controls.dll juntamente com o arquivo XAP na pasta de ClientBin do aplicativo no servidor. Finalmente, você reestruturar seu código conforme mostrado na Figura 3 . O botão click manipulador agora downloads System.Windows.Controls.dll do servidor Web, carrega o appdomain com Assembly­Part.Load e cria um controle Calendar.

A Figura 3 por demanda assembly ao carregar que não funciona

private void CreateCalendarButton_Click(object sender, RoutedEventArgs e)
{
    WebClient wc = new WebClient();
    wc.OpenReadCompleted +=
        new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
    wc.OpenReadAsync(new Uri("System.Windows.Controls.dll",
        UriKind.Relative));
}

void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    if (e.Error == null) 
    {
        // Load the downloaded assembly
        AssemblyPart part = new AssemblyPart();
        part.Load(e.Result);

        // Create a Calendar control
        Calendar cal = new Calendar();
        cal.Width = 300.0;
        cal.Height = 200.0;
        cal.SelectedDatesChanged += new
           EventHandler<SelectionChangedEventArgs>(cal_SelectedDatesChanged);
        LayoutRoot.Children.RemoveAt(0);
        LayoutRoot.Children.Add(cal);
    }
}        

Parece razoável e o código é compilado bem, mas em tempo de execução, o manipulador de cliques gera uma exceção como da Figura 4 . O código que compila é bom. O código que emite exceções não é. Portanto, o que acontece? A mensagem de erro parece indicar que o CLR está tentando carregar System.Windows.Controls.dll, mas ele não precisa desde que você está carregá-lo por meio de programação.

Isso é um ótimo exemplo de um caso onde Conhecimento de internos do CLR pode fazer você um programador melhor do Silverlight. O problema aqui é que quando o compilador JIT compila seu método wc_OpenReadCompleted, ele verifica se o método, verá que ele faz referência um tipo chamado calendário e tenta carregar System.Windows.Controls.dll para que a referência pode ser resolvida.

fig04.gif

A Figura 4 Opa!

Infelizmente, isso acontece antes que o método seja executado mesmo, assim, você não obtiver a chance de chamar AssemblyPart.Load. É um problema de frango e ovos clássico. Você precisa chamar AssemblyPart.Load para carregar o assembly, mas antes que você possa chamá-lo, o compilador JIT intervenes e tenta carregá-lo para você. A tentativa falhará porque System.Windows.Controls.dll não estiver no pacote de aplicativos.

Este é o ponto em que muitos programadores lançar backup de suas mãos e um concluir que demanda assembly carregar não trabalhar no Silverlight ou recorrer à reflexão para instanciar o tipo de calendário:

AssemblyPart part = new AssemblyPart();
Assembly a = part.Load(e.Result);
Object cal = (Object)a.CreateInstance("Calendar");

Essa abordagem funciona, mas é trabalhosa. Você não pode converter a referência retornada pelo Assembly.Create­Instance para um calendário porque isso poderia causar o compilador JIT tentar carregar o assembly antes que o método executado. E se você não pode converter para o calendário, em seguida, métodos do controle, propriedades e eventos ter a serem acessados por meio de reflexão, muito. O código rapidamente aumenta tão complicado que é tentador apenas fornecer e incorporar System.Windows.Controls.dll no pacote do aplicativo e ao vivo com o maior tamanho XAP.

A boa notícia é que você pode combinar assembly dinâmico carregar e digitação de alta segurança. Simplesmente reestruturar seu código ao longo de linhas de Figura 5 . Observe que wc_OpenReadCompleted não referencia o tipo de calendário; todas as referências foram movidas para um método separado chamado CreateCalendar. Além disso, CreateCalendar é atribuído de tal forma que o compilador JIT não tentará in-line o método. (Se inlining fosse ocorrer, você ficaria direito novamente onde você iniciou porque wc_OpenReadCompleted deve conter uma referência implícita ao tipo de calendário.) Agora o compilador JIT não verifique se System.Windows.Controls.dll foi carregado até CreateCalendar é chamado e, por esse tempo, você já tiver carregado-lo no appdomain.

A Figura 5 por demanda assembly ao carregar que funciona

private void CreateCalendarButton_Click(object sender, RoutedEventArgs e)
{
    WebClient wc = new WebClient();
    wc.OpenReadCompleted +=
        new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
    wc.OpenReadAsync(new Uri("System.Windows.Controls.dll",
        UriKind.Relative));
}

void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    if (e.Error == null)
    {
        // Load the downloaded assembly
        AssemblyPart part = new AssemblyPart();
        part.Load(e.Result);

        // Create a Calendar control
        CreateCalendar();
    }
}

[MethodImpl(MethodImplOptions.NoInlining)]
private void CreateCalendar()
{
    Calendar cal = new Calendar();
    cal.Width = 300.0;
    cal.Height = 200.0;
    cal.SelectedDatesChanged += new
        EventHandler<SelectionChangedEventArgs>(cal_SelectedDatesChanged);
    LayoutRoot.Children.RemoveAt(0);
    LayoutRoot.Children.Add(cal);
}

Idéias: processamento e o thread da interface do usuário

Observe que Jeff disse que um aspecto do Silverlight que não obter muita atenção é o fato de que todos os processamento no Silverlight é feito no segmento de interface do usuário do aplicativo e se você hog o segmento de interface do usuário, você impedir que qualquer processamento aconteça. Como o WPF fornece um segmento de processamento, é provável que o Silverlight não surpreendente. Talvez seja interesse saber por que.

A decisão veio para baixo uma compensação entre sobrecarga do sistema e separação framerates. Com o Silverlight, aconteceu com uma espessura mais claras no thread abordagem e não isolar o código do aplicativo do sistema de processamento. Isso significa que você pode fazer mais em sua animação (como ter animação de layout-com base ou código personalizado em execução) e há latência mínima e obter o sistema de processamento de sobrecarga. O lado para baixo é que se você fizer muito, você pode interferir com operações como a reprodução de vídeo.

Dito isso, o sistema de processamento do Silverlight irá se beneficiar de processamento de vários núcleos e usar muitos segmentos para acelerar o processamento para ele. Portanto, o processamento é raramente "no segmento", mas ela é sincronizada com o seu aplicativo para evitar a sincronização e cópias de dados.

—Ashraf Michail, arquiteto de segurança, o Silverlight

Por acaso, se fosse Windows Presentation Foundation (WPF) em vez de Silverlight, você pode resolver o problema de maneira mais elegante registrando um manipulador para eventos de AppDomain.AssemblyResolve e carregar System.Windows.Controls.dll existe. AppDomain.AssemblyResolve existe no Silverlight, mas ele é atribuído SecurityCritical, que significa o código do usuário não é possível registrar manipuladores para ele.

a Figura 5 pressupõe que você incluiu no seu projeto uma referência a System.Windows.Controls.dll, mas que você defina Copy local como false (veja a Figura 2 ) e implantou o assembly na pasta ClientBin. Para provar que ele funciona, baixar o aplicativo OnDemandAssemblyDemo que acompanha esta coluna e clique no botão rotulado como criar controle de calendário. Um controle de calendário aparece no lugar do botão. Significativamente, OnDemandAssemblyDemo.xap não contém uma cópia do System.Windows.Controls.dll, que você pode facilmente verificar abrir o arquivo XAP com WinZip. Mágica! Isso será um separador gelo excelente para sua próxima festa do Silverlight.

Processamento Just-in-Time

Um aspecto do Silverlight que não obter muita pressione é o fato de que todos os processamento no Silverlight é feito no segmento de interface do usuário do aplicativo e se você hog o segmento de interface do usuário, você impedir que qualquer processamento do que está sendo feito. Isso significa que você deseja evitar loops de execução demorada no thread da interface do usuário se você estiver modificando um cenário XAML nesse loop ou se qualquer animações estão ocorrendo ao mesmo tempo.

Parece simples — evitar loops de execução demorada no thread da interface do usuário, mas na prática, ele pode ter um impacto profundos no código que você escreve. Considere o aplicativo chamado OpenFileDialogDemo, ilustrado na Figura 6 . Ele demonstra como usar OpenFileDialog classe do Silverlight para permitir que o usuário procure seu disco rígido arquivos de imagem e, em seguida, carregar as imagens em objetos de imagem XAML. Executar o aplicativo, clique no botão Abrir na parte superior da página, selecione vários arquivos de imagem (quanto maior o melhor) e, clique botão abrir o OpenFileDialog.

fig06.gif

A Figura 6 OpenFileDialogDemo em ação

Você verá que um por um, as imagens você selecionado pop para a cena usando objetos criados dinamicamente com XamlReader.Load e assumir posições aleatórias na página. Depois que as imagens são exibidas, você pode clicar em-los para torná-los venha para a frente e até mesmo usar o mouse para arrastá-las ao redor da página.

Apesar de sua simplicidade aparente, OpenFileDialogDemo oferece uma lição prática no que está sendo criteriosos com o segmento de interface do usuário. Quando originalmente escrevi o código para exibir o OpenFileDialog e carregar os arquivos de imagem, estruturada-algo parecido com o trecho na Figura 7 . Depois que o usuário foi fechada a caixa de diálogo, um loop foreach simples itera através os arquivos selecionados e carrega-os um por um.

A Figura 7 abordagem simples ao carregar arquivos de imagem

OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "JPEG Files (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
    "PNG Files (*.png)|*.png|All Files (*.*)|*.*";
ofd.FilterIndex = 1;
ofd.Multiselect = true;

if ((bool)ofd.ShowDialog())
{
    foreach (FileInfo fi in ofd.Files)
    {
        using (Stream stream = fi.OpenRead())
        {
            BitmapImage bi = new BitmapImage();
            bi.SetSource(stream);
            GetNextImage().Source = bi;
        }
    }
}

Infelizmente, nenhuma das imagens apareceu na tela até que todas elas foram carregadas. O atraso não era um aumento de grandes se o usuário selecionou um ou dois arquivos de imagem, mas era intolerable se arquivos de 40 ou 50 foram selecionados. Atender Resumindo, o aplicativo não aos requisitos mínimos que defini para ele, pois queria que as imagens para "pop" na tela como se eles fossem carregados. Obtê-lo? Pop! Pop! Pop!

O problema, obviamente, era que o loop foreach é executado no thread da interface do usuário e enquanto o loop estava em execução, o Silverlight não pôde processar as imagens conforme eles foram adicionados para a cena, que significa que ele era hora voltar, faça um algo e reestruturar o código para torná-lo capaz de processar as imagens em tempo hábil.

a Figura 8 mostra uma solução para o problema. O foreach modificado loop não faz nada mais de adicionar objetos de FileInfo a um System.Collections.Generic.Queue. Isso permite que o loop executar rapidamente e mão controle novamente para o Silverlight para que ele possa obter para baixo para os negócios de processamento. Talvez o aspecto mais interessante do código restructured é como ele dequeues e processa os objetos FileInfo em resposta a eventos de CompositionTarget.Rendering.

Figura 8 A melhor forma de carregar arquivos de imagem

private Queue<FileInfo> _files = new Queue<FileInfo>();
 ...
public Page()
{
    InitializeComponent();

    // Register a handler for Rendering events
    CompositionTarget.Rendering +=
        new EventHandler(CompositionTarget_Rendering);
}
 ...
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "JPEG Files (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
    "PNG Files (*.png)|*.png|All Files (*.*)|*.*";
ofd.FilterIndex = 1;
ofd.Multiselect = true;

if ((bool)ofd.ShowDialog())
{
    // Reset the queue
    _files.Clear();

    // Place each FileInfo in a queue
    foreach (FileInfo fi in ofd.Files)
    {
        _files.Enqueue(fi);
    }
}
 ...
private void CompositionTarget_Rendering(Object sender, EventArgs e)
{
    if (_files.Count != 0)
    {
        FileInfo fi = _files.Dequeue();
        using (Stream stream = fi.OpenRead())
        {
            BitmapImage bi = new BitmapImage();
            bi.SetSource(stream);
            GetNextImage().Source = bi;
        }
    }
}

CompositionTarget.Rendering é retorno de um por quadro processamento chamada tradicionalmente usado para implementar loops de jogos. Ele foi emprestado do WPF e aparecia mais tarde no ciclo de desenvolvimento 2 do Silverlight. O evento é acionado sempre que o Silverlight estiver pronto para re-render a cena.

OpenFileDialogDemo registra um manipulador para eventos de Composition­Target.Rendering (CompositionTarget_Rendering) e dequeues um objeto FileInfo, convertê-lo em uma imagem XAML, cada vez que o manipulador é chamado. O resultado? As imagens pop na tela como eles são carregados porque o Silverlight agora tem a oportunidade de atualizar a cena após a adição de cada nova imagem. Isso é como o loop foreach na versão final do OpenFileDialogDemo é estruturado, e é por isso quando você executou a ele, você viu imagens que aparecem na tela um por um em vez de todas ao mesmo tempo.

Você deseja ter cuidado para não sobrecarregar CompositionTarget.Rendering. Se OpenFileDialogDemo tivesse animações executando como ele adicionado imagens a cena, as animações provavelmente seriam falhar porque cada quadro deve ser adiado pela quantidade de tempo necessário para carregar os bits de imagem e atribuí-los a um objeto de imagem. Mas quando necessário, necessário incorretamente e OpenFileDialogDemo é um bom exemplo de um uso aceitável de CompositionTarget.Rendering—indeed, aqui o objetivo seria difíceis de realizar caso contrário.

Evitar dependências regionais

Uma dica final considera usando XamlReader.Load para criar objetos XAML dinamicamente. Você pode localizar o que há de errado com esse código?

Rectangle rect = (Rectangle)XamlReader.Load(
    String.Format(
        "<Rectangle xmlns=\"http://schemas.microsoft.com/client/2007\" " +
        "Width=\"{0}\" Height=\"{1}\" Stroke=\"Black\" Fill=\"Yellow\" />",
        100.5, 100.0
    )
); 

Se você reconhecido que esse código irá funcionar na maioria dos PCs nos Estados Unidos mas falhará a maioria dos PCs na Europa e em outras partes do mundo, forneça a seu self um pat na parte traseira. Para demonstrar, primeiro configure o sistema operacional para exibir números, moedas, datas e horas dos EUA Formate se ele não estiver configurado dessa maneira já. (No Vista, vá para a guia formato da caixa de opções regionais e de idioma diálogo acessível através do Painel de controle.) Executar a chamada para XamlReader.Load e verifique se a chamada é executado com êxito. Agora altere o formato regional para francês e executar a chamada novamente. Desta vez XamlReader.Load lança uma exceção: "valor de atributo inválido 100,5 para a propriedade width" ( Figura 9 ). O problema é que números decimais como 100.5 desenvolvidos 100,5 (observe o ponto-e-vírgula no lugar do ponto decimal) em diversos países. E como String.Format honra as configurações regionais no PC host, o decimal 100.5 se torna 100,5". Infelizmente, XamlReader.Load não sabe o que fazer de "100,5", portanto, ele lança uma exceção.

fig09.gif

A Figura 9 a exceção lançada pelo XamlReader.Load

O código a seguir mostra a maneira correta chamar XamlReader.Load para que ele funcione em qualquer computador que executa o Silverlight:

Rectangle rect = (Rectangle)XamlReader.Load(
    String.Format(
        CultureInfo.InvariantCulture,
        "<Rectangle xmlns=\"http://schemas.microsoft.com/client/2007\" " +
        "Width=\"{0}\" Height=\"{1}\" Stroke=\"Black\" Fill=\"Yellow\" />",
        100.5, 100.0
    )
);

O primeiro parâmetro passado para String.Format é um objeto CultureInfo referenciando a cultura invariável. XamlReader.Load espera seqüências invariável de cultura, portanto, o uso de CultureInfo.InvariantCulture garante que String.Format gera valores decimais corretamente formatados (bem como formatadas corretamente datas e horas) se você estiver usando os. Não coincidentally, o aplicativo OpenFileDialogDemo na seção anterior usa essa técnica para tornar-se de que seu próprio chamadas para XamlReader.Load funcionam independentemente das configurações regionais.

Se as seqüências de caracteres você passar para XamlReader.Load incluir valores decimais gerados pelo String.Format, sempre use CultureInfo.InvariantCulture para obter adequado formatação. Depois de seu aplicativo é executado em atividade, é responsável executar em uma variedade de configurações regionais.

Ativar uma nova página

Se você estiver começando a usar o Silverlight e você um desenvolvedor de .NET experiente, você já sabe 90 % do que você precisa saber. Mas Silverlight exibe diferenças para .NET que você deve entender.

Falando de páginas, muitos leitores solicitaram pretende atualizar o Estrutura da página-ativar Silverlight 1.0apresentadas em maio de 2008 para o Silverlight 2. Bem, a portagem foi concluída. Você pode exibir um aplicativo de exemplo que usa a estrutura atualizada em Wintellect.com/Silverlight/pageturndemo/, e você pode baixar o código-fonte de Wintellect.com/Downloads/PageTurnDemo2.zip.

A estrutura da página-por sua vez reside em PageTurn.cs, e o código no Page.xaml.cs ilustra como todos os funciona. A API é semelhante para a versão Silverlight 1.0 (em C# em vez de JavaScript), e fiz alterações para tornar página ativando melhor. Incluí um evento PageTurned que é acionado pela estrutura toda vez que uma vez de página for concluída para que você pode atualizar a interface do usuário, digamos, para mostrar o número da página atual.

Envie suas dúvidas e comentários para Jeff para Wicked@Microsoft.com.

Jeff Prosise é editor colaborador da MSDN Magazine e autor de vários livros, incluindo Programming Microsoft .NET (Microsoft Press, 2002). Ele também é cofounder da Wintellect ( Wintellect.com), uma consultoria e treinamento de software confirmar que é especializada em Microsoft. NET. Contato Jeff em Wicked@Microsoft.com.