Compartilhar via


Capítulo 2: Criando um aplicativo "Longhorn"

 

Introdução
Capítulo 1: o modelo de aplicativo "Longhorn"

Capítulo 2: Criando um aplicativo "Longhorn"

Refatoração Brent
Wise Owl Consulting

Novembro de 2003

Sumário

O Mecanismo de Build do Microsoft .NET: MSBuild.exe
Criando Olá, Mundo usando o MSBuild
Terminologia do MSBuild
Criando um aplicativo executável Longhorn
Criando um assembly da biblioteca Longhorn
Criando um documento longhorn
Um arquivo XAML como uma declaração de classe
O manifesto do aplicativo
O manifesto de implantação
Executando o aplicativo
Por que criar mais um sistema de build?
Resumo

Para criar um aplicativo Longhorn, você precisa do SDK (Longhorn Software Development Kit) instalado. Como alternativa, você pode instalar uma versão do Microsoft® Visual Studio® que dá suporte ao Longhorn. Neste livro, não discuto o uso do Visual Studio porque seus assistentes, recursos de geração de código sofisticados e recursos de build de projeto obscurecem o que realmente acontece nas capas. Acredito que você deve entender o que uma ferramenta faz para você antes de confiar na ferramenta.

Quando você instala o SDK do Longhorn, ele cria um conjunto de itens de menu Iniciar que você pode usar para criar uma sessão de prompt de comando na qual você pode criar aplicativos Longhorn. Para criar versões de depuração do aplicativo em um sistema microsoft Windows® XP de 32 bits, navegue pelos seguintes itens de menu para criar a sessão de prompt de comando apropriada:

  • Iniciar
  • Programas
  • Microsoft Longhorn SDK
  • Abrir a janela Ambiente de Build
  • Ambiente de Build do Windows XP de 32 bits
  • Definir o Ambiente de Build do Windows XP de 32 bits (Depuração)

O Mecanismo de Build do Microsoft .NET: MSBuild.exe

O MSBuild é a principal ferramenta que você usa para criar um aplicativo Longhorn. Você pode executar o MSBuild com a opção de linha de comando de ajuda para obter informações detalhadas sobre seu uso:

MSBuild /?

Quando você executa o MSBuild sem argumentos de linha de comando, conforme mostrado aqui, ele pesquisa no diretório de trabalho atual um nome de arquivo que termina com "proj", por exemplo, .proj, .csproj etc. Quando ele encontra um, ele cria o projeto de acordo com as diretivas nesse arquivo.

MSBuild

Quando você tiver mais de um arquivo de projeto no diretório, poderá especificar o arquivo de projeto apropriado na linha de comando:

MSBuild <ProjectName>.proj

Normalmente, o MSBuild cria o destino padrão no arquivo de projeto. Você pode substituir isso e especificar o destino que deseja criar. Por exemplo, para criar o destino chamado CleanBuild, você invoca o MSBuild da seguinte maneira:

MSBuild /t:Cleanbuild

Criando Olá, Mundo usando o MSBuild

Vamos examinar os arquivos necessários para criar um aplicativo de Olá, Mundo simples baseado em navegação. Posteriormente, descreverei a finalidade e o uso de cada arquivo em detalhes.

Primeiro, você precisa definir o objeto Application . Você faz isso em um arquivo normalmente chamado de arquivo de definição de aplicativo. Este arquivo HelloWorldApplication.xaml define meu objeto Application .

HelloWorldApplication.xaml

<NavigationApplication xmlns="https://schemas.microsoft.com/2003/xaml" 
                       StartupUri="HelloWorld.xaml" />

Essa definição diz: "Para meu objeto Application , quero usar uma instância da classe MSAvalon.Windows.Navigation.NavigationApplication . Na inicialização, o aplicativo deve navegar até e exibir a interface do usuário (interface do usuário) definida no arquivo HelloWorld.xaml."

Aqui estão o conteúdo do arquivo HelloWorld.xaml. É uma versão um pouco mais interessante do exemplo de Olá, Mundo anterior no Capítulo 1.

HelloWorld.xaml

<Border xmlns="https://schemas.microsoft.com/2003/xaml">
  <FlowPanel>
    <SimpleText Foreground="DarkRed" FontSize="14">Hello World!</SimpleText>   </FlowPanel>
</Border>

Agora que tenho todo o "código" para meu aplicativo Olá, Mundo simples, preciso de um arquivo de projeto que defina como criar meu aplicativo. Aqui está meu arquivo HelloWorld.proj.

HelloWorld.proj

<Project DefaultTargets="Build">
  <PropertyGroup>
    <Property Language="C#" />   
    <Property DefaultClrNameSpace="IntroLonghorn" />
    <Property TargetName="HelloWorld" />
  </PropertyGroup>

  <!--Imports the target which contains all the common targets-->
  <Import Project="$(LAPI)\WindowsApplication.target" />

  <ItemGroup>
    <!-- Application markup -->
    <Item Type="ApplicationDefinition" Include="HelloWorldApplication.xaml" />
   
    <!-- Compiled Xaml Files list -->
    <Item Type="Pages" Include="HelloWorld.xaml"/>      
  </ItemGroup>
</Project>

Coloque esses três arquivos em um diretório. Abra um prompt de comando do SDK do Longhorn, navegue até o diretório que contém seus arquivos e execute o MSBuild. Ele compilará seu programa em um executável.

