Partilhar via


Portar o exemplo do Clipboard de C# para C++/WinRT — um estudo de caso

Este tópico apresenta um estudo de caso de portabilidade de um dos exemplos de aplicativo da Plataforma Universal do Windows (UWP) de C# para C++/WinRT. Você pode ganhar prática e experiência de portabilidade acompanhando o guia e adaptando a amostra por si próprio à medida que avança.

Para obter um catálogo abrangente dos detalhes técnicos envolvidos na transição de C# para C++/WinRT, consulte o tópico complementar Mover para C++/WinRT a partir do C#.

Um breve prefácio sobre arquivos de código-fonte C# e C++

Em um projeto C#, seus arquivos de código-fonte são principalmente .cs arquivos. Ao mudar para C++, você notará que há mais tipos de arquivos de código-fonte para se acostumar. A razão tem a ver com a diferença entre compiladores, a forma como o código-fonte C++ é reutilizado e as noções de declarar e definir um tipo e suas funções (seus métodos).

Uma declaração de de função descreve apenas o de assinatura da função (seu tipo de retorno, seu nome e seus tipos e nomes de parâmetros). A definição de função inclui o corpo da função (a sua implementação).

É um pouco diferente quando se trata de tipos. Você definir um tipo fornecendo seu nome e (no mínimo) apenas declarando todas as suas funções de membro (e outros membros). Isso mesmo, tu podes definir um tipo, mesmo que não definas as suas funções associadas.

  • Os arquivos de código-fonte C++ comuns são .h (dot aitch) e .cpp arquivos. Um .h arquivo é um arquivo de cabeçalho e define um ou mais tipos. Embora você possa definir funções de membro em um cabeçalho, normalmente é para isso que serve um arquivo .cpp. Portanto, para um tipo C++ hipotético MyClass, você definiria MyClass em MyClass.h, e definiria suas funções de membro em MyClass.cpp. Para outros desenvolvedores reutilizarem suas classes, você compartilharia apenas os arquivos e o .h código-objeto. Você manteria seus .cpp arquivos secretos, porque a implementação constitui sua propriedade intelectual.
  • Cabeçalho pré-compilado (pch.h). Normalmente, há um conjunto de arquivos de cabeçalho que você inclui em seu aplicativo, e você não altera esses arquivos com muita frequência. Assim, em vez de processar o conteúdo desse conjunto de cabeçalhos cada vez que você compila, você pode agregar esses cabeçalhos em um arquivo, compilá-lo uma vez e, em seguida, usar a saída dessa etapa de pré-compilação cada vez que você compilar. Você faz isso por meio de um arquivo de cabeçalho pré-compilado (geralmente chamado pch.h).
  • .idl ficheiros. Esses arquivos contêm Interface Definition Language (IDL). Pode pensar no IDL como arquivos de cabeçalho para tipos de ambiente de execução do Windows. Falaremos mais sobre o IDL na seção IDL para o MainPage tipo.

Faça o download e teste o exemplo de Área de Transferência.

Visite a página web de exemplo da Área de Transferência do e clique em Descarregar ZIP. Descompacte o arquivo baixado e dê uma olhada na estrutura de pastas.

  • A versão C# do código-fonte de exemplo está contida na pasta chamada cs.
  • A versão C++/WinRT do código-fonte de exemplo está contida na pasta chamada cppwinrt.
  • Outros arquivos — usados pela versão C# e pela versão C++/WinRT — podem ser encontrados nas shared pastas e SharedContent .

O passo a passo neste tópico mostra como pode recriar a versão C++/WinRT do exemplo Clipboard, ao portá-la do código-fonte C#. Dessa forma, você pode ver como pode portar seus próprios projetos C# para C++/WinRT.

Para ter uma ideia do que o exemplo faz, abra a solução C# (\Clipboard_sample\cs\Clipboard.sln), altere a configuração conforme apropriado (talvez para x64), compile e execute. A própria interface do usuário (UI) do exemplo orienta você através de seus vários recursos, passo a passo.

Sugestão

A pasta raiz do exemplo que você baixou pode ser nomeada Clipboard em vez de Clipboard_sample. Mas continuaremos a referir-nos a essa pasta como Clipboard_sample para distingui-la da versão C++/WinRT que você criará em uma etapa posterior.

Criar uma aplicação vazia (C++/WinRT), chamada Clipboard

Observação

Para obter informações sobre como instalar e usar o C++/WinRT Visual Studio Extension (VSIX) e o pacote NuGet (que, juntos, fornecem suporte a modelo de projeto e compilação), consulte suporte do Visual Studio para C++/WinRT.

Inicie o processo de portabilidade criando um novo projeto C++/WinRT no Microsoft Visual Studio. Crie um novo projeto usando o modelo de projeto Aplicação em Branco (C++/WinRT). Defina o nome para Área de Transferênciae (para que a estrutura de pastas corresponda ao passo a passo) verifique se Colocar solução e projeto no mesmo diretório está desmarcado.

Apenas para obter uma linha de base, certifique-se de que este novo projeto, vazio, compila e executa.

Package.appxmanifest e ficheiros de recursos

Se as versões C# e C++/WinRT do exemplo não precisarem ser instaladas lado a lado na mesma máquina, os arquivos de origem do manifesto do pacote do aplicativo dos dois projetos (Package.appxmanifest) poderão ser idênticos. Nesse caso, você pode simplesmente copiar Package.appxmanifest do projeto C# para o projeto C++/WinRT e pronto.

Para que as duas versões da amostra coexistam, elas precisam de identificadores diferentes. Nesse caso, no projeto C++/WinRT, abra o Package.appxmanifest arquivo em um editor XML e anote esses três valores.

  • Dentro do elemento /Package/Identity, observe o valor do atributo Name. Este é o nome do pacote. Para um projeto recém-criado, o projeto lhe dará um valor inicial de um GUID exclusivo.
  • Dentro do elemento /Package/Applications/Application, preste atenção ao valor do atributo Id. Este é o ID do aplicativo.
  • Dentro do elemento /Package/mp:PhoneIdentity, observe o valor do atributo PhoneProductId. Novamente, para um projeto recém-criado, isto será definido para o mesmo GUID que é atribuído ao nome do pacote.

Em seguida, copie Package.appxmanifest do projeto C# para o projeto C++/WinRT. Finalmente, você pode restaurar os três valores que você anotou. Ou você pode editar os valores copiados para torná-los exclusivos e/ou apropriados para o aplicativo e para sua organização (como normalmente faria para um novo projeto). Por exemplo, neste caso, em vez de restaurar o valor do nome do pacote, podemos apenas alterar o valor copiado de Microsoft.SDKSamples.Clipboard.CS para Microsoft.SDKSamples.Clipboard.CppWinRT. E podemos deixar o ID do aplicativo definido como App. Contanto que o nome do pacote ou a ID do aplicativo sejam diferentes, os dois aplicativos terão AUMIDs (Application User Model IDs) diferentes. E isso é o que é necessário para que dois aplicativos sejam instalados lado a lado na mesma máquina.

Para os fins deste passo a passo, faz sentido fazer algumas outras alterações no Package.appxmanifest. Há três ocorrências da cadeia de caracteres Exemplo de C# da Área de Transferência. Altere isso para Clipboard C++/WinRT Sample.

No projeto C++/WinRT, o Package.appxmanifest arquivo e o projeto agora estão fora de sincronia em relação aos arquivos de ativos aos quais fazem referência. Para corrigir isso, primeiro remova os ativos do projeto C++/WinRT selecionando todos os arquivos na Assets pasta (no Gerenciador de Soluções no Visual Studio) e removendo-os (escolha Excluir na caixa de diálogo).

O projeto C# faz referência a arquivos de ativos de uma pasta compartilhada. Você pode fazer o mesmo no projeto C++/WinRT ou copiar os arquivos como faremos neste passo a passo.

Navegue até a pasta \Clipboard_sample\SharedContent\media. Selecione os sete ficheiros que o projeto C# inclui (microsoft-sdk.png, smalltile-sdk.png, splash-sdk.png, squaretile-sdk.png, storelogo-sdk.png, tile-sdk.png, e windows-sdk.png), copie-os e cole-os na pasta \Clipboard\Clipboard\Assets no novo projeto.

Clique com o botão direito do mouse na Assets pasta (no Gerenciador de Soluções no projeto C++/WinRT) >Adicionar>item existente... e navegue até \Clipboard\Clipboard\Assets. No seletor de arquivos, selecione os sete arquivos e clique em Adicionar.

Package.appxmanifest agora está novamente em sincronia com os ficheiros de ativos do projeto.

MainPage, incluindo a funcionalidade que configura o exemplo

O exemplo da Área de Transferência — tal como todos os exemplos de aplicações da Plataforma Universal do Windows (UWP) — consiste numa coleção de cenários que o utilizador pode explorar um a um. A coleção de cenários em um determinado exemplo é configurada no código-fonte do exemplo. Cada cenário na coleção é um item de dados que armazena um título, bem como o tipo da classe no projeto que implementa o cenário.

Na versão C# do exemplo, se você procurar no SampleConfiguration.cs arquivo de código-fonte, verá duas classes. A maior parte da lógica de configuração está na classe MainPage, que é uma classe parcial (forma uma classe completa quando combinada com a marcação em MainPage.xaml e o código imperativo em MainPage.xaml.cs). A outra classe neste ficheiro de código-fonte é Scenario, com as propriedades Title e ClassType.

Nas próximas subseções, veremos como portar MainPage e Scenario.

IDL do tipo MainPage

Vamos começar esta seção falando brevemente sobre a Interface Definition Language (IDL) e como ela nos ajuda quando estamos programando com C++/WinRT. IDL é um tipo de código-fonte que descreve a interface chamável de um tipo de Windows Runtime. A superfície chamável (ou pública) de um tipo é projetada para o mundo, para que o tipo possa ser consumido. Essa parte projetada do tipo contrasta com a implementação interna real do tipo, que, obviamente, não é invocável e não é pública. É apenas a parte projetada que definimos no IDL.

Tendo criado o código-fonte IDL (dentro de um .idl arquivo), você pode compilar o IDL em arquivos de metadados legíveis por máquina (também conhecidos como Metadados do Windows). Esses arquivos de metadados têm a extensão .winmd, e aqui estão alguns de seus usos.

  • A .winmd pode descrever os tipos do Tempo de Execução do Windows em um componente. Quando você faz referência a um componente do Tempo de Execução do Windows (WRC) de um projeto de aplicativo, o projeto de aplicativo lê os metadados do Windows pertencentes ao WRC (esses metadados podem estar em um arquivo separado ou podem ser empacotados no mesmo arquivo que o próprio WRC) para que você possa consumir os tipos do WRC de dentro do aplicativo.
  • Um .winmd pode descrever os tipos do Tempo de Execução do Windows em uma parte do seu aplicativo para que eles possam ser consumidos por uma parte diferente do mesmo aplicativo. Por exemplo, um tipo de Tempo de Execução do Windows que é consumido por uma página XAML na mesma aplicação.
  • Para facilitar o uso de tipos do Windows Runtime (internos ou de terceiros), o sistema de compilação C++/WinRT usa arquivos .winmd para criar tipos de encapsulamento que representam as partes projetadas desses tipos do Windows Runtime.
  • Para facilitar a implementação de seus próprios tipos de Tempo de Execução do Windows, o sistema de compilação C++/WinRT transforma seu IDL em um .winmd arquivo e, em seguida, usa isso para gerar wrappers para sua projeção, bem como stubs nos quais basear sua implementação (falaremos mais sobre esses stubs mais adiante neste tópico).

