Windows 8

Compartilhando código entre aplicativos do Windows Phone 8 e Windows 8

Doug Holland

Baixar o código de exemplo

Com o Visual Studio 2012, você tem um excelente conjunto de ferramentas para criar aplicativos para o Windows 8 e para o Windows Phone 8. Portanto, é interessante explorar quantos códigos é possível compartilhar entre as versões da Windows Store e do Windows Phone de seus aplicativos.

Você pode escrever aplicativos da Windows Store usando linguagens de programação diferentes — XAML com C#, Visual Basic, C++ e até mesmo HTML5 com JavaScript.

Os aplicativos do Windows Phone 8 geralmente são escritos usando XAML com C# ou Visual Basic, embora o SDK do Windows Phone 8 agora permita escrever aplicativos Direct3D usando XAML e C++. Embora o SDK do Windows Phone 8 também forneça um modelo para os aplicativos baseados em HTML5, eles são apenas baseados em XAML, com o controle WebBrowser hospedando páginas da Web baseadas em HTML5.

Neste artigo, analisarei três estratégias para compartilhar código entre aplicativos da Windows Store e do Windows Phone: PCLs (Bibliotecas de Classes Portáteis), componentes do WinRT (Tempo de Execução do Windows) (e componentes do Tempo de Execução do Windows Phone) e a opção Adicionar como vínculo do Visual Studio. Você pode obter diretrizes adicionais sobre o compartilhamento de código entre aplicativos da Windows Store e do Windows Phone no Centro de Desenvolvimento (aka.ms/sharecode).

Vale observar que, embora existam muitas semelhanças entre os aplicativos da Windows Store e do Windows Phone — blocos dinâmicos, por exemplo —­ essas são plataformas distintas, que devem ter um design de UX específico.

Arquitetura

Os princípios de arquitetura que permitem aumentar a porcentagem do código que pode ser compartilhada são, em geral, aqueles que promovem uma separação das preocupações. Se estiver usando padrões que promovem uma separação das preocupações, como o MVVM (Model-View-ViewModel) ou o MVC (Model-View-Controller), achará mais fácil ativar o compartilhamento de código — e esse também é o caso se você usar padrões de injeção de dependência em sua arquitetura. Você definitivamente deve considerar o uso desses padrões ao desenvolver a arquitetura de novos aplicativos, para aumentar o nível de compartilhamento de código que pode ser atingido. Com aplicativos existentes, talvez você possa considerar a refatoração da arquitetura para promover uma separação das preocupações e, portanto, o compartilhamento de código. Com a separação das preocupações fornecida pelo MVVM ou MVC há benefícios adicionais, como a capacidade de permitir que designers e desenvolvedores trabalhem simultaneamente. Os designers criam o design de UX com ferramentas como o Expression Blend, ao passo que os desenvolvedores escrevem o código para dar vida ao UX no Visual Studio.

Bibliotecas de classes portáteis

O projeto de PCL no Visual Studio 2012 possibilita o desenvolvimento entre plataformas, que permite escolher as estruturas de destino às quais o assembly resultante oferecerá suporte. Apresentando no Visual Studio 2010 como um suplemento opcional, o modelo do projeto de PCL agora está incluído no Visual Studio Professional 2012 e em versões superiores.

Então, quais códigos podem ser compartilhados em uma PCL?

As PCLs recebem esse nome porque permitem o compartilhamento de código portátil, e para que o código seja portátil, deve ser um código gerenciado escrito no C# ou no Visual Basic. Como uma PCL produz um binário único, o código portátil não emprega diretivas de compilação condicional, em vez disso, recursos específicos da plataforma são abstraídos usando interfaces ou classes base abstratas. Quando o código portátil precisa interagir com código específico da plataforma, padrões de injeção de dependência são usados para fornecer implementações específicas da plataforma das abstrações. Quando compilada, a PCL resulta em um único assembly que pode ser referenciado de qualquer projeto baseado nas estruturas de destino.