Examinaremos o conteúdo do arquivo de definição de aplicativo mais adiante neste capítulo. No Capítulo 3, descrevo detalhadamente muitos dos elementos XAML (Extensible Application Markup Language) que você pode usar para definir uma interface do usuário. Antes de examinarmos o arquivo de projeto mais detalhadamente, vamos examinar algumas terminologias do MSBuild.

Terminologia do MSBuild

Vamos estabelecer definições para alguns elementos do MSBuild. Uma Propriedade é um par chave-valor. O valor de uma Propriedade pode se originar de uma variável de ambiente, de um comutador de linha de comando ou de uma definição de propriedade em um arquivo de projeto, conforme mostrado aqui:

<Property OutputDir="bin\" />

Você pode pensar em um Item como uma matriz de arquivos. Um Item pode conter curingas e pode excluir arquivos específicos. O MSBuild usa o atributo Type de um Item para categorizar itens, conforme mostrado aqui:

<Item Type="Compile" Include="*.cs" Exclude="DebugStuff.cs" />

Uma Tarefa é uma unidade atômica no processo de build. Uma Tarefa pode aceitar parâmetros de entrada de Elementos de propriedade , elementos item ou cadeias de caracteres simples. O Nome de uma Tarefa identifica o tipo de dados .NET de build necessário para executar a Tarefa. Uma Tarefa pode emitir Items que outras Tarefasconsomem. O MSBuild inclui muitas tarefas, todas as quais podem ser amplamente categorizadas como

  • Tarefas da ferramenta .NET
  • Tarefas de implantação
  • Tarefas do Shell

Por exemplo, the Task with a Name of Csc invokes the C# compiler as the build tool, which compiles all Item elements specified in the Sources attribute (which specifies Item elements with a Type of Compile) into an assembly, and produces the assembly as an output Item.

<Task Name="Csc" AssemblyName="$(OutputDir)\HelloWorld.exe"
                 Sources="@(Compile)" />

Um Destino é uma única etapa lógica no processo de build. Um Destino pode executar a análise de carimbo de data/hora. Isso significa que um Destino não será executado se não for necessário. Um Destino executa uma ou mais Tarefaspara executar as operações desejadas, conforme mostrado aqui:

<Target Name="CopyToServer"
        Inputs="$(OutputDir)\HelloWorld.exe"
        Outputs="\\DeployServer\$(BuildNum)\HelloWorld.exe"
        DependsOnTargets="Compile">

  <Task Name="Copy" ... />
</Target>

Um atributo Condition é aproximadamente equivalente a uma instrução se simples. Uma Condição pode comparar duas cadeias de caracteres ou marcar para a existência de um arquivo ou diretório. Você pode aplicar uma Condição a qualquer elemento em um arquivo de projeto. Por exemplo, aqui está um grupo de propriedades que são definidas somente quando a propriedade Configuration tem o valor Depurar:

<PropertyGroup Condition=" '$(Configuration)'=='Debug' " >
    <Property ... />
    <Property ... />
</PropertyGroup>

Uma importação é aproximadamente equivalente a uma instrução #include C/C++, conforme mostrado no exemplo a seguir. Quando você importa um projeto, o conteúdo do projeto importado logicamente se torna parte do projeto de importação.

<Import Project="$(LAPI)\WindowsApplication.target" />

Agora que a terminologia está fora do caminho, vamos examinar um arquivo de projeto típico.

Criando um aplicativo executável Longhorn

Aqui está um arquivo de projeto simples, mas relativamente abrangente, que cria um aplicativo Longhorn executável:

<Project DefaultTargets="Build">
  <PropertyGroup>
    <Property Language="C#" />
    <Property DefaultClrNameSpace="IntroLonghorn" />
    <Property TargetName="MyApp" />
  </PropertyGroup>

  <Import Project="$(LAPI)\WindowsApplication.target" />

  <ItemGroup>
    <Item Type="ApplicationDefinition" Include="MyApp.xaml" />

    <Item Type="Pages" Include="HomePage.xaml" />
    <Item Type="Pages" Include="DetailPage.xaml" />
    <Item Type="Code" Include="DetailPage.xaml.cs"/>

    <Item Type="DependentProjects" Include="MyDependentAssembly.proj" /> 

    <Item Type="Components" Include="SomeThirdParty.dll" />

    <Item Type="Resources" Include="Picture1.jpg"
          FileStorage="embedded" Localizable="False"/>
    <Item Type="Resources" Include="Picture2.jpg"
          FileStorage="embedded" Localizable="True"/>
  </ItemGroup>
</Project>

O elemento Project

Todos os arquivos de projeto começam com uma definição de elemento raiz chamada Project. Seu atributo DefaultTargets especifica os nomes dos destinos que o sistema deve criar quando você não especificar um destino de outra forma. Neste exemplo, especifiquei que, por padrão, o sistema deve criar o destino chamado Build.

Os elementos PropertyGroup e Property

As regras de build podem ser executadas condicionalmente com base em valores de propriedade. Conforme mencionado, o valor de uma propriedade pode ser originado de uma variável de ambiente, de um comutador de linha de comando do MSBuild ou de uma definição de propriedade em um arquivo de projeto.

Um projeto para um aplicativo deve especificar, no mínimo, um valor para as propriedades Language e TargetName . Neste exemplo, especifiquei que o idioma é C# e que o nome do aplicativo resultante deve ser MyApp. Também atribuí um valor à propriedade chamada DefaultClrNameSpace.