A versão específica do IDL que usamos com C++/WinRT é a Microsoft Interface Definition Language 3.0. No restante desta seção do tópico, examinaremos o tipo MainPage em C# em mais detalhe. Decidiremos quais partes dele precisam estar no de projeção do tipo de C++/WinRT MainPage (ou seja, em sua superfície chamável ou pública) e quais podem ser apenas parte de sua implementação. Essa distinção é importante porque quando chegarmos a escrever o nosso IDL (o que faremos na seção a seguir a esta), estaremos definindo apenas as partes chamáveis lá.

Os arquivos de código-fonte C# que, juntos, implementam o tipo MainPage são: MainPage.xaml (que iremos portar em breve, copiando-o), MainPage.xaml.cse SampleConfiguration.cs.

Na versão C++/WinRT, vamos fatorar nossos MainPage digitar em arquivos de código-fonte de maneira semelhante. Vamos pegar a lógica em MainPage.xaml.cs e traduzi-la principalmente para MainPage.h e MainPage.cpp. E para a lógica em SampleConfiguration.cs, vamos traduzir isso para SampleConfiguration.h e SampleConfiguration.cpp.

As classes em um aplicativo da Plataforma Universal do Windows (UWP) em C# são, claro, tipos do Tempo de Execução do Windows. Mas quando o programador cria um tipo numa aplicação C++/WinRT, pode optar por que esse tipo seja um Tipo do Tempo de Execução do Windows ou uma classe/estrutura/enumeração regular de C++.

Qualquer página XAML no nosso projeto precisa ser um tipo do Tempo de Execução do Windows, portanto, MainPage precisa ser um tipo do Tempo de Execução do Windows. No projeto C++/WinRT, MainPage já é um tipo do Tempo de Execução do Windows, portanto, não precisamos alterar esse aspeto dele. Especificamente, é uma classe de tempo de execução .

  • Para obter mais detalhes sobre se deves ou não criar uma classe de runtime para um determinado tipo, consulta o tópico Desenvolver APIs com C++/WinRT.
  • Em C++/WinRT, a implementação interna de uma classe de tempo de execução e as partes projetadas (públicas) dela existem na forma de duas classes diferentes. Estes são conhecidos como o tipo de implementação e o tipo projetado. Você pode saber mais sobre eles no tópico mencionado no marcador anterior e também em Consumir APIs com C++/WinRT.
  • Para obter mais informações sobre a conexão entre classes de tempo de execução e IDL (arquivos.idl), você pode ler e acompanhar o tópico controles XAML; vincular a uma propriedade C++/WinRT. Esse tópico percorre o processo de criação de uma nova classe de tempo de execução, cuja primeira etapa é adicionar um novo item Midl File (.idl) ao projeto.

Para MainPage, na verdade, já temos o arquivo necessário MainPage.idl no projeto C++/WinRT. Isso porque o modelo de projeto o criou para nós. Mas mais adiante neste passo a passo, adicionaremos mais .idl arquivos ao projeto.

Em breve veremos uma lista de exatamente qual IDL precisamos adicionar ao arquivo MainPage.idl existente. Antes disso, temos alguns raciocínios a fazer sobre o que precisa e o que não precisa ir no IDL.

Para determinar quais membros de MainPage precisamos declarar em MainPage.idl (para que eles se tornem parte da classe de tempo de execução MainPage), e quais podem simplesmente ser membros do tipo de implementação MainPage, vamos fazer uma lista dos membros da classe C# MainPage. Procuramos esses membros procurando em MainPage.xaml.cs e em SampleConfiguration.cs.

Encontramos um total de doze protected e private campos e métodos. E encontramos os membros public seguintes.

  • O construtor padrão MainPage().
  • Os campos estáticos atual e FEATURE_NAME.
  • As propriedades IsClipboardContentChangedEnabled e Scenarios.
  • Os métodos BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotificationse NotifyUser.

São esses membros public que são candidatos a declarar em MainPage.idl. Portanto, vamos examinar cada um deles e ver se eles precisam fazer parte da classe de tempo de execução MainPage ou se precisam apenas fazer parte da sua implementação.

  • O construtor padrão MainPage(). Para um XAML Page, é normal declarar um construtor padrão em seu IDL. Dessa forma, a estrutura da interface do usuário XAML pode ativar o tipo.
  • O campo estático Current é utilizado dentro das páginas XAML do cenário individual para aceder à instância de MainPagedo aplicativo. Como atual não está sendo usado para interoperar com a estrutura XAML (nem é usado em unidades de compilação), poderíamos reservá-lo para ser apenas um membro do tipo de implementação. Com os teus próprios projetos em casos como este, poderás optar por fazer isso. Mas como o campo é uma instância do tipo projetado, parece lógico declará-lo no IDL. Então é isso que faremos aqui (e fazer isso também torna o código um pouco mais limpo).
  • É um caso semelhante para o campo estático FEATURE_NAME, que é acessado dentro do tipo MainPage. Mais uma vez, optar por declará-lo no IDL torna o nosso código ligeiramente mais limpo.
  • A propriedade IsClipboardContentChangedEnabled é usada somente na classe OtherScenarios. Assim, durante a migração, iremos simplificar as coisas e torná-la um campo privado da classe de tempo de execução OtherScenarios. Para que não entre no IDL.
  • A propriedade Scenarios é uma coleção de objetos do tipo Scenario (um tipo que mencionamos anteriormente). Falaremos sobre o cenário na próxima subseção, por isso vamos deixar a propriedade dos cenários para tratar mais tarde também.
  • Os métodos BuildClipboardFormatsOutputString, DisplayToast e EnableClipboardContentChangedNotifications são funções utilitárias que parecem mais relacionadas com o estado geral do exemplo do que com a página principal. Portanto, durante a migração, refatoraremos esses três métodos em um novo tipo de utilitário chamado SampleState (que não precisa ser um tipo do tempo de execução do Windows). Por essa razão, esses três métodos não entrarão no IDL.
  • O método NotifyUser é chamado de dentro das páginas XAML do cenário individual na instância de MainPage que é retornada do campo estático Current. Como (como já observado) atual é uma instância do tipo projetado, precisamos declarar NotifyUser no IDL. NotifyUser usa um parâmetro do tipo NotifyType. Falaremos sobre isso na próxima subseção.

Qualquer membro ao qual você deseja vincular dados também precisa ser declarado no IDL (quer você esteja usando {x:Bind} ou {Binding}). Para obter mais informações, consulte de vinculação de dados .

Estamos a fazer progressos: estamos a desenvolver uma lista dos membros a adicionar e dos que não adicionar ao MainPage.idl ficheiro. Mas ainda temos que discutir o Scenarios propriedade e o NotifyType tipo. Então, vamos fazer isso a seguir.

IDL para os tipos de cenário e NotifyType

A classe Scenario é definida em SampleConfiguration.cs. Temos uma decisão a tomar sobre como portar essa classe para C++/WinRT. Por padrão, provavelmente o tornaríamos um C++ structcomum. Mas, se o Scenario estiver a ser usado entre binários ou para interoperar com o framework XAML, então precisará ser declarado no IDL como um tipo do Tempo de Execução do Windows.

Estudando o código-fonte do C#, descobrimos que o Scenario é usado neste contexto.

<ListBox x:Name="ScenarioControl" ... >
var itemCollection = new List<Scenario>();
int i = 1;
foreach (Scenario s in scenarios)
{
    itemCollection.Add(new Scenario { Title = $"{i++}) {s.Title}", ClassType = s.ClassType });
}
ScenarioControl.ItemsSource = itemCollection;

Uma coleção de objetos Scenario está sendo atribuída à propriedade ItemsSource de um ListBox (que é um controle de itens). Como o Cenário precisa interoperar com XAML, ele precisa ser um tipo do Windows Runtime. Por isso, precisa ser definido no IDL. Definir o tipo de Scenario no IDL faz com que o sistema de compilação C++/WinRT gere uma definição de código-fonte de Scenario para si em um arquivo de cabeçalho nos bastidores (cujo nome e localização não são importantes para este passo a passo).

E você vai se lembrar que MainPage.Scenarios é uma coleção de objetos Scenario, que acabamos de dizer que precisam estar no IDL. Por esse motivo, MainPage.Scenarios em si também precisa ser declarado no IDL.

NotifyType é um enum declarado na linguagem C# MainPage.xaml.cs. Como passamos NotifyType para um método da classe de tempo de execução MainPage, NotifyType também precisa ser um tipo do Tempo de Execução do Windows; e precisa ser definido em MainPage.idl.

Agora vamos adicionar ao MainPage.idl arquivo os novos tipos e o novo membro da Mainpage que decidimos declarar no IDL. Ao mesmo tempo, removeremos do IDL os membros de marcador de posição de Mainpage que o modelo de projeto do Visual Studio nos deu.

Portanto, em seu projeto C++/WinRT, abra MainPage.idle edite-o para que se pareça com a listagem abaixo. Observe que uma das edições é alterar o nome do namespace de Área de Transferência para SDKTemplate. Se quiseres, podes simplesmente substituir todo o conteúdo de MainPage.idl pelo código seguinte. Outro ajuste a ser observado é que estamos alterando o nome de Scenario::ClassType para Scenario::ClassName.

// MainPage.idl
namespace SDKTemplate
{
    struct Scenario
    {
        String Title;
        Windows.UI.Xaml.Interop.TypeName ClassName;
    };

    enum NotifyType
    {
        StatusMessage,
        ErrorMessage
    };

    [default_interface]
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();

        static MainPage Current{ get; };
        static String FEATURE_NAME{ get; };

        static Windows.Foundation.Collections.IVector<Scenario> scenarios{ get; };

        void NotifyUser(String strMessage, NotifyType type);
    };
}

Observação

Para obter mais informações sobre o conteúdo de um .idl arquivo em um projeto C++/WinRT, consulte Microsoft Interface Definition Language 3.0.

Com seu próprio trabalho de portabilidade, você pode não querer ou precisar alterar o nome do namespace como fizemos acima. Estamos a fazer isto aqui apenas porque o namespace padrão do projeto C# que estamos a portar é SDKTemplate; enquanto o nome do projeto e do assembly é Clipboard.

À medida que atualmente progredimos com a portagem neste passo a passo, alteraremos todas as ocorrências do nome do namespace Clipboard no código-fonte para SDKTemplate. Há também um lugar nas propriedades do projeto C++/WinRT onde o nome do namespace Clipboard aparece, então aproveitaremos a oportunidade para alterar isso agora.

No Visual Studio, para o projeto C++/WinRT, defina a propriedade do projeto Common Properties>C++/WinRT>Root Namespace para o valor SDKTemplate.

Salve o IDL e gere novamente os arquivos de stub

O tópico controlos XAML; ligar a uma propriedade C++/WinRT introduz a noção de arquivos stube mostra um guia de sua utilização em ação. Também mencionamos stubs anteriormente neste tópico, quando mencionamos que o sistema de compilação C++/WinRT transforma o conteúdo de seus .idl arquivos em metadados do Windows e, a partir desses metadados, uma ferramenta chamada cppwinrt.exe gera stubs nos quais você pode basear sua implementação.