A Figura 1 mostra uma abordagem de arquitetura recomendada para permitir o compartilhamento de código usando PCLs. Com o padrão MVVM, os modelos de exibição e modelos estão contidos na PCL, juntamente com abstrações de quaisquer recursos específicos da plataforma. Os aplicativos da Windows Store e do Windows Phone fornecem lógica, exibições e implementações de inicialização de quaisquer abstrações de recursos específicos da plataforma. Embora o padrão de design MVVM não seja especificamente necessário para permitir o código portátil, a separação de preocupações que o padrão promove resulta em uma arquitetura limpa e extensível.

Compartilhando código usando o padrão de design MVVM
Figura 1 Compartilhando código usando o padrão de design MVVM

A caixa de diálogo Adicionar Biblioteca de Classes Portátil no Visual Studio 2012 é onde você pode selecionar as estruturas de destino às quais o assembly resultante oferecerá suporte.

Inicialmente, talvez você pense que deve marcar a caixa para o Silverlight 5, mas não é necessário para compartilhar código entre os aplicativos da Windows Store e do Windows Phone. Na verdade, com a escolha do Silverlight 5, seu código portátil não poderá aproveitar alguns tipos novos muito úteis, como a classe CallerMemberNameAttribute apresentada no Microsoft .NET Framework 4.5.

Se você já desenvolve para o Windows Phone, provavelmente esteja familiarizado com a classe MessageBox, que permite que as mensagens sejam apresentadas ao usuário. Os aplicativos da Windows Store usam a classe MessageDialog do Tempo de Execução do Windows para essa finalidade. Vejamos como abstrair essa funcionalidade específica da plataforma em uma PCL.

A interface IMessagingManager na Figura 2 abstrai a funcionalidade específica da plataforma para exibir mensagens para o usuário. A interface IMessagingManager fornece um método ShowAsync sobrecarregado que captura a mensagem e o título da mensagem a ser exibida para o usuário.

Figura 2 Interface IMessagingManager

/// <summary>
/// Provides an abstraction for platform-specific user messaging capabilities.
/// </summary>
public interface IMessagingManager
{
  /// <summary>
  /// Displays the specified message using platform-specific
  /// user-messaging capabilities.
  /// </summary>
  /// <param name="message">The message to be displayed to the user.</param>
  /// <param name="title">The title of the message.</param>
  /// <returns>A <see cref="T:MessagingResult"/>
  value representing the user's response.</returns>
  Task<MessagingResult> ShowAsync(string message, string title);
  /// <summary>
  /// Displays the specified message using platform-specific
  /// user-messaging capabilities.
  /// </summary>
  /// <param name="message">The message to be displayed to the user.</param>
  /// <param name="title">The title of the message.</param>
  /// <param name="buttons">The buttons to be displayed.</param>
  /// <returns>A <see cref="T:MessagingResult"/>
  value representing the user's response.</returns>
  Task<MessagingResult> ShowAsync(string message, string title,
    MessagingButtons buttons);
}

O método ShowAsync é sobrecarregado para que você tenha a opção de especificar botões para exibir juntamente com a mensagem. A enumeração MessagingButtons fornece uma abstração independente da plataforma para exibir um botão OK, botões OK e Cancelar, ou botões Sim e Não (consulte a Figura 3).

Figura 3 Enumeração MessagingButtons

/// <summary>
/// Specifies the buttons to include when a message is displayed.
/// </summary>
public enum MessagingButtons
{
  /// <summary>
  /// Displays only the OK button.
  /// </summary>
  OK = 0,
  /// <summary>
  /// Displays both the OK and Cancel buttons.
  /// </summary>
  OKCancel = 1,
  /// <summary>
  /// Displays both the Yes and No buttons.
  /// </summary>
  YesNo = 2
}

Os valores inteiros subjacentes da enumeração MessagingButtons foram intencionalmente mapeados para a enumeração MessageBoxButton do Windows Phone para converter com segurança a enumeração MessagingButtons na enumeração MessageBoxButton.

ShowAsync é um método assíncrono que retorna Task­<MessagingResult> indicando o botão no qual o usuário clicou ao ignorar a mensagem. A enumeração MessagingResult (consulte a Figura 4) também é uma abstração independente da plataforma.

Figura 4 Enumeração MessagingResult