O sistema de build compila cada arquivo XAML em uma definição de classe gerenciada. Por padrão, a classe gerenciada terá o mesmo nome que o nome do arquivo base do arquivo de origem XAML. Por exemplo, o arquivo Markup.xaml é compilado em uma definição de uma classe chamada Markup. Ao definir a propriedade DefaultClrNameSpace como IntroLonghorn, peço ao sistema de build que prefixe os nomes de classe gerados com o namespace IntroLonghorn . Por isso, o sistema de build produz uma classe chamada IntroLonghorn.Markup para a definição markup.xaml.

Defini minhas propriedades antes de importar outros projetos, portanto, as regras nos projetos importados usarão meus valores de propriedade especificados, por exemplo, obterei as regras de build adequadas para aplicativos C# porque defino a propriedade Language como C#.

O elemento Import

As regras no destino Build produzem o arquivo executável do meu aplicativo Longhorn. Especificar essas regras de build em cada arquivo de projeto seria entediante e repetitivo. Portanto, um pouco mais tarde no arquivo de projeto, uso a seguinte definição para importar um arquivo de projeto predefinido chamado WindowsApplication.target:

  <Import Project="$(LAPI)\WindowsApplication.target" />

Esse arquivo importado contém as regras de build padrão para a criação de um aplicativo do Windows e define (indiretamente) o destino chamado Build.

Os elementos ItemGroup e Item

O elemento ItemGroup e seus elementos Item filho definem todas as partes necessárias para compilar o aplicativo.

Você deve ter um Item com um Tipo de ApplicationDefinition, conforme mostrado aqui. Este Item especifica o arquivo que descreve o objeto Application a ser usado para seu aplicativo. O objeto Application normalmente é uma instância da classe MSAvalon.Windows.Application ou da classe MSAvalon.Windows.Navigation.NavigationApplication , que descrevo posteriormente neste capítulo.

<Item Type="ApplicationDefinition" Include="MyApp.xaml" />

Cada Item com um Tipo de Páginas define um conjunto de arquivos XAML, conforme mostrado aqui. O sistema de build compila essas definições XAML em classes que ele inclui no assembly resultante.

<Item Type="Pages" Include="HomePage.xaml" />
<Item Type="Pages" Include="DetailPage.xaml" />

Cada Item com um Tipo de Código representa um arquivo de origem, conforme mostrado aqui. O sistema de build compila esses arquivos de origem usando o compilador apropriado selecionado pela propriedade Language do projeto.

<Item Type="Code" Include="DetailPage.xaml.cs"/>

Esse projeto pode depender de outros projetos. O sistema de build deve compilar esses projetos dependentes antes de poder compilar esse projeto. Você lista cada projeto dependente usando um Item com Tipo de DependentProjects:

<Item Type="DependentProjects" Include="MyDependentAssembly.proj" /> 

O código neste projeto pode usar tipos em um assembly predefinido, também conhecido como assembly de componente. Para compilar o código usando esses assemblies de componente, o compilador precisa de uma referência para cada assembly. Além disso, ao implantar seu aplicativo, você também precisará implantar esses assemblies de componentes. Você lista cada assembly de componente usando um Item com Tipo de Componentes:

<Item Type="Components" Include="SomeThirdParty.dll" />

Um assembly referenciado é um pouco diferente de um assembly de componente. Em ambos os casos, seu código usa tipos em um assembly predefinido. No entanto, você não envia um assembly referenciado como parte do aplicativo, enquanto envia um assembly de componente como parte do aplicativo. O sistema de build precisa saber essa distinção.

Especifique um Item com um Tipo de Referências para indicar que o compilador deve referenciar o assembly especificado no momento da compilação, conforme mostrado aqui, mas o assembly não fará parte da implantação do aplicativo. O sistema de build inclui automaticamente referências a assemblies de sistema padrão, por exemplo, mscorlib.dll, System.dll PresentationFramework.dll. e muito mais, mas você precisará adicionar qualquer assembly não padrão que seu aplicativo precise referenciar.

<Item Type="References" Include="SharedThirdParty.dll" />

Seu aplicativo também pode usar recursos. Um Item com um Tipo de Recursos descreve um recurso usado pelo aplicativo, conforme mostrado aqui. O sistema de build pode inserir o recurso no assembly resultante ou incluí-lo como um arquivo autônomo. O sistema de build também pode colocar recursos localizáveis em assemblies satélites.

<Item Type="Resources" Include="Picture1.jpg"
      FileStorage="embedded" Localizable="False"/>
<Item Type="Resources" Include="Picture2.jpg"
      FileStorage="embedded" Localizable="True"/>

Criando um assembly da biblioteca Longhorn

Você também desejará criar bibliotecas além de aplicativos executáveis. As principais diferenças entre um projeto de aplicativo e um projeto de biblioteca são estas:

  • Um projeto de biblioteca define o valor da propriedade TargetType como Library.
  • Um projeto de biblioteca normalmente não inclui um item de definição de aplicativo.

Aqui está um exemplo de um arquivo de projeto que cria uma biblioteca:

<Project DefaultTargets="Build">
  <PropertyGroup>
    <Property Language="C#" />
    <Property DefaultClrNameSpace="IntroLonghorn" />
    <Property TargetName="MyLibrary" />
    <Property TargetType="Library" />
  </PropertyGroup>

  <Import Project="$(LAPI)\WindowsApplication.target" />

  <ItemGroup>
    <Item Type="Pages" Include="ErrorPage.xaml" />
    <Item Type="Code" Include="ErrorPage.xaml.cs"/>
    <Item Type="Code" Include="Utilities.cs"/>

    <Item Type="DependentProjects" Include="MyDependentAssembly.proj" /> 

    <Item Type="Components" Include="SomeThirdParty.dll" />

    <Item Type="Resources" Include="Picture1.jpg"
          FileStorage="embedded" Localizable="False"/>
    <Item Type="Resources" Include="Picture2.jpg"
          FileStorage="embedded" Localizable="True"/>
  </ItemGroup>
</Project>

Criando um documento longhorn

Você não está restrito à criação de aplicativos com XAML. Você também pode usar arquivos XAML para criar um documento adaptável altamente interativo, renderizado de forma inteligente para um usuário ler. Nesse caso, seus arquivos XAML representam coletivamente páginas de um documento. Você pode usar o mecanismo do MSBuild para criar esses documentos.

As alterações no arquivo de projeto para criar um documento em vez de um aplicativo são secundárias:

  • Defina o valor da propriedade TargetType como Document.
  • Importe o projeto WindowsDocument.target para as regras de build apropriadas.
  • Inclua todos os outros arquivos de projeto como de costume.

É importante entender o que um TargetType of Document realmente produz. Quando você cria um Documento, a saída de build é um arquivo .container e o sistema de build otimiza o conteúdo do contêiner para download em vez de velocidade. Um arquivo de contêiner é uma extensão do formato Armazenamento Estruturado do Windows, também conhecido como DocFile. A manipulação de contêiner longhorn fornece recursos que permitem a renderização de arquivos parcialmente baixados. Portanto, você não precisa que todo o contêiner seja baixado antes que o aplicativo comece a ser executado.

Além disso, quando você solicita ao MSBuild para criar um arquivo de contêiner, ele compila cada arquivo XAML em uma representação binária do XML, chamada BAML (XAML binário). BAML é muito mais compacto do que o arquivo de texto original ou um assembly compilado para IL. Os arquivos BAML são baixados mais rapidamente — são otimizados para download — mas um interpretador deve analisá-los em tempo de execução para criar instâncias das classes descritas no arquivo. Portanto, esses arquivos não são otimizados para velocidade. Até agora, tenho gerado arquivos compilados para IL (também conhecidos como arquivos CAML, abreviação de XAML compilado).

Aqui está um exemplo de um arquivo de projeto que cria um documento eletrônico:

<Project DefaultTargets="Build">
  <PropertyGroup>
    <Property TargetType="Document" />
      <Property Language="C#" />
      <Property DefaultClrNameSpace="IntroLonghorn" />
      <Property TargetName="MyDocument" />
  </PropertyGroup>
    
  <Import Project="$(LAPI)\WindowsDocument.target" />

  <ItemGroup>
    <Item Type="ApplicationDefinition" Include="MyApp.xaml" />

    <Item Type="Pages" Include="Markup.xaml" />
    <Item Type="Pages" Include="Navigate.xaml" />
    <Item Type="Code" Include="Navigate.xaml.cs"/>

    <Item Type="Resources" Include="Picture1.jpg"
          FileStorage="embedded" Localizable="False"/>
    <Item Type="Resources" Include="Picture2.jpg"
          FileStorage="embedded" Localizable="True"/>
  </ItemGroup>
</Project>

Agora que você aprendeu a criar os vários tipos de aplicativos e componentes do Longhorn, vamos examinar mais detalhadamente os arquivos XAML. Especificamente, vamos examinar o que o sistema de build faz quando transforma um arquivo XAML em uma classe .NET.

Um arquivo XAML como uma declaração de classe

O arquivo de definição de aplicativo é o arquivo XAML que define a classe do objeto Application do aplicativo. No entanto, em geral, um documento XAML é simplesmente um arquivo que define uma classe. A classe produzida pela definição XAML deriva da classe associada ao nome do elemento raiz do documento XML. Por padrão, o sistema de build usa o nome do arquivo base XAML como o nome de classe gerado.

Criando um arquivo de definição de aplicativo para um aplicativo de navegação

Lembre-se de que o elemento Item com Type of ApplicationDefinition especifica o nome do arquivo XAML que define o objeto Application . Em outras palavras, esse elemento especifica o arquivo XAML que contém o ponto de entrada para seu aplicativo. A plataforma Longhorn criará uma instância do tipo derivado de MSAvalon.Windows.Application que você define nesse arquivo e permitirá que ele gerencie a inicialização, o desligamento e a navegação do seu aplicativo.

No Capítulo 1, você viu como criar e usar uma instância de aplicativo programaticamente. O seguinte arquivo XAML usa marcação para definir o objeto Application para um projeto:

<NavigationApplication xmlns="https://schemas.microsoft.com/2003/xaml" 
                       StartupUri="HelloWorld.xaml" />

Espero que a maioria dos aplicativos Longhorn sejam aplicativos baseados em navegação e, portanto, geralmente reutilizarão o objeto NavigationApplication padrão. Você pode reutilizar esse arquivo de definição de aplicativo para a maioria dos aplicativos baseados em navegação alterando apenas o valor do atributo StartupUri .

Por exemplo, se a definição de aplicativo anterior residir no arquivo HelloWorldApplication.xaml e eu usar o arquivo de projeto HelloWorld.proj listado anteriormente, o sistema de build produzirá a seguinte declaração de classe:

namespace IntroLonghorn {
  class HelloWorldApplication :
           MSAvalon.Windows.Navigation.NavigationApplication {
.
.
.
   }
 }

O namespace resulta da declaração DefaultClrNameSpace no arquivo de projeto, o nome da classe declarada é o mesmo que o nome do arquivo base e a classe declarada estende a classe representada pelo elemento raiz no arquivo XAML.

Personalizando o código gerado usando atributos

Ao declarar um elemento raiz em um arquivo XAML, você pode usar atributos no elemento raiz para controlar o nome da declaração de classe gerada. Você pode usar qualquer um dos seguintes atributos opcionais:

  • Uma definição de prefixo de namespace que associa um prefixo a um namespace chamado Definition. Você deve definir um prefixo para esse namespace para usar os atributos Language e Class . Tradicionalmente, o prefixo def é usado.
  • Um atributo Language (definido no namespace Definition ) que especifica a linguagem de programação usada por qualquer código embutido no arquivo XAML.
  • Um atributo Class (definido no namespace Definition ) que especifica o nome da classe gerada. Quando você especifica um nome que contém um ou mais períodos, o sistema de build não prefixa o nome com o valor DefaultClrNameSpace .

Por exemplo, vamos alterar o conteúdo do arquivo HelloWorldApplication.xaml para o seguinte:

<NavigationApplication xmlns="https://schemas.microsoft.com/2003/xaml"
                       xmlns:def="Definition"
                       def:Class="Special.MyApp"
                       def:CodeBehind="HelloWorldApplication.xaml.cs" 
                       StartupUri="HelloWorld.xaml" />

A classe gerada seria:

namespace Special {
  class MyApp :
           MSAvalon.Windows.Navigation.NavigationApplication {
.
.
.
  }
}

Usando código e marcação na mesma classe

Quase todos os aplicativos exigirão que você escreva algum código, por exemplo, um manipulador de eventos de clique para um botão ou uma substituição de método virtual, além da marcação que especifica a interface do usuário. Lembre-se do Capítulo 1 de que meu aplicativo não baseado em navegação substituiu o método OnStartingUp para criar sua janela e controles. Reescreverei esse exemplo para ilustrar como você combinaria o código do aplicativo e a marcação.

Embora este próximo exemplo crie um aplicativo que não seja de navegação, quero enfatizar que não há realmente nenhum motivo convincente para criar esse aplicativo. Você sempre pode criar um aplicativo baseado em navegação que nunca realmente navega para uma página diferente. No entanto, escrever esse aplicativo exige que eu misture código e marcação na mesma classe, portanto, fornece um bom exemplo.

Lembre-se de que a criação de um aplicativo que não seja de navegação exige que você defina uma classe personalizada herdada de MSAvalon.Windows.Application e que substitua o método OnStartingUp . O arquivo de definição de aplicativo declara a classe de objeto do aplicativo que seu programa usa. Portanto, um aplicativo de não navegação deve definir seu método OnStartingUp de substituição na mesma classe.

Exceto pelas seguintes alterações, um arquivo de configuração de aplicativo para um aplicativo sem navegação contém os mesmos itens que um arquivo de definição para um aplicativo de navegação:

  • O elemento raiz é Application em vez de NavigationApplication.
  • O arquivo deve conter ou referenciar a implementação do método OnStartingUp para sua classe de aplicativo.

Como preciso usar marcação e código para implementar uma única classe, preciso mostrar uma técnica para associar um arquivo de código-fonte a um arquivo XAML.

Associando um arquivo Source-Behind a um arquivo XAML

Com frequência, você desejará desenvolver partes do seu aplicativo usando marcação e desenvolver outras partes usando uma linguagem de programação mais tradicional. É altamente recomendável separar a interface do usuário e a lógica em arquivos de origem individuais usando a técnica a seguir.

Você pode adicionar um elemento CodeBehind XAML (definido no namespace Definition ) ao elemento raiz de qualquer arquivo XAML e especificar o nome de um arquivo de código-fonte (também conhecido como arquivo code-behind). O mecanismo de build compilará as declarações XAML em uma classe gerenciada. O sistema de build também compila o arquivo code-behind em uma declaração de classe gerenciada. O aspecto complicado é que ambas as declarações de classe representam declarações parciais de uma única classe.

Aqui está uma definição XAML que produz uma classe de aplicativo que não é de navegação equivalente ao primeiro exemplo no Capítulo 1:

<Application xmlns="https://schemas.microsoft.com/2003/xaml"
             xmlns:def="Definition"
             def:Language="C#"
             def:Class="IntroLonghorn.CodeBehindSample"
             def:CodeBehind="CodeBehind.xaml.cs" />

Há dois aspectos notáveis nesse arquivo de definição de aplicativo:

  • O atributo Language especifica que o arquivo code-behind contém o código-fonte C#.
  • O atributo CodeBehind especifica que o nome do arquivo de origem é CodeBehindMySample2.xaml.cs.

Aqui está o arquivo source-behind correspondente:

namespace IntroLonghorn {
  using System;
  using MSAvalon.Windows;
  using MSAvalon.Windows.Controls;
  using MSAvalon.Windows.Media;

  public partial class CodeBehindSample {
    MSAvalon.Windows.Controls.SimpleText txtElement;
    MSAvalon.Windows.Window              mainWindow;