Cada vez que você adiciona, remove ou altera algo em seu IDL e compila, o sistema de compilação atualiza as implementações de stub nesses arquivos de stubs. Portanto, cada vez que você alterar seu IDL e compilar, recomendamos que você visualize esses arquivos de stubs, copie todas as assinaturas alteradas e cole-as em seu projeto. Daremos mais detalhes e exemplos de como fazer isso em um momento. Mas a vantagem de fazer isso é dar-lhe uma maneira livre de erros de saber em todos os momentos qual deve ser a forma do seu tipo de implementação e qual deve ser a assinatura de seus métodos.

Neste ponto do passo a passo, terminamos de editar o MainPage.idl arquivo por enquanto, então você deve salvá-lo agora. O projeto não será concluído neste momento, mas realizar um processo de construção agora é útil porque regenera os arquivos de stub para MainPage. Portanto, construa o projeto agora e desconsidere quaisquer erros de compilação.

Para este projeto C++/WinRT, os arquivos de stub são gerados na \Clipboard\Clipboard\Generated Files\sources pasta. Você os encontrará lá depois que a compilação parcial tiver chegado ao fim (novamente, como esperado, a compilação não terá sucesso totalmente. Mas a etapa que nos interessa – gerar stubs – terá sido bem-sucedida). Os ficheiros em que estamos interessados são MainPage.h e MainPage.cpp.

Nesses dois ficheiros de stub, verás novas implementações de stub dos membros de MainPage que adicionámos ao IDL (atual e FEATURE_NAME , por exemplo). Quer copiar essas implementações de stub para os arquivos MainPage.h e MainPage.cpp que já estão no projeto. Ao mesmo tempo, tal como fizemos com o IDL, removeremos dos ficheiros existentes os membros de espaço reservado da Mainpage que o modelo de projeto do Visual Studio nos forneceu (a propriedade fictícia denominada MyPropertye o manipulador de eventos denominado ClickHandler).

Na verdade, na versão atual do MainPage, o único membro que queremos manter é o construtor.

Depois de copiar os novos membros dos arquivos de stub, excluir os membros que não queremos e atualizar o namespace, os arquivos MainPage.h e MainPage.cpp em seu projeto devem ficar parecidos com as listagens de código abaixo. Observe que há dois tipos de MainPage . Um na namespace de implementação e um segundo na namespace factory_implementation . A única alteração que fizemos no factory_implementation é adicionar SDKTemplate ao seu namespace.

// MainPage.h
#pragma once
#include "MainPage.g.h"

namespace winrt::SDKTemplate::implementation
{
    struct MainPage : MainPageT<MainPage>
    {
        MainPage();

        static SDKTemplate::MainPage Current();
        static hstring FEATURE_NAME();
        static Windows::Foundation::Collections::IVector<SDKTemplate::Scenario> scenarios();
        void NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type);
    };
}
namespace winrt::SDKTemplate::factory_implementation
{
    struct MainPage : MainPageT<MainPage, implementation::MainPage>
    {
    };
}
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"

namespace winrt::SDKTemplate::implementation
{
    MainPage::MainPage()
    {
        InitializeComponent();
    }
    SDKTemplate::MainPage MainPage::Current()
    {
        throw hresult_not_implemented();
    }
    hstring MainPage::FEATURE_NAME()
    {
        throw hresult_not_implemented();
    }
    Windows::Foundation::Collections::IVector<SDKTemplate::Scenario> MainPage::scenarios()
    {
        throw hresult_not_implemented();
    }
    void MainPage::NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type)
    {
        throw hresult_not_implemented();
    }
}

Para strings, C# usa System.String. Consulte o método MainPage.NotifyUser para obter um exemplo. No nosso IDL, declaramos uma string com String, e quando a ferramenta cppwinrt.exe gera código C++/WinRT para nós, ela utiliza o tipo winrt::hstring. Sempre que nos depararmos com uma cadeia de caracteres no código C#, a portaremos para winrt::hstring. Para obter mais informações, consulte Manipulação de cadeia de caracteres em C++/WinRT.

Para obter uma explicação dos parâmetros const& nas assinaturas do método, consulte Parameter-passing.

Atualize todas as restantes declarações/referências de namespace e compile.

Antes de criar o projeto C++/WinRT, localize todas as declarações de (e referências a) do namespace Clipboard e altere-as para SDKTemplate.

  • MainPage.xaml e App.xaml. O namespace aparece nos valores dos x:Class e xmlns:local atributos.
  • App.idl.
  • App.h.
  • App.cpp. Há duas diretivas using namespace (procure a substring using namespace Clipboard) e duas qualificações do tipo MainPage (procure Clipboard::MainPage). Estes precisam de mudar.

Como removemos o manipulador de eventos de MainPage, também entramos em e excluímos o elemento Button da marcação.

Salve todos os arquivos. Limpe a solução (Build>Clean Solution) e, em seguida, compile-a. Tendo seguido todas as mudanças até agora, exatamente como escrito, espera-se que a compilação tenha sucesso.

Implementar os membros da MainPage que declaramos no IDL

O construtor, Currente FEATURE_NAME