/// <summary>
/// Represents the result of a message being displayed to the user.
/// </summary>
public enum MessagingResult
{
  /// <summary>
  /// This value is not currently used.
  /// </summary>
  None = 0,
  /// <summary>
  /// The user clicked the OK button.
    /// </summary>
    OK = 1,
    /// <summary>
    /// The user clicked the Cancel button.
    /// </summary>
    Cancel = 2,
    /// <summary>
    /// The user clicked the Yes button.
    /// </summary>
    Yes = 6,
   /// <summary>
  /// The user clicked the No button.
  /// </summary>
  No = 7
}

Neste exemplo, a interface IMessagingManager e as enumerações Messaging­Buttons e MessagingResult são portáteis e, portanto, podem ser compartilhadas em uma PCL.

Com a funcionalidade específica da plataforma abstraída na PCL, você deve fornecer implementações específicas da plataforma da interface IMessagingManager para os aplicativos da Windows Store e do Windows Phone. A Figura 5 mostra a implementação para aplicativos do Windows Phone e a Figura 6 mostra a implementação para aplicativos da Windows Store.

Figura 5 MessagingManager — Implementação do Windows Phone

/// <summary>
/// Windows Phone implementation of the <see cref="T:IMessagingManager"/> interface.
/// </summary>
internal class MessagingManager : IMessagingManager
{
  /// <summary>
  /// Initializes a new instance of the <see cref="T:MessagingManager"/> class.
  /// </summary>
  public MessagingManager()
  {
  }
  /// <summary>
  /// Displays the specified message using platform-specific
  /// user-messaging capabilities.
  /// </summary>
  /// <param name="message">The message to be displayed to the user.</param>
  /// <param name="title">The title of the message.</param>
  /// <returns>A <see cref="T:MessagingResult"/>
      value representing the users response.</returns>
  public async Task<MessagingResult> ShowAsync(string message, string title)
  {
    MessagingResult result = await this.ShowAsync(message, title,
      MessagingButtons.OK);
    return result;
  }
  /// <summary>
  /// Displays the specified message using platform-specific
  /// user-messaging capabilities.
  /// </summary>
  /// <param name="message">The message to be displayed to the user.</param>
  /// <param name="title">The title of the message.</param>
  /// <param name="buttons">The buttons to be displayed.</param>
  /// <exception cref="T:ArgumentException"/>
  /// The specified value for message or title is <c>null</c> or empty.
  /// </exception>
  /// <returns>A <see cref="T:MessagingResult"/>
  /// value representing the users response.</returns>
  public async Task<MessagingResult> ShowAsync(
    string message, string title, MessagingButtons buttons)
  {
    if (string.IsNullOrEmpty(message))
    {
      throw new ArgumentException(
        "The specified message cannot be null or empty.", "message");
    }
    if (string.IsNullOrEmpty(title))
    {
      throw new ArgumentException(
        "The specified title cannot be null or empty.", "title");
    }
    MessageBoxResult result = MessageBoxResult.None;
    // Determine whether the calling thread is the thread
    // associated with the Dispatcher.
    if (App.RootFrame.Dispatcher.CheckAccess())
    {
      result = MessageBox.Show(message, title, 
        (MessageBoxButton)buttons);
    }
    else
    {
      // Execute asynchronously on the thread the Dispatcher is associated with.
      App.RootFrame.Dispatcher.BeginInvoke(() =>
      {
        result = MessageBox.Show(message, title, 
          (MessageBoxButton)buttons);
      });
    }
    return (MessagingResult) result;
  }
}

Figura 6 MessagingManager — Implementação da Windows Store