    protected override
    void OnStartingUp (StartingUpCancelEventArgs e) {
      base.OnStartingUp (e);
      CreateAndShowMainWindow ();
    }

    private void CreateAndShowMainWindow () {
      // Create the application's main window
      mainWindow = new MSAvalon.Windows.Window ();

      // Add a dark red, 14 point, "Hello World!" text element
      txtElement = new MSAvalon.Windows.Controls.SimpleText ();
      txtElement.Text = "Hello World!";
      txtElement.Foreground = new
       MSAvalon.Windows.Media.SolidColorBrush (Colors.DarkRed);
      txtElement.FontSize = new FontSize (14,
                                          FontSizeType.Point);
      mainWindow.Children.Add (txtElement);
      mainWindow.Show ();
    }
  }
}

Observe a palavra-chave parcial na declaração de classe no arquivo code-behind. Esse palavra-chave afirma que o compilador deve mesclar essa definição de classe com outras definições da mesma classe. Isso permite que você forneça várias definições parciais de uma classe, cada uma em um arquivo de origem separado, que o compilador combina em uma única definição de classe no assembly resultante.

Combinando código-fonte e marcação em um único arquivo XAML

Acho que é errado misturar código-fonte e marcação no mesmo arquivo. Até considerei não mostrar como fazer isso. No entanto, algum malfeitor em algum lugar escreverá um programa de exemplo usando essa técnica, então talvez seja necessário entender o que ele fez. Além disso, você pode usar a abordagem de arquivo code-behind descrita anteriormente para livrar o mundo de uma pequena quantidade de maldade e separar a interface do usuário da lógica.

Aqui está um arquivo de definição de aplicativo com o código-fonte inserido diretamente embutido com a marcação :

<Application xmlns="https://schemas.microsoft.com/2003/xaml"
    xmlns:def="Definition"
    def:Language="C#"
    def:Class="IntroLonghorn.MySample2" >

  <def:Code>
  <![CDATA[
    protected override void OnStartingUp (StartingUpCancelEventArgs e) {
      base.OnStartingUp (e);
      CreateAndShowMainWindow ();
    }
    . . . Remaining methods elided for clarity
  ]]>
  </def:Code>
</Application>

Neste exemplo, o atributo Language especifica que o código-fonte embutido é C#. Observe que o elemento Code é um bloco CDATA que contém o código-fonte embutido. Às vezes, é tecnicamente necessário colocar o código-fonte embutido em um bloco CDATA XML para garantir que o documento esteja bem formado. Na verdade, o analisador XAML sempre exige que você coloque o código-fonte embutido em um bloco CDATA, mesmo ao omiti-lo produz um documento bem formado.

Peço desculpas mais uma vez por te mostrar uma farsa.

O manifesto do aplicativo

Quando você compila um aplicativo, o MSBuild produz o arquivo .exe mais dois arquivos de manifesto: o manifesto do aplicativo, *.manifest e um manifesto de implantação, *.deploy. Você usa esses manifestos ao implantar um aplicativo ou documento de um servidor. Primeiro, copie o aplicativo, todas as suas dependências e os dois arquivos de manifesto para o local apropriado no servidor. Em segundo lugar, edite o manifesto de implantação para que ele aponte para o local do manifesto do aplicativo.

Para fins de integridade, vamos examinar exemplos dos manifestos de aplicativo e implantação. O manifesto do aplicativo, mostrado no exemplo a seguir, na verdade não é tão interessante quanto o manifesto de implantação. O manifesto do aplicativo simplesmente define todas as partes que compõem um aplicativo. O MSBuild produz o manifesto do aplicativo quando ele compila seu aplicativo e você normalmente modifica pouco ou nada nele.

HelloWorld.manifest

<?xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"
          xmlns:asmv2="urn:schemas-microsoft-com:asm.v2"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd">

  <assemblyIdentity name="HelloWorld" version="1.0.0.0"
                    processorArchitecture="x86" asmv2:culture="en-us"
                    publicKeyToken="0000000000000000" />

  <entryPoint name="main" xmlns="urn:schemas-microsoft-com:asm.v2"
              dependencyName="HelloWorld">

    <commandLine file="HelloWorld.exe" parameters="" />
  </entryPoint>

  <TrustInfo xmlns="urn:schemas-microsoft-com:asm.v2" xmlns:temp="temporary">
    <Security>
      <ApplicationRequestMinimum>
        <PermissionSet class="System.Security.PermissionSet" version="1" 
                       ID="SeeDefinition">
          <IPermission 
            class="System.Security.Permissions.FileDialogPermission"
            version="1" Unrestricted="true" />
          <IPermission 
            class="System.Security.Permissions.IsolatedStorageFilePermission" 
            version="1" Allowed="DomainIsolationByUser" UserQuota="5242880" />
          <IPermission
            class="System.Security.Permissions.SecurityPermission"
            version="1" Flags="Execution" />
          <IPermission
            class="System.Security.Permissions.UIPermission" version="1"
            Window="SafeTopLevelWindows" Clipboard="OwnClipboard" />
          <IPermission
            class="System.Security.Permissions.PrintingPermission"
            version="1" Level="SafePrinting" />
          <IPermission
            class="MSAvalon.Windows.AVTempUIPermission, PresentationFramework,
                   Version=6.0.4030.0, Culture=neutral,
                   PublicKeyToken=a29c01bbd4e39ac5" version="1"
                   NewWindow="LaunchNewWindows" FullScreen="SafeFullScreen" />
        </PermissionSet>

        <AssemblyRequest name="HelloWorld"
                         PermissionSetReference="SeeDefinition" />
      </ApplicationRequestMinimum>
    </Security>
  </TrustInfo>

  <dependency asmv2:name="HelloWorld">
    <dependentAssembly>
      <assemblyIdentity name="HelloWorld" version="0.0.0.0"
                        processorArchitecture="x86" />
    </dependentAssembly>

    <asmv2:installFrom codebase="HelloWorld.exe"
                       hash="5c58153494c16296d9cab877136c3f106785bfab" 
                       hashalg="SHA1" size="5632" />
  </dependency>