Aqui está o código relevante (do projeto C#) que precisamos portar.

<!-- MainPage.xaml -->
...
<TextBlock x:Name="SampleTitle" ... />
...
// MainPage.xaml.cs
...
public sealed partial class MainPage : Page
{
    public static MainPage Current;

    public MainPage()
    {
        InitializeComponent();
        Current = this;
        SampleTitle.Text = FEATURE_NAME;
    }
...
}
...

// SampleConfiguration.cs
...
public partial class MainPage : Page
{
    public const string FEATURE_NAME = "Clipboard C# sample";
...
}
...

Em breve, iremos reutilizar MainPage.xaml na íntegra (copiando-o). Para já (abaixo), iremos adicionar temporariamente um elemento TextBlock , com o nome apropriado, dentro do MainPage.xaml do projeto C++/WinRT.

FEATURE_NAME é um campo estático de MainPage (um campo const C# é essencialmente estático no seu comportamento), definido em SampleConfiguration.cs. Para C++/WinRT, em vez de um campo (estático), vamos transformá-lo na forma de uma propriedade somente leitura (estática) expressa em C++/WinRT. A maneira C++/WinRT de expressar um getter de propriedade é como uma função que retorna o valor da propriedade e não usa parâmetros (um acessador). Assim, o campo estático C# FEATURE_NAME torna-se a função de acessador estático C++/WinRT FEATURE_NAME (neste caso, retornando a cadeia de caracteres literal).

Aliás, faríamos a mesma coisa se estivéssemos portando uma propriedade de leitura apenas do C#. Para uma propriedade editável em C#, a forma C++/WinRT de expressar um setter de propriedade é como uma função void que recebe o valor da propriedade como parâmetro (mutator). Em ambos os casos, se o campo ou propriedade C# for estático, o mesmo acontecerá com o acessador e/ou mutador C++/WinRT.

atual é um campo estático (e não constante) de MainPage. Mais uma vez, iremos torná-la uma propriedade somente de leitura (a expressão C++/WinRT de) e, mais uma vez, torná-la estática. Onde FEATURE_NAME é constante, Atual não é. Então, em C++/WinRT precisaremos de um campo de suporte, e nosso acessador retornará isso. Assim, no projeto C++/WinRT, declararemos em MainPage.h um campo estático privado chamado atual, definiremos/inicializaremos a corrente em MainPage.cpp (porque ela tem duração de armazenamento estático) e a acessaremos por meio de uma função de acessador estático pública chamada Atual.

O próprio construtor executa algumas tarefas, que são simples de portar.

No projeto C++/WinRT, adicione um novo item Visual C++>Code>C++ File (.cpp) com o nome SampleConfiguration.cpp.

Edite MainPage.xaml, MainPage.h, MainPage.cppe SampleConfiguration.cpp para corresponder às listagens abaixo.

<!-- MainPage.xaml -->
...
<StackPanel ...>
    <TextBlock x:Name="SampleTitle" />
</StackPanel>
...
// MainPage.h
...
namespace winrt::SDKTemplate::implementation
{
    struct MainPage : MainPageT<MainPage>
    {
...
        static SDKTemplate::MainPage Current() { return current; }
...
    private:
        static SDKTemplate::MainPage current;
...
    };
...
}

// MainPage.cpp
...
namespace winrt::SDKTemplate::implementation
{
    SDKTemplate::MainPage MainPage::current{ nullptr };

    MainPage::MainPage()
    {
        InitializeComponent();
        MainPage::current = *this;
        SampleTitle().Text(FEATURE_NAME());
    }
...
}

// SampleConfiguration.cpp
#include "pch.h"
#include "MainPage.h"

using namespace winrt;
using namespace SDKTemplate;

hstring implementation::MainPage::FEATURE_NAME()
{
    return L"Clipboard C++/WinRT Sample";
}

Além disso, certifique-se de excluir os corpos de função existentes de MainPage.cppMainPage::Current() e MainPage::FEATURE_NAME(), porque agora estamos definindo esses métodos em outro lugar.

Como você pode ver, MainPage::current é declarado como sendo do tipo SDKTemplate::MainPage, que é o tipo projetado. Não é do tipo SDKTemplate::implementation::MainPage, que é o tipo de implementação. O tipo projetado é aquele que foi concebido para ser consumido, quer seja dentro do projeto para a interoperação com XAML, quer entre binários. O tipo de implementação é aquilo que utilizas para implementar as funcionalidades que expuseste no tipo projetado. Como a declaração de MainPage::current (em ) aparece dentro do namespace de implementação (winrt::SDKTemplate::implementation), um MainPage de não qualificado teria se referido ao tipo de implementação. Assim, qualificamos com SDKTemplate:: para deixar claro que queremos que MainPage::current seja uma instância do tipo projetado winrt::SDKTemplate::MainPage.

No construtor, existem alguns pontos relacionados a MainPage::current = *this; que merecem uma explicação.

  • Quando você usa o ponteiro this dentro de um membro do tipo de implementação, o ponteiro this é, naturalmente, um ponteiro para o tipo de implementação.
  • Para converter o ponteiro this para o tipo projetado correspondente, desreferencie-o. Desde que você gere seu tipo de implementação a partir do IDL (como temos aqui), o tipo de implementação tem um operador de conversão que converte para seu tipo projetado. É por isso que a tarefa aqui funciona.

Para obter mais informações sobre esses detalhes, consulte Instanciando e retornando tipos de implementação e interfaces.

Também está no construtor o SampleTitle().Text(FEATURE_NAME());. A parte SampleTitle() é uma chamada para uma função de acesso simples chamada SampleTitle, que retorna o TextBlock que adicionámos ao XAML. Sempre que você x:Name um elemento XAML, o compilador XAML gera um acessador para você com o nome do elemento. A parte .Text(...) invoca a função mutadora Text no objeto TextBlock que o acessador SampleTitle retornou. E FEATURE_NAME() chama nossa função de acessador estático MainPage::FEATURE_NAME para retornar a string literal. No conjunto, essa linha de código define a propriedade Text do TextBlock chamada SampleTitle.

Observe que, como as cadeias de caracteres são amplas no Tempo de Execução do Windows, para portar um literal de cadeia de caracteres, nós a prefixamos com o prefixo de codificação wide-char L. Assim, mudamos (por exemplo) "a string literal" para L"a string literal". Consulte também Literais de cadeia larga.

Cenários

Aqui está o código C# relevante que precisamos portar.

// MainPage.xaml.cs
...
public sealed partial class MainPage : Page
{
...
    public List<Scenario> Scenarios
    {
        get { return this.scenarios; }
    }
...
}
...

// SampleConfiguration.cs
...
public partial class MainPage : Page
{
...
    List<Scenario> scenarios = new List<Scenario>
    {
        new Scenario() { Title = "Copy and paste text", ClassType = typeof(CopyText) },
        new Scenario() { Title = "Copy and paste an image", ClassType = typeof(CopyImage) },
        new Scenario() { Title = "Copy and paste files", ClassType = typeof(CopyFiles) },
        new Scenario() { Title = "Other Clipboard operations", ClassType = typeof(OtherScenarios) }
    };
...
}
...

Da nossa investigação anterior, sabemos que esta coleção de objetos Scenario está a ser exibida numa ListBox. Em C++/WinRT, há limites para o tipo de de coleção que podemos atribuir à propriedade ItemsSource de um controle de itens. A coleção deve ser um vetor ou um vetor observável, e seus elementos devem ser um dos seguintes:

Para o caso IInspectable , se os elementos não forem eles próprios classes de tempo de execução, então esses elementos precisam ser de um tipo que pode ser encapsulado e desencapsulado de e para IInspectable. E isso significa que eles devem ser tipos de Tempo de Execução do Windows (consulte Encaixotamento e desencaixotamento de valores para IInspectable).

Para este estudo de caso, não fizemos do Scenario uma classe em tempo de execução. No entanto, essa continua a ser uma opção razoável. E haverá casos no seu próprio trabalho de portabilidade em que uma classe de tempo de execução sem dúvida será a melhor opção. Por exemplo, se você precisar tornar o tipo de elemento observável (consulte controles XAML; vincular a uma propriedade C++/WinRT), ou se o elemento precisar ter métodos por qualquer outro motivo, e for mais do que apenas um conjunto de membros de dados.

Uma vez que, neste passo a passo, não vamos com uma classe de tempo de execução para o tipo Scenario, então precisamos pensar no boxe. Se tivéssemos feito Scenario um structC++ regular, não conseguiríamos encaixotá-lo. Mas declaramos Cenário como um struct no IDL, e por isso podemos encaixotá-lo.

Resta-nos a opção de preparar o do Cenário com antecedência ou esperar até estarmos prestes a atribuir ao ItemsSource e pré-prepará-los conforme necessário. Aqui estão algumas considerações sobre essas duas opções.

  • Boxe antes do tempo. Para essa opção, nosso membro de dados é uma coleção de IInspectable pronto para atribuir à interface do usuário. Na inicialização, encaixotamos os objetos Scenario nesse membro de dados. Precisamos apenas de um exemplar dessa coleção, mas temos que desencaixotar um elemento sempre que precisamos de ler os seus campos.
  • Boxe na hora certa. Para esta opção, nosso membro de dados é uma coleção de Scenario. Quando chegar a hora de atribuir à UI, encapsulamos os objetos Scenario do membro de dados em uma nova coleção de IInspectable. Podemos ler os campos dos elementos no membro de dados sem unboxing, mas precisamos de duas cópias da coleção.

Como pode ver, para uma pequena coleção como esta, os prós e contras fazem dela algo neutro. Então, para este estudo de caso, vamos optar pela opção just-in-time.

Os cenários membro é um campo de MainPage, definido e inicializado em SampleConfiguration.cs. E Scenarios é uma propriedade somente leitura de MainPage, definida em MainPage.xaml.cs (e implementada para simplesmente retornar o campo de cenários ). Faremos algo semelhante no projeto C++/WinRT; mas tornaremos os dois membros estáticos (já que precisamos de apenas uma instância em todo o aplicativo; e para que possamos acessá-los sem precisar de uma instância de classe). E vamos nomeá-los cenários internos e cenários , respectivamente. Vamos declarar cenáriosInner em MainPage.h. E, como ele tem duração de armazenamento estático, vamos defini-lo/inicializá-lo em um .cpp arquivo (SampleConfiguration.cpp, neste caso).

Edite MainPage.h e SampleConfiguration.cpp para corresponder às listagens abaixo.

// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
    static Windows::Foundation::Collections::IVector<Scenario> scenarios() { return scenariosInner; }
...
private:
    static winrt::Windows::Foundation::Collections::IVector<Scenario> scenariosInner;
...
};

// SampleConfiguration.cpp
...
using namespace Windows::Foundation::Collections;
...
IVector<Scenario> implementation::MainPage::scenariosInner = winrt::single_threaded_observable_vector<Scenario>(
{
    Scenario{ L"Copy and paste text", xaml_typename<SDKTemplate::CopyText>() },
    Scenario{ L"Copy and paste an image", xaml_typename<SDKTemplate::CopyImage>() },
    Scenario{ L"Copy and paste files", xaml_typename<SDKTemplate::CopyFiles>() },
    Scenario{ L"History and roaming", xaml_typename<SDKTemplate::HistoryAndRoaming>() },
    Scenario{ L"Other Clipboard operations", xaml_typename<SDKTemplate::OtherScenarios>() },
});

Além disso, certifique-se de excluir o corpo da função existente do MainPage.cpp para MainPage::scenarios(), porque agora estamos definindo esse método no arquivo de cabeçalho.

Como se pode ver, em SampleConfiguration.cpp, inicializamos o membro de dados estático cenáriosInner chamando uma função auxiliar C++/WinRT chamada winrt::single_threaded_observable_vector. Essa função cria um novo objeto de coleção do Tempo de Execução do Windows para nós e o retorna como um interface de IObservableVector. Neste exemplo, como a coleção não é observável (não precisa ser, porque não adiciona nem remove elementos após a inicialização), temos a opção de chamar winrt::single_threaded_vector. Essa função retorna a coleção como uma interface IVector.

Para saber mais sobre coleções e vinculá-las, veja controles de itens XAML; vincular a uma coleção C++/WinRTe coleções com C++/WinRT.

O código de inicialização que você acabou de adicionar faz referência a tipos que ainda não estão no projeto (por exemplo, winrt::SDKTemplate::CopyText. Para corrigir isso, vamos continuar e adicionar cinco novas páginas XAML em branco ao projeto.

Adicionar cinco novas páginas XAML em branco

Adicione um novo Visual C++>Blank Page (C++/WinRT) item ao projeto (certifique-se de que é o modelo de item Página em Branco (C++/WinRT) e não o Página em Branco um). Dê-lhe o nome de CopyText. A nova página XAML é definida dentro do namespace SDKTemplate , que é o que queremos.

Repita o processo acima mais quatro vezes e nomeie as páginas CopyImageXAML , CopyFiles, HistoryAndRoaminge OtherScenarios.

Agora você poderá construir novamente, se desejar.

NotificarUtilizador

No projeto C#, você encontrará a implementação do método MainPage.NotifyUser em MainPage.xaml.cs. MainPage.NotifyUser tem uma dependência em MainPage.UpdateStatus, e esse método, por sua vez, tem dependências nos elementos XAML que ainda não foram portados. Então, por enquanto, vamos apenas criar um esboço de um método UpdateStatus no projeto C++/WinRT, e vamos migrá-lo mais tarde.

Aqui está o código C# relevante que precisamos portar.

// MainPage.xaml.cs
...
public void NotifyUser(string strMessage, NotifyType type)
if (Dispatcher.HasThreadAccess)
{
    UpdateStatus(strMessage, type);
}
else
{
    var task = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => UpdateStatus(strMessage, type));
}
private void UpdateStatus(string strMessage, NotifyType type) { ... }{
...

NotifyUser usa o Windows.UI.Core.CoreDispatcherPriority enum. Em C++/WinRT, sempre que quiser usar um tipo de namespaces do Windows, você precisará incluir o arquivo de cabeçalho de namespace do Windows C++/WinRT correspondente (para obter mais informações sobre isso, consulte Introdução ao C++/WinRT). Neste caso, como você verá na lista de códigos abaixo, o cabeçalho é winrt/Windows.UI.Core.h, e vamos incluí-lo em pch.h.

UpdateStatus é privado. Portanto, faremos disso um método privado em nosso tipo de implementação MainPage . UpdateStatus não se destina a ser chamado na classe runtime, portanto, não o declararemos no IDL.

Depois de portar MainPage.NotifyUsere extrair MainPage.UpdateStatus , isso é o que temos no projeto C++/WinRT. Após esta listagem de código, examinaremos alguns dos detalhes.

// pch.h
...
#include <winrt/Windows.UI.Core.h>
...

// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
    void NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type);
...
private:
    void UpdateStatus(hstring const& strMessage, SDKTemplate::NotifyType const& type);
...
};

// MainPage.cpp
...
void MainPage::NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
    if (Dispatcher().HasThreadAccess())
    {
        UpdateStatus(strMessage, type);
    }
    else
    {
        Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [strMessage, type, this]()
            {
                UpdateStatus(strMessage, type);
            });
    }
}
void MainPage::UpdateStatus(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
    throw hresult_not_implemented();
}
...

Em C#, você pode usar a notação de ponto para aceder a propriedades aninhadas. Assim, o tipo C# MainPage pode aceder à sua própria propriedade Dispatcher com a sintaxe Dispatcher. E o C# pode ainda ponto em esse valor com sintaxe como Dispatcher.HasThreadAccess. Em C++/WinRT, as propriedades são implementadas como funções de acessador, portanto, a sintaxe difere apenas na medida em que você adiciona parênteses para cada chamada de função.

C# C++/WinRT
Dispatcher.HasThreadAccess Dispatcher().HasThreadAccess()

Quando a versão em C# do NotifyUser chama CoreDispatcher.RunAsync, ele implementa o delegado de retorno de chamada assíncrono como uma função lambda. A versão C++/WinRT faz a mesma coisa, mas a sintaxe é um pouco diferente. Em C++/WinRT, capturamos os dois parâmetros que vamos usar, bem como o ponteiro this (já que vamos chamar uma função de membro). Há mais informações sobre como implementar delegados como lambdas e exemplos de código no tópico Manipular eventos usando delegados em C++/WinRT. Além disso, podemos desconsiderar a var task = parte neste caso específico. Não estamos esperando o objeto assíncrono retornado, portanto, não há necessidade de armazená-lo.

Implementar os membros restantes do MainPage

Vamos fazer uma lista completa dos membros do MainPage (implementado em MainPage.xaml.cs e SampleConfiguration.cs) para que possamos ver quais transferimos até agora e quais ainda estão para finalizar.

Membro Acesso Situação
MainPage construtor public Portado
Propriedade atual public Portado
FEATURE_NAME propriedade public Portado
propriedade IsClipboardContentChangedEnabled public Não iniciado
Cenários propriedade public Portado
Método BuildClipboardFormatsOutputString public Não iniciado
método DisplayToast public Não iniciado
Método de EnableClipboardContentChangedNotifications public Não iniciado
método NotifyUser public Portado
método OnNavigatedTo protected Não iniciado
isApplicationWindowActive campo private Não iniciado
campo needToPrintClipboardFormat private Não iniciado
cenários campo private Portado
Método Button_Click private Não iniciado
método DisplayChangedFormats private Não iniciado
Método Footer_Click private Não iniciado
método HandleClipboardChanged private Não iniciado
método OnClipboardChanged private Não iniciado
método OnWindowActivated private Não iniciado
ScenarioControl_SelectionChanged método private Não iniciado
método UpdateStatus private Eliminado

Então, falaremos dos membros ainda não portados nas próximas subseções.