/// <summary>
/// Windows Store implementation of the <see cref="T:IMessagingManager"/> interface.
/// </summary>
internal class MessagingManager : IMessagingManager
{
  /// <summary>
  /// Initializes a new instance of the <see cref="T:MessagingManager"/> class.
  /// </summary>
  public MessagingManager()
  {
  }
  /// <summary>
  /// Displays the specified message using platform-specific
  /// user-messaging capabilities.
  /// </summary>
  /// <param name="message">The message to be displayed to the user.</param>
  /// <param name="title">The title of the message.</param>
  /// <returns>A <see cref="T:MessagingResult"/>
      value representing the users response.</returns>
  public async Task<MessagingResult> ShowAsync(string message, string title)
  {
    MessagingResult result = await this.ShowAsync(message, title,
      MessagingButtons.OK);
    return result;
  }
  /// <summary>
  /// Displays the specified message using platform-specific
  /// user-messaging capabilities.
  /// </summary>
  /// <param name="message">The message to be displayed to the user.</param>
  /// <param name="title">The title of the message.</param>
  /// <param name="buttons">The buttons to be displayed.</param>
  /// <exception cref="T:ArgumentException"/>
  /// The specified value for message or title is <c>null</c> or empty.
  /// </exception>
  /// <exception cref="T:NotSupportedException"/>
  /// The specified <see cref="T:MessagingButtons"/> value is not supported.
  /// </exception>
  /// <returns>A <see cref="T:MessagingResult"/>
  /// value representing the users response.</returns>
  public async Task<MessagingResult> ShowAsync(
    string message, string title, MessagingButtons buttons)
  {
    if (string.IsNullOrEmpty(message))
    {
      throw new ArgumentException(
        "The specified message cannot be null or empty.", "message");
    }
    if (string.IsNullOrEmpty(title))
    {
      throw new ArgumentException(
        "The specified title cannot be null or empty.", "title");
    }
    MessageDialog dialog = new MessageDialog(message, title);
    MessagingResult result = MessagingResult.None;
    switch (buttons)
    {
      case MessagingButtons.OK:
        dialog.Commands.Add(new UICommand("OK",
          new UICommandInvokedHandler((o) => result = MessagingResult.OK)));
        break;
      case MessagingButtons.OKCancel:
        dialog.Commands.Add(new UICommand("OK",
          new UICommandInvokedHandler((o) => result = MessagingResult.OK)));
        dialog.Commands.Add(new UICommand("Cancel",
          new UICommandInvokedHandler((o) => result = MessagingResult.Cancel)));
        break;
      case MessagingButtons.YesNo:
        dialog.Commands.Add(new UICommand("Yes",
          new UICommandInvokedHandler((o) => result = MessagingResult.Yes)));
        dialog.Commands.Add(new UICommand("No",
          new UICommandInvokedHandler((o) => result = MessagingResult.No)));
        break;
      default:
        throw new NotSupportedException(
          string.Format("MessagingButtons.{0} is not supported.",
          buttons.ToString()));
            }
    dialog.DefaultCommandIndex = 1;
    // Determine whether the calling thread is the
    // thread associated with the Dispatcher.
    if (Window.Current.Dispatcher.HasThreadAccess)
    {
      await dialog.ShowAsync();
    }
    else
    {
      // Execute asynchronously on the thread the Dispatcher is associated with.
      await Window.Current.Dispatcher.RunAsync(
        CoreDispatcherPriority.Normal, async () =>
      {
        await dialog.ShowAsync();
      });
    }
    return result;
  }
}

A versão da classe MessagingManager para o Windows Phone usa a classe MessageBox específica da plataforma para exibir a mensagem. Os valores inteiros subjacentes da enumeração MessagingButtons foram intencionalmente mapeados para a enumeração MessageBoxButton do Windows Phone, o que permite que você converta com segurança a enumeração MessagingButtons na enumeração MessageBoxButton. Da mesma forma, os valores inteiros subjacentes da enumeração MessagingResult permitem que você a converta com segurança na enumeração MessageBoxResult.

A versão da classe MessagingManager para a Windows Store na Figura 6 usa a classe MessageDialog do Tempo de Execução do Windows para exibir a mensagem. Os valores inteiros subjacentes da enumeração MessagingButtons foram intencionalmente mapeados para a enumeração MessageBoxButton do Windows Phone, o que permite que você converta com segurança a enumeração MessagingButtons na enumeração MessageBoxButton.

Injeção de dependência