</assembly>

A maior parte do conteúdo do manifesto do aplicativo deve ser relativamente óbvia. O elemento entryPoint especifica o nome do método de ponto de entrada, main e faz referência à dependência, chamada HelloWorld, que contém o ponto de entrada. O elemento entryPoint também contém o nome do programa e o argumento de linha de comando de que o shell precisará para executar o aplicativo.

O elemento de dependênciaHelloWorld contém as informações (o elemento dependentAssembly) que especifica o assembly dependente e um elemento installFrom que informa ao carregador onde localizar o arquivo do assembly e o hash original do arquivo. O carregador pode usar o hash para detectar alterações feitas no assembly subsequentes à compilação.

O Gerenciador de Confiança Longhorn usa o elemento TrustInfo para determinar as permissões de segurança que o aplicativo requer. No exemplo anterior, meu aplicativo HelloWorld define um conjunto de permissões que ele nomeia como o conjunto de permissões SeeDefinition . Imediatamente após definir o conjunto de permissões, o elemento AssemblyRequest solicita que o assembly chamado HelloWorld receba pelo menos o conjunto de permissões no conjunto chamado SeeDefinition. As permissões neste exemplo são as permissões normalmente concedidas a aplicativos em execução no SEE, portanto, o aplicativo Olá, Mundo é executado sem exibir ao usuário nenhum aviso de segurança do Gerenciador de Confiança.

O manifesto de implantação

Conforme mencionado, o manifesto de implantação é mais interessante. O manifesto de implantação contém, obviamente, configurações que controlam a implantação do aplicativo.

HelloWorld.deploy

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" 
          xmlns:asmv2="urn:schemas-microsoft-com:asm.v2"  
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd">
  
  <assemblyIdentity name="HelloWorld.deploy" version="1.0.0.0" 
                    processorArchitecture="x86" asmv2:culture="en-us" 
                    publicKeyToken="0000000000000000" />

  <description asmv2:publisher="Wise Owl, Inc." 
               asmv2:product="Brent's HelloWorld Application"            
    asmv2:supportUrl="http://www.wiseowl.com/AppServer/HelloWorld/support.asp" 
  />
  
  <deployment xmlns="urn:schemas-microsoft-com:asm.v2" 
              isRequiredUpdate="false">
    <install shellVisible="true" />
    <subscription>
      <update>
        <beforeApplicationStartup />
        <periodic>
          <minElapsedTimeAllowed time="6" unit="hours" />
          <maxElapsedTimeAllowed time="1" unit="weeks" />
        </periodic>
      </update>
    </subscription>
  </deployment>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity name="HelloWorld" version="1.0.0.0" 
                        processorArchitecture="x86" asmv2:culture="en-us" 
                        publicKeyToken="0000000000000000" />
    </dependentAssembly>
    <asmv2:installFrom codebase="HelloWorld.manifest" />
  </dependency>
</assembly>

O manifesto de implantação contém informações que o Longhorn requer para instalar e atualizar um aplicativo. Observe que o elemento assemblyIdentity do manifesto de implantação faz referência ao manifesto do aplicativo. Afinal, o manifesto do aplicativo já descreve todos os componentes de um aplicativo. Para instalar um aplicativo, o manifesto de implantação diz, na verdade, "Esta é a descrição dos arquivos necessários para instalar este aplicativo".

É claro que, quando você instala um aplicativo, também precisa de mais informações do que apenas os arquivos para copiar em um sistema. O elemento description lista os atributos publisher, product e supportUrl ; o sistema exibe seu conteúdo na caixa de diálogo Adicionar/Remover Programas.

O elemento de implantação especifica como implantar e atualizar o aplicativo após a implantação. Neste exemplo, o aplicativo fica visível para o shell e o sistema do cliente marcar para e, se necessário, baixar uma nova versão do aplicativo sempre que o usuário iniciar o aplicativo. Além disso, o sistema periodicamente — não mais do que a cada seis horas e não menos de uma vez por semana — verifica se há uma nova versão. Quando o marcar periódico localizar uma nova versão, o Longhorn baixará a nova versão em segundo plano e a instalará; ela estará pronta para ser executada na próxima vez que o usuário executar o aplicativo.

Executando o aplicativo

Normalmente, um usuário "executará" o manifesto do aplicativo para executar o aplicativo do servidor diretamente sem instalar o aplicativo no computador local. O Longhorn baixa os componentes do aplicativo conforme necessário. Nesse caso, o servidor deve estar disponível para executar o aplicativo.

Quando um usuário "executa" o manifesto de implantação, o Longhorn baixa e instala o aplicativo no computador local. O aplicativo pode instalar ícones na área de trabalho, adicionar itens de menu Iniciar e geralmente se tornar um aplicativo instalado tradicional. É claro que você também obtém as atualizações automáticas em segundo plano, a reversão de versão e o suporte à desinstalação.