Observação

De tempos em tempos, encontraremos referências no código-fonte a elementos da interface do usuário na marcação XAML (em MainPage.xaml). À medida que chegamos a essas referências, vamos contornando-as temporariamente, adicionando simples elementos de espaço reservado ao XAML. Dessa forma, o projeto continuará a ser construído após cada subseção. A alternativa é resolver as referências copiando todo o conteúdo de MainPage.xaml do projeto C# para o projeto C++/WinRT agora. Mas se fizermos isso, levará muito tempo até que possamos chegar a um pit stop e construir novamente (potencialmente obscurecendo quaisquer erros de digitação ou outros erros que cometemos ao longo do caminho).

Quando terminarmos de portar o código imperativo para a classe MainPage, então copiaremos o conteúdo do arquivo XAML e ficaremos confiantes de que o projeto continuará a ser compilado.

IsClipboardContentChangedEnabled

Esta é uma propriedade em C# com get e set, cujo valor padrão é false. É um membro da MainPage e é definido em SampleConfiguration.cs.

Para C++/WinRT, precisaremos de uma função acessora, uma função de mutação e um membro de dados de suporte como campo. Como IsClipboardContentChangedEnabled representa o estado de um dos cenários do exemplo, em vez do estado de MainPage propriamente dito, criaremos os novos membros num novo tipo de utilitário chamado SampleState. E implementaremos isso em nosso SampleConfiguration.cpp arquivo de código-fonte, e tornaremos os membros static (já que precisamos de apenas uma instância em todo o aplicativo; e para que possamos acessá-los sem precisar de uma instância de classe).

Para acompanhar o nosso SampleConfiguration.cpp no projeto C++/WinRT, adicione um novo item Visual C++>Code>Header File (.h) com o nome SampleConfiguration.h. Edite SampleConfiguration.h e SampleConfiguration.cpp para corresponder às listagens abaixo.

// SampleConfiguration.h
#pragma once 
#include "pch.h"

namespace winrt::SDKTemplate
{
    struct SampleState
    {
        static bool IsClipboardContentChangedEnabled();
        static void IsClipboardContentChangedEnabled(bool checked);
    private:
        static bool isClipboardContentChangedEnabled;
    };
}

// SampleConfiguration.cpp
...
#include "SampleConfiguration.h"
...
bool SampleState::isClipboardContentChangedEnabled = false;
...
bool SampleState::IsClipboardContentChangedEnabled()
{
    return isClipboardContentChangedEnabled;
}
void SampleState::IsClipboardContentChangedEnabled(bool checked)
{
    if (isClipboardContentChangedEnabled != checked)
    {
        isClipboardContentChangedEnabled = checked;
    }
}

Novamente, um campo com static armazenamento (como SampleState::isClipboardContentChangedEnabled) deve ser definido uma vez no aplicativo, e um .cpp arquivo é um bom lugar para isso (SampleConfiguration.cpp neste caso).

BuildClipboardFormatsOutputString

Esse método é um membro público da MainPage e é definido em SampleConfiguration.cs.

// SampleConfiguration.cs
...
public string BuildClipboardFormatsOutputString()
{
    DataPackageView clipboardContent = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent();
    StringBuilder output = new StringBuilder();

    if (clipboardContent != null && clipboardContent.AvailableFormats.Count > 0)
    {
        output.Append("Available formats in the clipboard:");
        foreach (var format in clipboardContent.AvailableFormats)
        {
            output.Append(Environment.NewLine + " * " + format);
        }
    }
    else
    {
        output.Append("The clipboard is empty");
    }
    return output.ToString();
}
...

Em C++/WinRT, tornaremos o BuildClipboardFormatsOutputString um método estático público de SampleState. Podemos torná-lo static porque ele não acessa nenhum membro da instância.

Para usar o Área de Transferência e tipos de DataPackageView em C++/WinRT, precisaremos incluir o arquivo de cabeçalho de namespace do Windows C++/WinRT .

Em C#, a propriedade DataPackageView.AvailableFormats é um IReadOnlyList, assim podemos acessar a propriedade Count daquela. Em C++/WinRT, a função accessor DataPackageView::AvailableFormats retorna um IVectorView, que tem uma função accessor Size que podemos chamar.

Para portar o uso do tipo de System.Text.StringBuilder C#, usaremos o tipo C++ padrão std::wostringstream. Esse tipo é um fluxo de saída para cadeias de caracteres largas (e para usá-lo precisaremos incluir o sstream arquivo de cabeçalho). Em vez de usar um método Append como faz com um StringBuilder, utiliza-se o operador de inserção (<<) com um fluxo de saída, como o wostringstream. Para saber mais, veja programação iostreame Formatando cadeias de caracteres C++/WinRT.

O código C# constrói um StringBuilder com a new palavra-chave. Em C#, objetos são tipos de referência por padrão, declarados no heap com new. No C++ padrão moderno, por padrão, os objetos são tipos de valor, declarados na pilha (sem usar new). Então, nós portamos StringBuilder output = new StringBuilder(); para C++/WinRT como simplesmente std::wostringstream output;.

A palavra-chave C# var pede ao compilador para inferir um tipo. Você porta var para auto com C++/WinRT. Mas em C++/WinRT, há casos em que (para evitar cópias) você deseja uma referência a um tipo inferido (ou deduzido) e expressa uma referência lvalue a um tipo deduzido com auto&. Há também casos em que se deseja um tipo especial de referência que se liga corretamente, quer seja inicializada com um lvalue ou com um rvalue . E você expressa isso com auto&&. Essa é a forma que você vê usada no for loop no código portado abaixo. Para obter uma introdução aos lvalues e rvalues, veja Categorias de valor e referências a elas.

Edite pch.h, SampleConfiguration.he SampleConfiguration.cpp para corresponder às listagens abaixo.

// pch.h
...
#include <sstream>
#include "winrt/Windows.ApplicationModel.DataTransfer.h"
...

// SampleConfiguration.h
...
struct SampleState
{
    static hstring BuildClipboardFormatsOutputString();
    ...
}
...

// SampleConfiguration.cpp
...
using namespace Windows::ApplicationModel::DataTransfer;
...
hstring SampleState::BuildClipboardFormatsOutputString()
{
    DataPackageView clipboardContent{ Clipboard::GetContent() };
    std::wostringstream output;

    if (clipboardContent && clipboardContent.AvailableFormats().Size() > 0)
    {
        output << L"Available formats in the clipboard:";
        for (auto&& format : clipboardContent.AvailableFormats())
        {
            output << std::endl << L" * " << std::wstring_view(format);
        }
    }
    else
    {
        output << L"The clipboard is empty";
    }

    return hstring{ output.str() };
}

Observação

A sintaxe na linha de código DataPackageView clipboardContent{ Clipboard::GetContent() }; usa um recurso do C++ padrão moderno chamado inicialização uniforme, com seu uso característico de colchetes encaracolados em vez de um sinal de =. Essa sintaxe deixa claro que a inicialização, em vez da atribuição, está ocorrendo. Se você preferir a forma de sintaxe que se parece atribuição (mas na verdade não é), então você pode substituir a sintaxe acima pelo equivalente DataPackageView clipboardContent = Clipboard::GetContent();. No entanto, é uma boa ideia se sentir confortável com ambas as maneiras de expressar a inicialização, porque é provável que você veja ambas usadas com frequência no código encontrado.

DisplayToast

DisplayToast é um método estático público da classe MainPage C# e você o encontrará definido em SampleConfiguration.cs. Em C++/WinRT, vamos torná-lo um método estático público de SampleState.

Já encontramos a maioria dos detalhes e técnicas relevantes para a adaptação desse método. Um novo item a ser observado é que você converta um literal de cadeia de caracteres verbatim C# (@) para um literal de cadeia de caracteres bruto padrão C++ (LR).

Além disso, ao fazer referência aos tipos ToastNotification e XmlDocument em C++/WinRT, pode qualificá-los através do nome do namespace ou pode editar SampleConfiguration.cpp e adicionar diretivas using namespace, conforme o exemplo a seguir.

using namespace Windows::UI::Notifications;

Você tem a mesma opção quando faz referência ao tipo de XmlDocument e sempre que faz referência a qualquer outro tipo do Tempo de Execução do Windows.

Além desses itens, basta seguir as mesmas orientações que você fez anteriormente para realizar as etapas a seguir.

  • Declare o método em SampleConfiguration.he defina-o em SampleConfiguration.cpp.
  • Edite pch.h para incluir quaisquer arquivos de cabeçalho de namespace C++/WinRT do Windows necessários.
  • Construa objetos C++/WinRT na pilha, não na heap.
  • Substitua chamadas aos acessores de propriedades de obtenção pela sintaxe de chamada de função (()).

Uma causa muito comum de erros do compilador/vinculador é esquecer de incluir os arquivos de cabeçalho de namespace do Windows C++/WinRT que você precisa. Para obter mais informações sobre um possível erro, consulte C3779: Por que o compilador está me dando um erro "consume_Something: a função que retorna 'auto' não pode ser usada antes de ser definida?.

Se você quiser acompanhar o passo a passo e a porta DisplayToast você mesmo, então você pode comparar seus resultados com o código na versão C++/WinRT no ZIP do Clipboard exemplo código-fonte que você baixou.

EnableClipboardContentChangedNotifications

EnableClipboardContentChangedNotifications é um método estático público da classe MainPage C# e é definido em SampleConfiguration.cs.

// SampleConfiguration.cs
...
public bool EnableClipboardContentChangedNotifications(bool enable)
{
    if (IsClipboardContentChangedEnabled == enable)
    {
        return false;
    }

    IsClipboardContentChangedEnabled = enable;
    if (enable)
    {
        Clipboard.ContentChanged += OnClipboardChanged;
        Window.Current.Activated += OnWindowActivated;
    }
    else
    {
        Clipboard.ContentChanged -= OnClipboardChanged;
        Window.Current.Activated -= OnWindowActivated;
    }
    return true;
}
...
private void OnClipboardChanged(object sender, object e) { ... }
private void OnWindowActivated(object sender, WindowActivatedEventArgs e) { ... }
...

Em C++/WinRT, vamos torná-lo um método estático público de SampleState.

Em C#, você usa a sintaxe do operador += e -= para registrar e revogar delegados de manipulação de eventos. Em C++/WinRT, você tem várias opções sintáticas para registrar/revogar um delegado, conforme descrito em Manipular eventos usando delegados em C++/WinRT. Mas a forma geral é que você se registre e revogue com chamadas para um par de funções nomeadas para o evento. Para vos registar, passais o vosso delegado para a função de registo e recuperais um token de revogação em troca (um winrt::event_token). Para revogar, você passa esse token para a função de revogação. Neste caso, o hander é estático e (como você pode ver na lista de código a seguir) a sintaxe de chamada de função é simples.

Tokens semelhantes, como e, estão a ser realmente usados, por detrás dos bastidores, em C#. Mas a linguagem torna implícito esse detalhe. C++/WinRT torna isso explícito.

O tipo de objeto aparece nas assinaturas de manipuladores de eventos C#. Na linguagem C#, o objeto é uma alcunha para o tipo System.Object em .NET . O equivalente em C++/WinRT é winrt::Windows::Foundation::IInspectable. Portanto, você verá IInspectable nos manipuladores de eventos C++/WinRT.

Edite SampleConfiguration.h e SampleConfiguration.cpp para corresponder às listagens abaixo.