Com a arquitetura do aplicativo definida como mostra a Figura 1, IMessagingManager fornece a abstração específica da plataforma para enviar mensagens aos usuários. Agora, usarei padrões de injeção de dependência para injetar implementações específicas da plataforma dessa abstração no código portátil. No exemplo na Figura 7, o HelloWorldViewModel usa injeção de construtor para injetar uma implementação específica de plataforma da interface IMessagingManager. Em seguida, o método HelloWorldView­Model.DisplayMessage usa a implementação injetada para enviar mensagens ao usuário. Para saber mais sobre a injeção de dependência, recomendo a leitura de “Dependency Injection in .NET”, de Mark Seemann (Manning Publications, 2011, bit.ly/dotnetdi).

Figura 7 Classe HelloWorldViewModel portátil

/// <summary>
/// Provides a portable view model for the Hello World app.
/// </summary>
public class HelloWorldViewModel : BindableBase
{
  /// <summary>
  /// The message to be displayed by the messaging manager.
  /// </summary>
  private string message;
  /// <summary>
  /// The title of the message to be displayed by the messaging manager.
  /// </summary>
  private string title;
  /// <summary>
  /// Platform specific instance of the <see cref="T:IMessagingManager"/> interface.
  /// </summary>
  private IMessagingManager MessagingManager;
  /// <summary>
  /// Initializes a new instance of the <see cref="T:HelloWorldViewModel"/> class.
  /// </summary>
  public HelloWorldViewModel(IMessagingManager messagingManager,
    string message, string title)
  {
    this.messagingManager = MessagingManager;
    this.message = message;
    this.title = title;
    this.DisplayMessageCommand = new Command(this.DisplayMessage);
  }
  /// <summary>
  /// Gets the display message command.
  /// </summary>
  /// <value>The display message command.</value>
  public ICommand DisplayMessageCommand
  {
    get;
    private set;
  }
  /// <summary>
  /// Displays the message using the platform-specific messaging manager.
  /// </summary>
  private async void DisplayMessage()
  {
    await this.messagingManager.ShowAsync(
      this.message, this.title, MessagingButtons.OK);
  }
}

Componentes de Tempo de Execução do Windows

Os componentes de Tempo de Execução do Windows permitem compartilhar código não portátil entre aplicativos da Windows Store e do Windows Phone. No entanto, os componentes não são compatíveis com o binário, por isso, você também precisará criar projetos comparáveis de componentes do Tempo de Execução do Windows e do Tempo de Execução do Windows Phone para aproveitar o código nas duas plataformas. Embora você precise incluir projetos em sua solução para os componentes do Tempo de Execução do Windows e do Tempo de Execução do Windows Phone, esses projetos são criados usando os mesmos arquivos de origem do C++.

Com a capacidade de compartilhar código nativo do C++ entre os aplicativos da Windows Store e do Windows Phone, os componentes do Tempo de Execução do Windows são uma escolha excelente para escrever operações que exigem muitos recursos computacionais usando o C++ para atingir o desempenho ideal.

As definições da API nos componentes do Tempo de Execução do Windows são expostas nos metadados contidos nos arquivos .winmd. Usando esses metadados, as projeções de linguagem permitem que a linguagem de consumo determine como as APIs são consumidas nessa linguagem. A Figura8 mostra as linguagens com suporte para a criação e o consumo de componentes do Tempo de Execução do Windows. No momento da redação deste artigo, apenas o C++ tem suporte para a criação dos dois tipos de componente.

Figura 8 Criando e consumindo componentes do Tempo de Execução do Windows

Plataforma Criar Consumir
Componentes de Tempo de Execução do Windows C++, C#, Visual Basic C++, C#, Visual Basic, JavaScript
Componentes de Tempo de Execução do Windows Phone C++ C++, C#, Visual Basic

No exemplo a seguir, demonstrarei como uma classe C++ criada para calcular números Fibonacci pode ser compartilhada entre os aplicativos da Windows Store e do Windows Phone. A Figura 9 e a Figura 10 mostram a implementação da classe FibonacciCalculator no C++/CX (Extensões de Componente).

Figura 9 Fibonacci.h

#pragma once
namespace MsdnMagazine_Fibonacci
{
  public ref class FibonacciCalculator sealed
  {
  public:
    FibonacciCalculator();
    uint64 GetFibonacci(uint32 number);
  private:
    uint64 GetFibonacci(uint32 number, uint64 p0, uint64 p1);
  };    
}