Quando você inicia um manifesto de implantação pela primeira vez, o Longhorn instala o aplicativo no cache do aplicativo e adiciona uma entrada à lista Adicionar ou Remover Programas do Painel de Controle. Posteriormente, sempre que você executar o manifesto de implantação, o aplicativo será carregado diretamente do cache do aplicativo. Normalmente, ele não é baixado novamente.

No entanto, quando você desinstala o aplicativo do cache usando o miniaplicativo Adicionar/Remover Programas do Painel de Controle, a execução posterior do manifesto de implantação baixa e instala o aplicativo mais uma vez.

Como alternativa, você pode alterar o número de versão do aplicativo no servidor. Em seguida, quando você executar o manifesto de implantação, o Longhorn baixará e instalará a nova versão lado a lado com a versão atual. Ambas as versões do aplicativo aparecerão na lista Adicionar ou Remover Programas.

Por que criar mais um sistema de build?

Eu realmente gosto do MSBuild, mesmo que, no momento deste escrito, eu tive apenas algumas semanas de experiência com ele. Claro, anos de experiência com makefiles tornam qualquer sistema de construção mais elegante atraente. Atualmente, há dois sistemas de build alternativos em uso comum: Make e Ant. Parece natural comparar o MSBuild com essas alternativas.

Por que não usar Make?

Por que desenvolver um novo sistema de build quando muitos desenvolvedores estão familiarizados com um existente chamado Make? O Make tem uma integração ruim de ferramentas ao sistema de build. Criar simplesmente executa comandos de shell. Por isso, não há capacidade inerente para uma ferramenta se comunicar com outra ferramenta durante o processo de build. O MSBuild cria instâncias das classes Task e as tarefas podem se comunicar entre si passando tipos normais do .NET.

Os makefiles têm uma sintaxe incomum, são difíceis de escrever e não são dimensionados bem, pois ficam complexos para projetos grandes. Além disso, ferramentas diferentes de Make não podem processar facilmente um makefile. Ferramentas diferentes do MSBuild podem facilmente gerar e analisar a sintaxe baseada em XML de um projeto do MSBuild.

Por fim, o Make realmente não tem suporte para projetos. Não há abstração do sistema de arquivos e não há suporte para propriedades em cascata. Além disso, não há suporte em tempo de design para gerar um makefile.

Por que não usar formiga?

Uma pergunta frequente semelhante é por que desenvolver um novo sistema de build baseado em XML quando há um sistema muito bem-sucedido e avançado existente chamado Ant? Ant é um sistema de build java código aberto de Apache.org que foi pioneiro em arquivos e tarefas de projeto baseados em XML como a operação de compilação atômica. Há também uma ótima porta .NET de Ant chamada NAnt disponível de nant.sourceforge.net. Na superfície, MSBuild e Ant/NAnt são semelhantes. Ambas as ferramentas usam XML como formato de serialização de projeto e ambas as ferramentas usam tarefas como sua unidade atômica de operação de build. Ambas as ferramentas têm seus pontos fortes, mas quando você olha mais de perto, elas têm objetivos de design diferentes.

Ant tomou a decisão de design de colocar muita funcionalidade em um grande conjunto de tarefas. O MSBuild tem uma meta de design diferente, em que a funcionalidade semelhante é encapsulada pelo mecanismo (como análise de carimbo de data/hora, comunicação de intertarefa por meio de itens, envio em lote de tarefas, transformações de item e assim por diante). Ambas as abordagens têm seus pontos fortes e fracos.

O modelo do Ant permite que os desenvolvedores estendam e controlem cada detalhe do build e, portanto, ele é muito flexível. No entanto, ele também coloca uma responsabilidade maior sobre os gravadores de tarefas porque as tarefas precisam ser muito mais sofisticadas para fornecer funcionalidade consistente. O modelo do MSBuild diminui a quantidade de funcionalidade que cada tarefa precisa implementar. Portanto, os autores do projeto podem confiar em funcionalidades consistentes em diferentes projetos, destinos e tarefas. Além disso, ambientes de desenvolvimento integrados, como o Visual Studio, também podem contar com esses serviços para fornecer resultados consistentes e uma experiência avançada do usuário, sem precisar saber nada sobre os internos das tarefas chamadas durante o processo de build.

Da mesma forma, embora o Ant tenha o conceito de um script de build, ele não tem o conceito de um manifesto de projeto que o MSBuild tem. Um script de build diz como criar um conjunto de arquivos, mas não fornece semântica adicional que descreve como os arquivos são usados. Além disso, um manifesto descreve a semântica dos arquivos, que permite que ferramentas adicionais, como um IDE, se integrem mais profundamente ao sistema de build. Por outro lado, a falta de um manifesto do projeto significa que um desenvolvedor pode adaptar mais facilmente o Ant para criar novos tipos de "coisas" porque não há esquema de restrição para o script de build.

Resumo

Agora você dominou as noções básicas. Você pode escrever XAML e pode compilar, implantar e executar o aplicativo resultante. Infelizmente, os aplicativos que você aprendeu a escrever até agora são muito chatos. O capítulo 3 se aprofunda no XAML e mostra como usar uma ampla variedade de objetos de interface do usuário fornecidos pela plataforma Longhorn. Capítulos posteriores mostram várias das outras novas tecnologias que você também pode usar em seus aplicativos.

Continue para o Capítulo 3: Controles e XAML.