// SampleConfiguration.h
...
    static bool EnableClipboardContentChangedNotifications(bool enable);
    ...
private:
    ...
    static event_token clipboardContentChangedToken;
    static event_token activatedToken;
    static void OnClipboardChanged(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
    static void OnWindowActivated(Windows::Foundation::IInspectable const& sender, Windows::UI::Core::WindowActivatedEventArgs const& e);
...

// SampleConfiguration.cpp
...
using namespace Windows::Foundation;
using namespace Windows::UI::Core;
using namespace Windows::UI::Xaml;
...
event_token SampleState::clipboardContentChangedToken;
event_token SampleState::activatedToken;
...
bool SampleState::EnableClipboardContentChangedNotifications(bool enable)
{
    if (isClipboardContentChangedEnabled == enable)
    {
        return false;
    }

    IsClipboardContentChangedEnabled(enable);
    if (enable)
    {
        clipboardContentChangedToken = Clipboard::ContentChanged(OnClipboardChanged);
        activatedToken = Window::Current().Activated(OnWindowActivated);
    }
    else
    {
        Clipboard::ContentChanged(clipboardContentChangedToken);
        Window::Current().Activated(activatedToken);
    }
    return true;
}
void SampleState::OnClipboardChanged(IInspectable const&, IInspectable const&){}
void SampleState::OnWindowActivated(IInspectable const&, WindowActivatedEventArgs const& e){}

Deixe os próprios delegados de manipulação de eventos (OnClipboardChanged e OnWindowActivated) como stubs por enquanto. Eles já estão na nossa lista de membros a transferir, então tratamos deles em subseções posteriores.

OnNavigatedTo

OnNavigatedTo é um método protegido da classe MainPage C# e é definido em MainPage.xaml.cs. Aqui está, juntamente com o ListBox XAML referenciado por ele.

<!-- MainPage.xaml -->
...
<ListBox x:Name="ScenarioControl" ... />
...
// MainPage.xaml.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    // Populate the scenario list from the SampleConfiguration.cs file
    var itemCollection = new List<Scenario>();
    int i = 1;
    foreach (Scenario s in scenarios)
    {
        itemCollection.Add(new Scenario { Title = $"{i++}) {s.Title}", ClassType = s.ClassType });
    }
    ScenarioControl.ItemsSource = itemCollection;

    if (Window.Current.Bounds.Width < 640)
    {
        ScenarioControl.SelectedIndex = -1;
    }
    else
    {
        ScenarioControl.SelectedIndex = 0;
    }
}

É um método importante e interessante, porque é aqui que nossa coleção de objetos Scenario é atribuída à interface do usuário. O código C# cria um System.Collections.Generic.List de objetos do tipo Scenario e atribui-o à propriedade ItemsSource de um ListBox (que é um controle de itens). E, em C#, usamos a interpolação de cadeia de caracteres para criar o título para cada objeto Cenário (observe o uso do caractere especial $).

Em C++/WinRT, tornaremos OnNavigatedTo um método público de MainPage. E adicionaremos um stub elemento ListBox ao XAML para que uma compilação seja bem-sucedida. Após a listagem de código, examinaremos alguns dos detalhes.

<!-- MainPage.xaml -->
...
<StackPanel ...>
    ...
    <ListBox x:Name="ScenarioControl" />
</StackPanel>
...
// MainPage.h
...
void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
...

// MainPage.cpp
...
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Navigation;
...
void MainPage::OnNavigatedTo(NavigationEventArgs const& /* e */)
{
    auto itemCollection = winrt::single_threaded_observable_vector<IInspectable>();
    int i = 1;
    for (auto s : MainPage::scenarios())
    {
        s.Title = winrt::to_hstring(i++) + L") " + s.Title;
        itemCollection.Append(winrt::box_value(s));
    }
    ScenarioControl().ItemsSource(itemCollection);

    if (Window::Current().Bounds().Width < 640)
    {
        ScenarioControl().SelectedIndex(-1);
    }
    else
    {
        ScenarioControl().SelectedIndex(0);
    }
}
...

Novamente, estamos chamando a função winrt::single_threaded_observable_vetor, mas desta vez para criar uma coleção de IInspectable. Isso foi parte da nossa decisão de realizar o encapsulamento dos nossos objetos Scenario numa base just-in-time.

E, em vez do uso do C# de interpolação de cadeia de caracteres aqui, usamos uma combinação da função to_hstring e o operador de concatenação de winrt::hstring.

janelaDeAplicaçãoEstáAtiva

Em C#, isApplicationWindowActive é um campo privado simples pertencente à classe MainPage e é definido em . O padrão é false. Em C++/WinRT, vamos torná-lo um campo estático privado de SampleState (pelos motivos que já descrevemos) nos SampleConfiguration.h arquivos e SampleConfiguration.cpp , com o mesmo padrão.

Já vimos como declarar, definir e inicializar um campo estático. Para relembrar, olhe para o que fizemos com o campo isClipboardContentChangedEnabled e faça o mesmo com isApplicationWindowActive.

precisaImprimirFormatoDaÁreaDeTransferência

Mesmo padrão que isApplicationWindowActive (veja o título imediatamente antes deste).

Botão_Clique

Button_Click é um método privado (manipulação de eventos) da classe MainPage C# e é definido em MainPage.xaml.cs. Aqui está, juntamente com o SplitView XAML que ele referencia e o ToggleButton que o registra.

<!-- MainPage.xaml -->
...
<SplitView x:Name="Splitter" ... />
...
<ToggleButton Click="Button_Click" .../>
...
private void Button_Click(object sender, RoutedEventArgs e)
{
    Splitter.IsPaneOpen = !Splitter.IsPaneOpen;
}

E aqui está o equivalente, portado para C++/WinRT. Observe que na versão C++/WinRT, o manipulador de eventos é public (como você pode ver, você o declara antes de as declarações private:). Isso ocorre porque um manipulador de eventos registrado na marcação XAML, como este, precisa estar public em C++/WinRT para que a marcação XAML possa acessá-lo. Por outro lado, se você registrar um manipulador de eventos em código imperativo (como fizemos em MainPage::EnableClipboardContentChangedNotifications anteriormente), o manipulador de eventos não precisará ser public.

<!-- MainPage.xaml -->
...
<StackPanel ...>
    ...
    <SplitView x:Name="Splitter" />
</StackPanel>
...
// MainPage.h
...
    void Button_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
private:
...

// MainPage.cpp
void MainPage::Button_Click(Windows::Foundation::IInspectable const& /* sender */, Windows::UI::Xaml::RoutedEventArgs const& /* e */)
{
    Splitter().IsPaneOpen(!Splitter().IsPaneOpen());
}

FormatosAlteradosDoEcrã

Em C#, DisplayChangedFormats é um método privado pertencente à classe MainPage e é definido em SampleConfiguration.cs.

private void DisplayChangedFormats()
{
    string output = "Clipboard content has changed!" + Environment.NewLine;
    output += BuildClipboardFormatsOutputString();
    NotifyUser(output, NotifyType.StatusMessage);
}

Em C++/WinRT, faremos dele um campo estático privado de SampleState (ele não acessa nenhum membro da instância), nos SampleConfiguration.h arquivos e SampleConfiguration.cpp . O código C# para este método não usa System.Text.StringBuilder; mas realiza formatações de strings suficientes para que na versão C++/WinRT este seja outro bom lugar para usar std::wostringstream.

Em vez da propriedade estática System.Environment.NewLine , que é usada no código C#, inseriremos o C++ std::endl padrão (um caractere de nova linha) no fluxo de saída.

// SampleConfiguration.h
...
private:
    static void DisplayChangedFormats();
...

// SampleConfiguration.cpp
void SampleState::DisplayChangedFormats()
{
    std::wostringstream output;
    output << L"Clipboard content has changed!" << std::endl;
    output << BuildClipboardFormatsOutputString().c_str();
    MainPage::Current().NotifyUser(output.str(), NotifyType::StatusMessage);
}

Há uma pequena ineficiência no design da versão C++/WinRT acima. Primeiro, criamos um std::wostringstream. Mas também chamamos o método BuildClipboardFormatsOutputString (que portamos anteriormente). Esse método cria seu próprio std::wostringstream. E transforma o seu fluxo num winrt::hstring e retorna esse valor. Chamamos a função hstring::c_str para transformar o retornado hstring em uma cadeia de caracteres no estilo C e, em seguida, inserimos isso em nosso fluxo. Seria mais eficiente criar apenas um std::wostringstream, e passar (uma referência a) isso ao redor, para que os métodos possam inserir strings nele diretamente.

É o que fazemos na versão C++/WinRT do Clipboard Sample código-fonte (no ZIP que você baixou). Nesse código-fonte, há um novo método estático privado chamado SampleState::AddClipboardFormatsOutputString, que usa e opera em uma referência a um fluxo de saída. E, em seguida, os métodos SampleState::DisplayChangedFormats e SampleState::BuildClipboardFormatsOutputString são refatorados para chamar esse novo método. É funcionalmente equivalente às listagens de código neste tópico, mas é mais eficiente.

Footer_Click é um manipulador de eventos assíncrono pertencente à classe MainPage C# e é definido em MainPage.xaml.cs. A listagem de código abaixo é funcionalmente equivalente ao método no código-fonte que você baixou. Mas aqui eu o dividi de uma linha em quatro, para tornar mais fácil ver o que está a fazer e, consequentemente, como devemos portá-lo.

async void Footer_Click(object sender, RoutedEventArgs e)
{
    var hyperlinkButton = (HyperlinkButton)sender;
    string tagUrl = hyperlinkButton.Tag.ToString();
    Uri uri = new Uri(tagUrl);
    await Windows.System.Launcher.LaunchUriAsync(uri);
}

Embora, tecnicamente, o método seja assíncrono, ele não faz nada depois do await, então não precisa da await (nem da async palavra-chave). Ele provavelmente os usa para evitar a mensagem IntelliSense no Visual Studio.

O método equivalente C++/WinRT também será assíncrono (porque chama Launcher.LaunchUriAsync). Mas não precisa co_await, nem retornar um objeto assíncrono. Para obter informações sobre co_await objetos assíncronos, consulte Simultaneidade e operações assíncronas com C++/WinRT.

Agora vamos falar sobre o que o método está fazendo. Como este é um manipulador de eventos para o evento Click de um HyperlinkButton, o objeto chamado sender é, na verdade, um HyperlinkButton. Assim, a conversão de tipo é segura (poderíamos alternativamente ter expressado esta conversão como sender as HyperlinkButton). Em seguida, recuperamos o valor da propriedade Tag (se você examinar a marcação XAML no projeto C#, verá que ela está definida como uma cadeia de caracteres que representa uma URL da Web). Embora a propriedade FrameworkElement.Tag (HyperlinkButton é um FrameworkElement) seja do tipo objeto, em C# podemos stringify isso com Object.ToString. A partir da cadeia de caracteres resultante, construímos um objeto Uri . E finalmente (com a ajuda do Shell) lançamos um navegador e navegamos até a url.

Aqui está o método portado para C++/WinRT (novamente, expandido para clareza), após o qual é uma descrição dos detalhes.

// pch.h
...
#include "winrt/Windows.System.h"
...

// MainPage.h
...
    void Footer_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
private:
...

// MainPage.cpp
...
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI::Xaml::Controls;
...
void MainPage::Footer_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const&)
{
    auto hyperlinkButton{ sender.as<HyperlinkButton>() };
    hstring tagUrl{ winrt::unbox_value<hstring>(hyperlinkButton.Tag()) };
    Uri uri{ tagUrl };
    Windows::System::Launcher::LaunchUriAsync(uri);
}