Figura 10 Fibonacci.cpp

#include "pch.h"
#include "Fibonacci.h"
using namespace Platform;
using namespace MsdnMagazine_Fibonacci;
FibonacciCalculator::FibonacciCalculator()
{
}
uint64 FibonacciCalculator::GetFibonacci(uint32 number)
{
  return number == 0 ? 0L : GetFibonacci(number, 0, 1);
}
uint64 FibonacciCalculator::GetFibonacci(uint32 number, 
  uint64 p0, uint64 p1)
{
  return number == 1 ? p1 : GetFibonacci(number - 1, 
    p1, p0 + p1);
}

Na Figura 11, você pode ver a estrutura da solução no Gerenciador de Soluções do Visual Studio para as amostras que acompanham este artigo, mostrando os mesmos arquivos de origem do C++ contidos nos dois componentes.

Gerenciador de Soluções do Visual Studio
Figura 11 Gerenciador de Soluções do Visual Studio

Funcionalidade Adicionar como vínculo do Visual Studio

Ao adicionar um item existente a um projeto no Visual Studio, talvez você tenha observado a pequena seta à direita do botão Adicionar. Se você clicar nessa seta, as opções de Adicionar ou Adicionar como vínculo serão exibidas. Se você escolher a opção padrão para adicionar um arquivo, ele será copiado para o projeto e duas cópias do arquivo existirão separadamente em disco e no controle do código-fonte (se usado). Se você escolher Adicionar como vínculo, haverá apenas uma única instância do arquivo em disco e no controle do código-fonte, o que pode ser extremamente útil para controle de versão. Ao adicionar arquivos existentes nos projetos do Visual C++, esse é o comportamento padrão e, portanto, a caixa de diálogo Adicionar item existente não fornece uma opção no botão Adicionar. O Centro de Desenvolvimento fornece diretrizes adicionais sobre o compartilhamento de código com a opção Adicionar como vínculo em bit.ly/addaslink.

As APIs de Tempo de Execução do Windows não são portáteis, portanto, não podem ser compartilhadas em uma PCL. O Windows 8 e o Windows Phone 8 expõem um subconjunto da API de Tempo de Execução do Windows, para que o código possa ser escrito com base nesse subconjunto e, posteriormente, compartilhado entre os dois aplicativos usando a opção Adicionar como vínculo. O Centro de Desenvolvimento fornece detalhes sobre esse subconjunto compartilhado da API de Tempo de Execução do Windows em bit.ly/wpruntime.

Conclusão

Com as versões do Windows 8 e do Windows Phone 8, você pode começar a explorar maneiras de compartilhar o código entre as duas plataformas. Neste artigo, explorei como o código portátil pode ser compartilhado usando PCLs compatíveis com o binário e como recursos específicos da plataforma podem ser abstraídos. Em seguida, demonstrei como código nativo não portátil pode ser compartilhado usando componentes do Tempo de Execução do Windows. Por fim, mencionei a opção Adicionar como vínculo do Visual Studio.

Em termos de arquitetura, observei que os padrões que promovem uma separação das preocupações, como o MVVM, podem ser úteis para possibilitar o compartilhamento de código, e que os padrões de injeção de dependência permitem que o código compartilhado utilize recursos específicos da plataforma. O Centro de Desenvolvimento do Windows Phone fornece diretrizes adicionais sobre o compartilhamento de código entre os aplicativos da Windows Store e do Windows Phone em aka.ms/sharecode, e também fornece o aplicativo de exemplo PixPresenter em bit.ly/pixpresenter.

Doug Holland é um divulgador sênior de arquitetura da equipe de desenvolvedores e de divulgação da plataforma da Microsoft. Trabalhou nos últimos anos com parceiros estratégicos para criar aplicativos focados no consumidor para Windows e Windows Phone. Já foi MVP do Visual C# e Intel Black Belt Developer, além de autor do livro “Professional Windows 8 Programming: Application Development with C# and XAML” (Wrox, 2012), disponível em bit.ly/prowin8book.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Andrew Byrne (Microsoft), Doug Rothaus (Microsoft) e Marian Laparu (Microsoft)