Como sempre, criamos o manipulador de eventos public. Usamos a função no objeto do remetente para convertê-lo em HyperlinkButton. Em C++/WinRT, a propriedade Tag é um IInspectable (o equivalente a Object). Mas não há ToString em IInspectable. Em vez disso, temos que desencaixotar o IInspectable para um valor escalar (uma cadeia de caracteres, neste caso). Novamente, para obter mais informações sobre boxe e unboxing, consulte Boxe e valores de unboxing para IInspectable.

As duas últimas linhas repetem padrões de portabilidade que vimos antes, e elas praticamente ecoam a versão C#.

HandleClipboardChanged

Não há nada de novo envolvido na transposição deste método. Você pode comparar as versões C# e C++/WinRT no ZIP do exemplo Área de transferência código-fonte que você baixou.

OnClipboardChanged e OnWindowActivated

Até agora, temos apenas stubs vazios para esses dois manipuladores de eventos. Mas portá-los é simples e não há nada de novo para discutir.

ScenarioControl_SelectionChanged

Este é outro manipulador de eventos privado pertencente à classe MainPage C# e definido em MainPage.xaml.cs. Em C++/WinRT, vamos torná-lo público e implementá-lo em MainPage.h e MainPage.cpp.

Para esse método, precisaremos de MainPage::navigating, que é um campo booleano privado, inicializado como false. E você precisará de um Frame no , chamado ScenarioFrame. No entanto, excluindo esses detalhes, a adaptação deste método não revela novas técnicas.

Se, em vez de portar manualmente, você estiver a copiar o código a partir da versão C++/WinRT no ficheiro ZIP do exemplo de código-fonte do Área de transferência que você baixou, então verá um MainPage::NavigateTo a ser utilizado lá. Por enquanto, basta refatorar o conteúdo de NavigateTo em ScenarioControl_SelectionChanged.

AtualizarEstado

Até agora, temos apenas um esboço para MainPage.UpdateStatus. A portabilidade da sua implementação, mais uma vez, abrange em grande parte terreno antigo. Um novo ponto a ser observado é que, enquanto em C# podemos comparar uma string com String.Empty, em C++/WinRT chamamos a função winrt::hstring::empty. Outra é que nullptr é o equivalente padrão C++ do C#.null

Você pode executar o resto da porta com técnicas que já abordamos. Aqui está uma lista dos tipos de coisas que você precisará fazer antes que a versão portada desse método seja compilada.

  • Para MainPage.xaml, adicione um elemento Border chamado StatusBorder.
  • Para MainPage.xaml, adicione um TextBlock chamado StatusBlock.
  • Para MainPage.xaml, adicione um StackPanel chamado StatusPanel.
  • Para pch.h, adicione #include "winrt/Windows.UI.Xaml.Media.h".
  • Para pch.h, adicione #include "winrt/Windows.UI.Xaml.Automation.Peers.h".
  • Para MainPage.cpp adicionar using namespace winrt::Windows::UI::Xaml::Media;.
  • Para MainPage.cpp adicionar using namespace winrt::Windows::UI::Xaml::Automation::Peers;.

Copie o XAML e os estilos necessários para concluir a portabilidade MainPage

Para XAML, o caso ideal é que se possa usar a mesma marcação XAML num projeto C# e C++/WinRT. E a amostra do Clipboard é um desses casos.

No seu arquivo , o exemplo do Clipboard possui um ResourceDictionary de estilos XAML , que são aplicados aos botões, menus e outros elementos da interface do aplicativo. A Styles.xaml página é mesclada em App.xaml. E há também o ponto de partida MainPage.xaml padrão para a interface do usuário, que já vimos brevemente. Agora podemos reutilizar esses três .xaml arquivos, inalterados, na versão C++/WinRT do projeto.

Assim como acontece com os arquivos de ativos, você pode optar por fazer referência aos mesmos arquivos XAML compartilhados de várias versões do seu aplicativo. Neste passo a passo, apenas por uma questão de simplicidade, copiaremos arquivos para o projeto C++/WinRT e os adicionaremos dessa forma.

Navegue até a pasta \Clipboard_sample\SharedContent\xaml, selecione e copie App.xaml e MainPage.xaml, e cole esses dois arquivos na pasta \Clipboard\Clipboard no seu projeto C++/WinRT, escolhendo substituir arquivos, quando solicitado.

No projeto C++/WinRT no Visual Studio, clique em Mostrar todos os arquivos para ativá-lo. Agora adicione uma nova pasta, imediatamente sob o nó do projeto, e nomeie-a Styles. No Explorador de Ficheiros, navegue até à \Clipboard_sample\SharedContent\xaml pasta, selecione e copie Styles.xamle cole-a na \Clipboard\Clipboard\Styles pasta que acabou de criar. De volta ao Gerenciador de Soluções no projeto C++/WinRT, clique com o botão direito do mouse na Styles pasta >Adicionar>item existente... e navegue até \Clipboard\Clipboard\Styles. No seletor de arquivos, selecione Styles e clique em Adicionar.

Adicione uma nova pasta ao projeto C++/WinRT, imediatamente abaixo do nó do projeto, e nomeada Styles. Navegue até à pasta \Clipboard_sample\SharedContent\xaml, selecione e copie Styles.xaml, e cole-a na pasta \Clipboard\Clipboard\Styles do seu projeto C++/WinRT. Clique com o botão direito do mouse na Styles pasta (no Gerenciador de Soluções no projeto C++/WinRT) >Adicionar>item existente... e navegue até \Clipboard\Clipboard\Styles. No seletor de arquivos, selecione Styles e clique em Adicionar.

Clique em Mostrar todos os arquivos novamente para ocultá-los.

Agora terminamos de portar a MainPage e, se você estiver seguindo as etapas, seu projeto C++/WinRT será compilado e executado.

Consolide seus .idl arquivos

Além do ponto de partida padrão MainPage.xaml para a interface do usuário, o exemplo da Área de Transferência tem cinco outras páginas XAML específicas aos cenários, bem como os seus arquivos code-behind correspondentes. Reutilizaremos a marcação XAML real de todas essas páginas, inalterada, na versão C++/WinRT do projeto. E veremos como portar o code-behind nas próximas seções principais. Mas antes disso, vamos falar sobre IDL.

Há valor em consolidar o IDL para as suas classes de runtime num único ficheiro IDL. Para saber mais sobre esse valor específico, consulte Fatoração de classes de tempo de execução em arquivos Midl (.idl). Então, em seguida, vamos consolidar o conteúdo de CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idl e OtherScenarios.idl movendo esse IDL para um único ficheiro chamado Project.idl (e, em seguida, excluindo os arquivos originais).

Enquanto fazemos isso, também vamos remover a propriedade fictícia gerada automaticamente (Int32 MyProperty;e sua implementação) de cada um desses cinco tipos de página XAML.

Primeiro, adicione um novo item Midl File (.idl) ao projeto C++/WinRT. Dê-lhe o nome de Project.idl. Substitua todo o conteúdo de Project.idl pelo seguinte código.

// Project.idl
namespace SDKTemplate
{
    [default_interface]
    runtimeclass CopyFiles : Windows.UI.Xaml.Controls.Page
    {
        CopyFiles();
    }

    [default_interface]
    runtimeclass CopyImage : Windows.UI.Xaml.Controls.Page
    {
        CopyImage();
    }

    [default_interface]
    runtimeclass CopyText : Windows.UI.Xaml.Controls.Page
    {
        CopyText();
    }

    [default_interface]
    runtimeclass HistoryAndRoaming : Windows.UI.Xaml.Controls.Page
    {
        HistoryAndRoaming();
    }

    [default_interface]
    runtimeclass OtherScenarios : Windows.UI.Xaml.Controls.Page
    {
        OtherScenarios();
    }
}

Como se pode ver, isso é apenas uma cópia do conteúdo dos arquivos individuais de .idl, tudo dentro de um namespace, e com MyProperty removido de cada classe de runtime.

No Gerenciador de Soluções no Visual Studio, selecione todos os arquivos IDL originais (CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idle OtherScenarios.idl) e Editar>Remover (escolha Eliminar na caixa de diálogo).

Finalmente, e para concluir a remoção de MyProperty—nos ficheiros .h e .cpp para cada um desses cinco tipos de página XAML, elimine as declarações e definições das funções do acessador int32_t MyProperty() e do mutador void MyProperty(int32_t).

Aliás, é sempre uma boa ideia ter o nome dos seus arquivos XAML correspondente ao nome da classe que eles representam. Por exemplo, se você tiver x:Class="MyNamespace.MyPage" em um arquivo de marcação XAML, esse arquivo deverá ser nomeado MyPage.xaml. Embora isso não seja um requisito técnico, não ter que fazer malabarismos com nomes diferentes para o mesmo artefato tornará seu projeto mais compreensível e sustentável, e mais fácil de trabalhar.

CopiarFicheiros

No projeto C#, o tipo de página CopyFiles XAML é implementado nos ficheiros de código-fonte CopyFiles.xaml e CopyFiles.xaml.cs. Vamos dar uma olhada em cada um dos membros do CopyFiles uma de cada vez.

Página raiz

Este é um campo privado.

// CopyFiles.xaml.cs
...
public sealed partial class CopyFiles : Page
{
    MainPage rootPage = MainPage.Current;
    ...
}
...

Em C++/WinRT, podemos defini-lo e inicializá-lo assim.

// CopyFiles.h
...
struct CopyFiles : CopyFilesT<CopyFiles>
{
    ...
private:
    SDKTemplate::MainPage rootPage{ MainPage::Current() };
};
...

Novamente (assim como com MainPage::current), CopyFiles::rootPage é declarado como sendo do tipo SDKTemplate::MainPage, que é o tipo projetado, e não o tipo de implementação.

CopyFiles (o construtor)

No projeto C++/WinRT, o tipo CopyFiles já tem um construtor contendo o código que queremos (ele chama apenas InitializeComponent).

CopyButton_Click

O método C# CopyButton_Click é um manipulador de eventos e, a partir da async palavra-chave em sua assinatura, podemos dizer que o método faz um trabalho assíncrono. Em C++/WinRT, implementamos um método assíncrono como um co-rotina. Para obter uma introdução à concorrência em C++/WinRT, juntamente com uma descrição do que é uma corrotina , consulte Concorrência e operações assíncronas com C++/WinRT.

É comum querer agendar mais trabalho após a conclusão de uma corrotina, e, nesses casos, a corrotina retornaria algum tipo de objeto assíncrono que pode ser aguardado e que pode, opcionalmente, relatar o progresso. Mas essas considerações normalmente não se aplicam a um manipulador de eventos. Portanto, quando se tem um gestor de eventos que executa operações assíncronas, pode implementá-lo como uma coroutine que retorna winrt::fire_and_forget. Para mais informações, consulte Fire e esqueça.

Embora a ideia de uma corotina de incêndio e esquecimento seja que você não se importa quando ela termina, o trabalho ainda continua (ou está suspenso, aguardando retomada) em segundo plano. Você pode ver na implementação do C# que CopyButton_Click depende do ponteiro this (acede ao membro de dados da instância rootPage). Portanto, devemos garantir que o ponteiro this, um ponteiro para um objeto CopyFiles, dure mais do que a corrotina CopyButton_Click. Em uma situação como esta aplicação de exemplo, em que o usuário navega entre páginas da interface do usuário, não podemos controlar diretamente o tempo de vida dessas páginas. Se a página CopyFiles for destruída ao navegarmos para fora dela enquanto CopyButton_Click ainda está em execução em um thread em segundo plano, não será seguro acessar rootPage. Para corrigir a co-rotina, ela precisa obter uma referência forte ao ponteiro this e manter essa referência durante a co-rotina. Para obter mais informações, consulte Referências fortes e fracas em C++/WinRT.

Se você olhar na versão C++/WinRT do exemplo, em CopyFiles::CopyButton_Click, verá que isso é feito com uma declaração simples na pilha.

fire_and_forget CopyFiles::CopyButton_Click(IInspectable const&, RoutedEventArgs const&)
{
    auto lifetime{ get_strong() };
    ...
}

Vejamos os outros aspetos do código portado que são dignos de nota.

No código, instanciamos um objeto FileOpenPicker e, duas linhas depois, acedemos à propriedade FileTypeFilter desse objeto. O tipo de retorno dessa propriedade implementa um IVector de cadeias de caracteres. E nesse IVector, chamamos o método IVector<T>.ReplaceAll(T[]). O aspeto interessante é o valor que estamos passando para esse método, onde uma matriz é esperada. Aqui está a linha de código.

filePicker.FileTypeFilter().ReplaceAll({ L"*" });

O valor que estamos a passar ({ L"*" }) é uma lista de inicializadores padrão de C++ . Ele contém um único objeto, neste caso, mas uma lista de inicializadores pode conter qualquer número de objetos separados por vírgula. As partes do C++/WinRT que permitem a conveniência de passar uma lista de inicializadores para um método como este são explicadas em Listas de inicializadores padrão.

Nós portamos a palavra-chave C# await para co_await em C++/WinRT. Aqui está o exemplo do código.

auto storageItems{ co_await filePicker.PickMultipleFilesAsync() };

Em seguida, considere esta linha de código C#.

dataPackage.SetStorageItems(storageItems);

C# é capaz de converter implicitamente o IReadOnlyList<StorageFile> representado por storageItems para o IEnumerable<IStorageItem> esperado por DataPackage.SetStorageItems. Mas em C++/WinRT precisamos converter explicitamente de IVectorView<StorageFile> para IIterable<IStorageItem>. E assim temos outro exemplo da função como em ação.

dataPackage.SetStorageItems(storageItems.as<IVectorView<IStorageItem>>());

Onde usamos a null palavra-chave em C# (por exemplo, Clipboard.SetContentWithOptions(dataPackage, null)), usamos nullptr em C++/WinRT (por exemplo, Clipboard::SetContentWithOptions(dataPackage, nullptr)).

PasteButton_Click

Este é outro manipulador de eventos na forma de uma corrotina "fire-and-forget". Vejamos os aspetos do código portado que são dignos de nota.

Na versão C# do exemplo, capturamos exceções com catch (Exception ex). No código C++/WinRT portado, você verá a expressão catch (winrt::hresult_error const& ex). Para obter mais informações sobre winrt::hresult_error e como trabalhar com ele, consulte Tratamento de erros com C++/WinRT.

Um exemplo de teste se um objeto C# é null ou não é if (storageItems != null). Em C++/WinRT, podemos usar um operador de conversão para bool, que realiza internamente o teste contra nullptr.

Aqui está uma versão ligeiramente simplificada de um fragmento de código da versão C++/WinRT portada do exemplo.

std::wostringstream output;
output << std::wstring_view(ApplicationData::Current().LocalFolder().Path());

A construção de um std::wstring_view a partir de uma winrt::hstring como esta ilustra uma alternativa para chamar a função hstring::c_str (para transformar o winrt::hstring em uma cadeia de caracteres no estilo C). Esta alternativa funciona graças ao operador de conversão hstringpara std::wstring_view.

Considere este fragmento de C#.

var file = storageItem as StorageFile;
if (file != null)
...

Para portar a palavra-chave C# as para C++/WinRT, até agora vimos o como função utilizada várias vezes. Essa função lança uma exceção se a conversão de tipo falhar. Mas se quisermos que a conversão retorne nullptr se falhar (para que possamos lidar com essa condição no código), em vez disso, usamos a função try_as .

auto file{ storageItem.try_as<StorageFile>() };
if (file)
...

Copie o XAML necessário para concluir o porting CopyFiles

Agora pode selecionar todo o conteúdo do arquivo CopyFiles.xaml da pasta shared do código-fonte do exemplo original, e colá-lo no arquivo CopyFiles.xaml no projeto C++/WinRT (substituindo o conteúdo existente desse arquivo no projeto C++/WinRT).

Por fim, edite CopyFiles.h e .cpp e exclua a função fictícia do ClickHandler, uma vez que substituímos a marcação XAML correspondente.

Agora terminamos de portar CopyFiles e, se você estiver seguindo as etapas, seu projeto C++/WinRT será compilado e executado, e o cenário CopyFiles será funcional.

CopiarImagem

Para portar o CopyImage tipo de página XAML, siga o mesmo processo que para CopyFiles. Ao portar CopyImage, você encontrará o uso do C# usando a instrução, que garante que os objetos que implementam a interface IDisposable sejam descartados corretamente.

if (imageReceived != null)
{
    using (var imageStream = await imageReceived.OpenReadAsync())
    {
        ... // Pass imageStream to other APIs, and do other work.
    }
}

A interface equivalente em C++/WinRT é IClosable, com seu único método Close . Aqui está o equivalente C++/WinRT do código C# acima.

if (imageReceived)
{
    auto imageStream{ co_await imageReceived.OpenReadAsync() };
    ... // Pass imageStream to other APIs, and do other work.
    imageStream.Close();
}

Os objetos C++/WinRT implementam IClosable principalmente para o benefício de linguagens que carecem de finalização determinística. C++/WinRT tem finalização determinística e, portanto, muitas vezes não precisamos chamar IClosable::Close quando estamos escrevendo C++/WinRT. Mas há momentos em que é bom chamá-lo, e este é um desses momentos. Aqui, o identificador imageStream é um wrapper contado de referência que envolve um objeto subjacente da Windows Runtime (neste caso, um objeto que implementa IRandomAccessStreamWithContentType). Embora possamos determinar que o finalizador de imageStream (seu destruidor) será executado no final do escopo de fechamento (os colchetes), não podemos ter certeza de que esse finalizador chamará Fechar. Isso porque passámos imageStream para outras APIs, e elas ainda podem estar a contribuir para a contagem de referência do objeto subjacente do Runtime do Windows. Portanto, este é um caso em que chamar explicitamente Close é uma boa ideia. Para obter mais informações, consulte Preciso chamar IClosable::Fechar em classes de execução que estou a consumir?.

Em seguida, considere a expressão (uint)(imageDecoder.OrientedPixelWidth * 0.5)C# , que você encontrará no manipulador de eventos OnDeferredImageRequestedHandler . Esta expressão multiplica a uint por a double, resultando num double. Em seguida, converte isso para um uint. Em C++/WinRT, poderíamos usar um cast estilo C de aparência semelhante ((uint32_t)(imageDecoder.OrientedPixelWidth() * 0.5)), mas é preferível deixar claro exatamente que tipo de elenco pretendemos e, neste caso, faríamos isso com static_cast<uint32_t>(imageDecoder.OrientedPixelWidth() * 0.5).

A versão em C# de CopyImage.OnDeferredImageRequestedHandler tem uma finally cláusula, mas não uma catch cláusula. Fomos um pouco mais longe na versão C++/WinRT e implementamos uma catch cláusula para que possamos relatar se a renderização atrasada foi bem-sucedida ou não.

Portar o restante desta página XAML não resulta em nada de novo para discutir. Lembre-se de excluir a função de teste ClickHandler. E, assim como com CopyFiles, a última etapa na portagem é selecionar todo o conteúdo do CopyImage.xamle colá-lo no mesmo arquivo no projeto C++/WinRT.

TextoCopiar

Você pode portar CopyText.xaml e CopyText.xaml.cs usando técnicas que já abordamos.

HistóriaEItinerância

Há alguns pontos de interesse que surgem ao portar o tipo de página HistoryAndRoaming XAML.

Primeiro, examine o código-fonte do C# e siga o fluxo de execução desde o OnNavigatedTo até o manipulador de eventos OnHistoryEnabledChanged e, finalmente, até a função assíncrona CheckHistoryAndRoaming (que não é aguardada, portanto, é essencialmente disparar e esquecer). Como CheckHistoryAndRoaming é assíncrono, precisaremos ter cuidado em C++/WinRT quanto ao tempo de vida do ponteiro this. Você pode ver o resultado se observar a implementação no arquivo de HistoryAndRoaming.cpp código-fonte. Primeiro, quando anexamos delegados aos eventos Clipboard::HistoryEnabledChanged e Clipboard::RoamingEnabledChanged, tomamos apenas uma referência fraca ao objeto de página HistoryAndRoaming. Fazemos isso criando o delegado com uma dependência no valor retornado de winrt::get_weak, em vez de uma dependência no ponteiro this. O que significa que o próprio delegado, que eventualmente chama o código assíncrono, não mantém a página HistoryAndRoaming ativa, caso naveguemos para longe dela.

E em segundo lugar, quando finalmente chegamos à nossa 'fire-and-forget' CheckHistoryAndRoaming corrotina, a primeira coisa que fazemos é criar uma referência forte a this para garantir que a página HistoryAndRoaming viva pelo menos até que a corrotina finalmente termine. Para obter mais informações sobre os dois aspetos descritos, consulte Referências fortes e fracas em C++/WinRT.

Descobrimos outro ponto de interesse ao portar CheckHistoryAndRoaming. Ele contém código para atualizar a interface do usuário; portanto, precisamos ter certeza de que estamos fazendo isso no thread principal da interface do usuário. O thread que inicialmente chama um manipulador de eventos é o thread principal da interface do usuário. Mas, normalmente, um método assíncrono pode ser executado e/ou retomado em qualquer thread arbitrário. Em C#, a solução é chamar CoreDispatcher.RunAsync e atualizar a interface do usuário de dentro da função lambda. Em C++/WinRT, podemos usar a função winrt::resume_foreground juntamente com o Dispatcher do ponteiro para suspender a rotina e retomar imediatamente no thread principal da interface gráfica.

A expressão relevante é co_await winrt::resume_foreground(Dispatcher());. Alternativamente, embora com menos clareza, poderíamos expressar isso simplesmente como co_await Dispatcher();. A versão mais curta é obtida cortesia de um operador de conversão fornecido por C++/WinRT.

Portar o restante desta página XAML não resulta em nada de novo para discutir. Lembre-se de excluir a função fictícia ClickHandler e copiar a marcação XAML.

Outros Cenários

Você pode portar OtherScenarios.xaml e OtherScenarios.xaml.cs usando técnicas que já abordamos.

Conclusão

Espero que este passo a passo tenha armado você com informações e técnicas de portabilidade suficientes para que agora você possa ir em frente e portar seus próprios aplicativos C# para C++/WinRT. Por meio de uma atualização, você pode continuar a consultar as versões antes (C#) e depois (C++/WinRT) do código-fonte node exemplo da Área de Transferência do e compará-las lado a lado para ver a correspondência.