Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este tópico apresenta um estudo de caso de portabilidade de um dos exemplos de aplicativo Plataforma Universal do Windows (UWP) de C# para C++/WinRT. Você pode adquirir prática e experiência em portabilidade seguindo o passo a passo e fazendo a portabilidade da amostra para si mesmo à medida que avança.
Note
O código-fonte que está sendo portado é um aplicativo C# UWP. O código C++/WinRT de destino neste artigo é escrito para WinUI 3 (SDK do Aplicativo Windows). Sempre que a origem UWP usa APIs que diferem no WinUI 3 (por exemplo, Windows.UI.Core.CoreDispatcher vs. Microsoft.UI.Dispatching.DispatcherQueue), este artigo mostra explicitamente o equivalente correto do WinUI 3 na saída C++/WinRT. Você pode usar os padrões de código da coluna C++/WinRT diretamente no aplicativo WinUI 3.
Para obter um catálogo abrangente dos detalhes técnicos envolvidos na migração de C# para C++/WinRT, consulte o tópico complementar Migrar de C# para C++/WinRT.
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ê observará que há mais tipos de arquivos de código-fonte para os quais se acostumar. O motivo é a diferença entre compiladores, a maneira 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 função descreve apenas a assinatura da função (seu tipo de retorno, seu nome e seus tipos de parâmetro e nomes). Uma definição de função inclui o corpo da função (sua implementação).
É um pouco diferente quando se trata de tipos. Você define um tipo fornecendo o nome dele e (no mínimo) declarando todas as funções de membro (e outros membros) dele. Isso mesmo, você pode definir um tipo mesmo se não definir suas funções de membro.
- Arquivos de código-fonte C++ comuns são arquivos
.h(dot aitch) e.cpp. Um.harquivo é 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.cpparquivo. Portanto, para um tipo hipotético de C++ MyClass, você definiria MyClassMyClass.he definiria suas funções de membro emMyClass.cpp. Para que outros desenvolvedores reutilizem suas classes, você compartilharia apenas os arquivos e o código do.hobjeto. Você manteria seus.cpparquivos em segredo, pois 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. Portanto, em vez de processar o conteúdo desse conjunto de cabeçalhos cada vez que você compilar, você pode agregar esses cabeçalhos em um arquivo, compilá-los 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 chamadopch.h). - Arquivos
.idl. Esses arquivos contêm IDL (Interface Definition Language). Você pode pensar em IDL como arquivos de cabeçalho para tipos de Windows Runtime. Falaremos mais sobre IDL na seção IDL para o tipo MainPage.
Baixar e testar o exemplo de Área de Transferência
Visite a página da Web Amostra de Clipboard e clique em Baixar ZIP. Descompacte o arquivo baixado e dê uma olhada na estrutura da pasta.
- 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 denominada
cppwinrt. - Outros arquivos — usados tanto pela versão em C# quanto pela versão em C++/WinRT — podem ser encontrados nas pastas
sharedeSharedContent.
O passo a passo deste tópico mostra como recriar a versão C++/WinRT do Clipboard, portando-a do código-fonte C#. Dessa forma, você pode ver como pode portar seus próprios projetos em 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 de usuário (IU) do exemplo orienta você por seus diversos recursos, passo a passo.
Tip
A pasta raiz do exemplo que você baixou pode ser nomeada Clipboard em vez de Clipboard_sample. Mas continuaremos a nos referir a essa pasta Clipboard_sample para distingui-la da versão do C++/WinRT que você criará em uma etapa posterior.
Crie um aplicativo vazio, chamado Clipboard
Note
Para obter informações sobre como instalar e usar o VSIX (Extensão de Visual Studio) do C++/WinRT e o pacote NuGet (que juntos fornecem suporte ao modelo de projeto e ao build), consulte Visual Studio suporte para C++/WinRT.
Inicie o processo de portabilidade criando um novo projeto C++/WinRT no Microsoft Visual Studio. Crie um novo projeto usando o template de projeto Blank App, Packaged (WinUI 3 in Desktop) para C++. Defina o nome como Clipboard e (para que a estrutura de pastas corresponda ao passo a passo) verifique se a opção Posicionar a solução e o projeto no mesmo diretório está desmarcada.
Apenas para obter uma linha de base, certifique-se de que esse novo projeto, vazio, seja compilado e executado.
Package.appxmanifest e arquivos de ativos
Se as versões C# e C++/WinRT do exemplo não precisarem ser instaladas lado a lado no mesmo computador, os arquivosPackage.appxmanifest de origem do manifesto do pacote de aplicativos dos dois projetos podem ser idênticos. Nesse caso, você pode apenas copiar Package.appxmanifest do projeto C# para o projeto C++/WinRT e você terminou.
Para que as duas versões do exemplo 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 . Esse é o nome do pacote. Para um projeto recém-criado, será atribuído a ele um valor inicial na forma de um GUID exclusivo.
- Dentro do elemento /Package/Applications/Application , observe o valor do atributo Id . Essa é a ID do aplicativo.
- Dentro do elemento /Package/mp:PhoneIdentity , observe o valor do atributo PhoneProductId . Novamente, em um projeto recém-criado, isso será definido com o mesmo GUID que o nome do pacote.
Em seguida, copie Package.appxmanifest do projeto C# para o projeto C++/WinRT. Por fim, você pode restaurar os três valores que anotou. Ou você pode editar os valores copiados para torná-los exclusivos e/ou apropriados para o aplicativo e para sua organização (como você normalmente faria para um novo projeto). Por exemplo, neste caso, em vez de restaurar o valor do nome do pacote, podemos simplesmente alterar o valor copiado de Microsoft.SDKSamples.Clipboard.CS para Microsoft.SDKSamples.Clipboard.CppWinRT. E podemos deixar a ID do aplicativo definida como Aplicativo. Desde que o nome do pacote ou a ID do aplicativo sejam diferentes, os dois aplicativos terão AUMIDs (IDs de modelo de usuário de aplicativo) diferentes. E é isso que é necessário para que dois aplicativos sejam instalados lado a lado no mesmo computador.
Para fins deste passo a passo, faz sentido fazer algumas outras alterações em Package.appxmanifest. Há três ocorrências da sequência de caracteres Clipboard C# Sample. Altere para Clipboard C++/WinRT Sample.
No projeto C++/WinRT, o arquivo Package.appxmanifest e o projeto agora estão fora de sincronia em relação aos arquivos de recursos aos quais fazem referência. Para corrigir isso, primeiro remova os ativos do projeto C++/WinRT selecionando todos os arquivos na Assets pasta (em Gerenciador de Soluções em Visual Studio) e removendo-os (escolha Excluir na caixa de diálogo).
O projeto C# referencia arquivos de recursos 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 \Clipboard_sample\SharedContent\media pasta. Selecione os sete arquivos que o projeto C# inclui (microsoft-sdk.png, , smalltile-sdk.png, splash-sdk.png, squaretile-sdk.png, storelogo-sdk.pngtile-sdk.pnge windows-sdk.png), copie-os e cole-os \Clipboard\Clipboard\Assets na pasta no novo projeto.
Clique com o botão direito do mouse na Assets pasta (em 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 sincronizado com os arquivos de recursos do projeto.
MainPage, incluindo a funcionalidade que configura o exemplo
A amostra de Clipboard, como todas as amostras de aplicativos UWP (Plataforma Universal do Windows), consiste em uma coleção de cenários que o usuário pode percorrer 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ê examinar o 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 (ela compõe 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 arquivo de código-fonte é Scenario, com suas propriedades Title e ClassType .
Nas próximas subseções, veremos como portar MainPage e Scenario.
IDL para o tipo MainPage
Vamos começar esta seção falando brevemente sobre a Linguagem de Definição de Interface (IDL) e como ela nos ajuda quando estamos programando com C++/WinRT. A IDL é um tipo de código-fonte que descreve a superfície resgatável de um tipo do Windows Runtime. A superfície resgatável (ou pública) de um tipo é projetada no mundo, para que o tipo possa ser consumido. Essa parte projetada do tipo contrasta com a implementação interna real dele, que obviamente não é resgatável nem pública. É apenas a parte projetada que definimos na IDL.
Tendo criado o código-fonte IDL (em um .idl arquivo), você pode compilar a IDL em arquivos de metadados legíveis pelo computador (também conhecidos como metadados Windows). Esses arquivos de metadados têm a extensão .winmde aqui estão alguns de seus usos.
- Um(a)
.winmdpode descrever os tipos do Windows Runtime em um componente. Quando você faz referência a um WRC (componente Windows Runtime) de um projeto de aplicativo, o projeto de aplicativo lê os metadados Windows pertencentes ao WRC (que os 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
.winmdpode descrever os tipos do Windows Runtime em uma parte do aplicativo para que sejam consumidos por outra parte do mesmo aplicativo. Por exemplo, um tipo do Windows Runtime consumido por uma página XAML no mesmo aplicativo. - Para facilitar o uso de tipos do Windows Runtime (integrados ou de terceiros), o sistema de build do C++/WinRT usa arquivos
.winmdpara gerar tipos encapsuladores que representam as porções projetadas desses tipos do Windows Runtime. - Para facilitar a implementação de seus próprios tipos de Windows Runtime, o sistema de build do C++/WinRT transforma sua IDL em um
.winmdarquivo 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 é Microsoft Interface Definition Language 3.0. No restante desta seção do tópico, examinaremos o tipo C# MainPage em alguns detalhes. Decidiremos quais partes dele precisam estar na projeção do tipo MainPage C++/WinRT (ou seja, em sua superfície resgatável ou pública) e que podem ser apenas parte da implementação. Essa distinção é importante porque, quando chegarmos a escrever nossa IDL (o que faremos na seção seguinte), definiremos nela apenas as partes que podem ser chamadas.
Os arquivos de código-fonte C# que juntos implementam o tipo MainPage são: MainPage.xaml (que portaremos em breve, copiando-o) MainPage.xaml.cse SampleConfiguration.cs.
Na versão C++/WinRT, fatoraremos nosso tipo MainPage em arquivos de código-fonte de forma semelhante. Vamos pegar a lógica em MainPage.xaml.cs e traduzi-la, em grande parte, 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 C# da Plataforma Universal do Windows (UWP) são, é claro, tipos do Windows Runtime. Mas ao criar um tipo em um aplicativo C++/WinRT, você pode escolher se esse tipo é um tipo de Windows Runtime ou uma classe/struct/enumeração C++ regular.
Qualquer página XAML em nosso projeto precisa ser um tipo Windows Runtime, portanto, o MainPage precisa ser um tipo Windows Runtime. No projeto C++/WinRT, MainPage já é um tipo Windows Runtime, portanto, não precisamos alterar esse aspecto dele. Especificamente, é uma classe de runtime.
- Para obter mais detalhes sobre se você deve ou não criar uma classe de runtime para um determinado tipo, consulte o tópico Criar APIs com C++/WinRT.
- No C++/WinRT, a implementação interna de uma classe de runtime e as partes projetadas (públicas) dela existem na forma de duas classes diferentes. Eles são conhecidos como o tipo de implementação e o tipo projetado. Você pode aprender mais sobre eles no tópico mencionado no item anterior e também em Consumir APIs com C++/WinRT.
- Para obter mais informações sobre a relação entre classes de runtime e arquivos IDL (
.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 runtime, cujo primeiro passo é adicionar um novo item midl file (.idl) ao projeto.
Para o MainPage, na verdade, já temos o arquivo necessário MainPage.idl no projeto C++/WinRT. Isso ocorre porque o modelo de projeto o criou para nós. Mas, posteriormente, neste passo a passo, adicionaremos mais .idl arquivos ao projeto.
Em breve, veremos uma listagem de exatamente qual IDL precisamos adicionar ao arquivo existente MainPage.idl . Antes disso, vamos discutir o que precisa e o que não precisa entrar na 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. Encontramos esses membros procurando em MainPage.xaml.cs e em SampleConfiguration.cs.
Encontramos um total de doze protected campos e private métodos. E encontramos os seguintes public membros.
- O construtor
MainPage()padrão. - Os campos estáticos Atuais e FEATURE_NAME.
- As propriedades IsClipboardContentChangedEnabled e Scenarios.
- Os métodos BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotifications, e NotifyUser.
São esses membros public que são candidatos a declaração em MainPage.idl. Portanto, vamos examinar cada uma delas e ver se elas precisam fazer parte da classe de runtime MainPage ou se elas precisam apenas fazer parte de sua implementação.
- O construtor padrão
MainPage(). Para uma página XAML, é normal declarar um construtor padrão em sua IDL. Dessa forma, a estrutura da IU XAML pode ativar o tipo. - O campo estático Current é usado nas páginas XAML de cada cenário para acessar a instância de MainPage do aplicativo. Como o Current não está sendo usado para interoperar com o framework XAML (nem é usado em unidades de compilação diferentes), poderíamos reservá-lo para que seja apenas um membro do tipo de implementação. Em casos como esse, nos seus próprios projetos, você pode fazer isso. Mas como o campo é uma instância do tipo projetado, parece lógico declará-lo na IDL. Então é isso que faremos aqui (e fazê-lo também torna o código um pouco mais limpo).
- É um caso semelhante para o campo FEATURE_NAME estático, que é acessado dentro do tipo MainPage . Novamente, optar por declará-lo na IDL torna nosso código um pouco mais limpo.
- A propriedade IsClipboardContentChangedEnabled é usada apenas na classe OtherScenarios . Portanto, durante a portabilidade, simplificaremos um pouco e a transformaremos em um campo privado da classe de runtime OtherScenarios. Assim, ela não irá para a IDL.
- A propriedade Scenarios é uma coleção de objetos do tipo Scenario (um tipo que mencionamos anteriormente). Falaremos sobre Cenário na próxima subseção, então vamos deixar a propriedade Cenários para depois também.
- Os métodos BuildClipboardFormatsOutputString, DisplayToast e EnableClipboardContentChangedNotifications são funções de utilitário que têm mais a ver 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 utilitário chamado SampleState (que não precisa ser um tipo do Windows Runtime). Por esse motivo, esses três métodos não entrarão na IDL.
- O método NotifyUser é chamado de dentro das páginas XAML do cenário individual na instância de MainPage retornada do campo estático Current. Como (como já observado) Atual é uma instância do tipo projetado, precisamos declarar NotifyUser na IDL. NotifyUser usa um parâmetro do tipo NotifyType. Falaremos sobre isso na próxima subseção.
Qualquer membro ao qual você deseja associar dados também precisa ser declarado na IDL (se você estiver usando {x:Bind} ou {Binding}). Para obter mais informações, consulte Associação de dados.
Estamos fazendo progressos: estamos desenvolvendo uma lista de quais membros adicionar e quais não adicionar ao MainPage.idl arquivo. Mas ainda temos que discutir a propriedade Scenarios e o tipo NotifyType . Então vamos fazer isso a seguir.
IDL para os tipos Scenario e NotifyType
A classe Cenário é 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 Cenário estiver sendo usado entre binários ou para interoperar com a estrutura XAML, ele precisará ser declarado em IDL como um tipo de Windows Runtime.
Estudando o código-fonte do C#, descobrimos que Cenário é usado nesse 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 Scenarioprecisa interoperar com XAML, ele precisa ser um tipo do Windows Runtime. Portanto, ele precisa ser definido em IDL. Definir o tipo Scenario em IDL faz com que o sistema de compilação do C++/WinRT gere para você uma definição do código-fonte de Scenario em um arquivo de cabeçalho gerado internamente (cujo nome e localização não são importantes para este passo a passo).
E você se lembrará de que MainPage.Scenarios é uma coleção de objetos Scenario , que acabamos de dizer que precisam estar em IDL. Por esse motivo, MainPage.Scenarios em si também precisa ser declarado na IDL.
NotifyType é um enum declarado em MainPage.xaml.cs de C#. Como passamos NotifyType para um método pertencente à classe de runtime MainPage, NotifyType também precisa ser um tipo de Windows Runtime; e ele precisa ser definido em MainPage.idl.
Agora vamos adicionar ao arquivo MainPage.idl os novos tipos e o novo membro de Mainpage que decidimos declarar na IDL. Ao mesmo tempo, removeremos da IDL os membros temporários de Mainpage que o modelo de projeto do Visual Studio nos forneceu.
Portanto, em seu projeto C++/WinRT, abra MainPage.idle edite-o para que ele se pareça com a listagem abaixo. Observe que uma das alterações consiste em mudar o nome do namespace de Clipboard para SDKTemplate. Se desejar, você pode simplesmente substituir todo o conteúdo MainPage.idl pelo código a seguir. Outro ajuste a ser observado é que estamos alterando o nome de Scenario::ClassType para Scenario::ClassName.
// MainPage.idl
namespace SDKTemplate
{
struct Scenario
{
String Title;
Microsoft.UI.Xaml.Interop.TypeName ClassName;
};
enum NotifyType
{
StatusMessage,
ErrorMessage
};
[default_interface]
runtimeclass MainPage : Microsoft.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);
};
}
Note
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, talvez você não queira nem precise alterar o namespace, como fizemos acima. Estamos fazendo isso aqui apenas porque o namespace padrão do projeto C# que estamos migrando é SDKTemplate; ao passo que o nome do projeto e o do assembly é Clipboard.
Mas, à medida que prosseguirmos com a portabilidade neste passo a passo, mudaremos todas as ocorrências no código-fonte do namespace Clipboard para SDKTemplate. Também há um lugar nas propriedades do projeto C++/WinRT onde o nome do namespace Clipboard aparece, então vamos aproveitar a oportunidade para mudar isso agora.
Em Visual Studio, para o projeto C++/WinRT, defina a propriedade de projeto Common Properties>C++/WinRT>Root Namespace como o valor SDKTemplate.
Salve a IDL e gere novamente os arquivos stub
O tópico controles XAML; vincular a uma propriedade C++/WinRT apresenta a noção de arquivos de stub e mostra, passo a passo, como eles funcionam. Também mencionamos stubs anteriormente neste tópico quando mencionamos que o sistema de build do C++/WinRT transforma o conteúdo de seus .idl arquivos em metadados de Windows e, em seguida, a partir desse metadados, uma ferramenta nomeada cppwinrt.exe gera stubs nos quais você pode basear sua implementação.
Cada vez que você adiciona, remove ou altera algo em sua IDL e no build, o sistema de build atualiza as implementações de stub nesses arquivos stubs. Portanto, sempre que você altera a IDL e o build, recomendamos que veja esses arquivos stubs, copie as assinaturas alteradas e cole-as em seu projeto. Forneceremos mais detalhes e exemplos de exatamente como fazer isso em um momento. Mas a vantagem de fazer isso é dar-lhe uma maneira livre de erros de saber o tempo todo 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, portanto, você deve salvá-lo agora. No momento, o projeto não será compilado até o fim, mas fazer uma compilação agora é útil porque regenera os arquivos stub de MainPage. Portanto, crie o projeto agora e desconsidere todos os erros de build.
Para este projeto C++/WinRT, os arquivos stub são gerados na \Clipboard\Clipboard\Generated Files\sources pasta. Você os encontrará lá depois que o build parcial terminar (novamente, como esperado, o build não será totalmente bem-sucedido. Mas a etapa em que estamos interessados — a geração de stubs — terá sido bem-sucedida). Os arquivos nos quais estamos interessados são MainPage.h e MainPage.cpp.
Nesses dois arquivos stub, você verá novas implementações de stub dos membros do MainPage que adicionamos à IDL (Atual e FEATURE_NAME, por exemplo). Você vai querer copiar essas implementações stub para os arquivos MainPage.h e MainPage.cpp que já estão no projeto. Ao mesmo tempo, assim como fizemos com a IDL, removeremos desses arquivos existentes os membros do espaço reservado do Mainpage que o modelo de projeto Visual Studio nos deu (a propriedade fictícia chamada MyProperty e o manipulador de eventos chamado ClickHandler).
De fato, o único membro da versão atual de MainPage que queremos manter é o construtor.
Depois que você tiver copiado os novos membros dos arquivos stub, excluído os membros que não queremos e atualizado o namespace, os arquivos MainPage.h e MainPage.cpp em seu projeto deverão se parecer com as listagens de código abaixo. Observe que há dois tipos mainpage . Um no namespace de implementação e outro no namespace factory_implementation . A única alteração que fizemos na versão factory_implementation foi 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 cadeias de caracteres, O C# usa System.String. Consulte o método MainPage.NotifyUser para obter um exemplo. Em nossa IDL, declaramos uma cadeia de caracteres com String e, quando a cppwinrt.exe ferramenta gera código C++/WinRT para nós, ela usa o tipo winrt::hstring . Sempre que encontrarmos uma cadeia de caracteres no código C#, portaremos isso para winrt::hstring. Para obter mais informações, consulte Tratamento de cadeia de caracteres em C++/WinRT.
Para uma explicação sobre os const& parâmetros nas assinaturas de método, consulte Passagem de parâmetros.
Atualizar todas as declarações/referências de namespace restantes e compilar
Antes de compilar o projeto C++/WinRT, localize todas as declarações do namespace Clipboard (e as referências a ele) e altere-as para SDKTemplate.
-
MainPage.xamleApp.xaml. O namespace aparece nos valores dos atributosx:Classexmlns:local. -
App.idl. -
App.h. -
App.cpp. Há duas diretivasusing namespace(pesquise a substringusing namespace Clipboard) e duas qualificações do tipo MainPage (pesquise porClipboard::MainPage). Esses precisam ser alterados.
Como removemos o manipulador de eventos de MainPage, entre também em MainPage.xaml e exclua o elemento Button do código de marcação.
Salve todos os arquivos. Limpe a solução (Compilar>Limpar Solução) e compile-a em seguida. Tendo seguido todas as alterações até agora, exatamente como escrito, espera-se que o build tenha êxito.
Implementar os membros do MainPage que declaramos no IDL
O construtor, Current e 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, reutilizaremos MainPage.xaml por completo (copiando-o). Por enquanto (abaixo), adicionaremos temporariamente um elemento TextBlock , com o nome apropriado, ao MainPage.xaml projeto C++/WinRT.
FEATURE_NAME é um campo estático de MainPage (um campo C# const é essencialmente estático em seu comportamento), definido em SampleConfiguration.cs. Para C++/WinRT, em vez de um campo (estático), nós o tornaremos a expressão C++/WinRT de uma propriedade somente leitura (estática). A forma como o C++/WinRT expressa um getter de propriedade é por meio de uma função que retorna o valor da propriedade e não recebe parâmetros (um acessador). Portanto, o campo estático FEATURE_NAME C# torna-se a função de acessador estático FEATURE_NAME do C++/WinRT (neste caso, retornando a literal de cadeia de caracteres).
Aliás, faríamos o mesmo se estivéssemos portando uma propriedade somente leitura de C#. Para uma propriedade gravável em C#, a maneira do C++/ WinRT de expressar um setter de propriedades é como uma função void que assume o valor da propriedade como um parâmetro (um modificador). Em ambos os casos, se o campo ou a propriedade C# for estático, o acessor e/ou modificador C++/WinRT também será estático.
A corrente é um campo estático (não uma constante) do MainPage. Novamente, vamos transformá-lo em (a expressão C++/WinRT de) uma propriedade somente leitura e, mais uma vez, torná-lo estático. Onde FEATURE_NAME é constante, Atual não é. Portanto, em C++/WinRT, precisaremos de um campo subjacente, e nosso método acessor o retornará. Portanto, no projeto C++/WinRT, vamos declarar em MainPage.h um campo estático privado chamado atual, definiremos/inicializaremos atualmente ( MainPage.cpp porque ele tem duração de armazenamento estático) e o acessaremos por meio de uma função de acessador estático público chamada Current.
O próprio construtor executa algumas atribuições, que são fáceis de portar.
No projeto C++/WinRT, adicione um novo item Visual C++>Code>Arquivo C++ (.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, exclua os corpos de função existentes para MainPage.cppMainPage::Current() e MainPage::FEATURE_NAME(), pois 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 desenvolvido para ser consumido dentro do projeto, seja para interoperabilidade com XAML ou entre binários. O tipo de implementação é o que você usa para implementar os recursos que expôs no tipo projetado. Como a declaração de MainPage::current (in MainPage.h) aparece no namespace de implementação (winrt::SDKTemplate::implementation), um MainPage não qualificado teria se referido ao tipo de implementação. Portanto, nos qualificamos com SDKTemplate:: para ficar claro que queremos que MainPage::current seja uma instância do tipo projetado winrt::SDKTemplate::MainPage.
No construtor, há alguns pontos relacionados a MainPage::current = *this; que merecem uma explicação.
- Quando você usa o ponteiro
thisem um membro do tipo de implementação, o ponteirothisé, é claro, um ponteiro para o tipo de implementação. - Para converter o ponteiro
thisno tipo projetado correspondente, desreferencie-o. Desde que você gere seu tipo de implementação por meio da IDL (como temos aqui), o tipo de implementação tem um operador de conversão que converte em seu tipo projetado. É por isso que a tarefa aqui funciona.
Para obter mais informações sobre esses detalhes, consulte Instanciando e retornando interfaces e tipos de implementação.
SampleTitle().Text(FEATURE_NAME()); também está no construtor. A SampleTitle() parte é uma chamada para uma função de acessador simples chamada SampleTitle, que retorna o TextBlock que adicionamos ao XAML. Sempre que você define x:Name para um elemento XAML, o compilador XAML gera um acessador que é nomeado para o elemento. A parte .Text(...) chama a função de modificador 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 literal de cadeia de caracteres. Ao todo, essa linha de código define a propriedade Text do TextBlock chamada SampleTitle.
Observe que, como as cadeias de caracteres são longas no Windows Runtime, para portar uma literal de cadeia de caracteres, nós a prefixamos com o prefixo de codificação de caractere largo L. Então, alteramos (por exemplo) "uma literal de cadeia de caracteres" para "uma literal de cadeia de caracteres" L. Confira também Literais de cadeia de caracteres longa.
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) }
};
...
}
...
Em nossa investigação anterior, sabemos que essa coleção de objetos Scenario está sendo exibida em um ListBox. No C++/WinRT, há limites para o tipo 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:
- Classes de runtime ou
- IInspectable.
No caso de IInspectable, se os elementos não forem eles próprios classes de runtime, precisarão ser de um tipo que possa passar por conversão boxing e unboxing para (e de) IInspectable. E isso significa que eles precisam ser tipos do Windows Runtime (confira Fazer conversão boxing e unboxing de valores para IInspectable).
Para este estudo de caso, não fizemos do Cenário uma classe de runtime. Mas essa ainda é uma opção razoável. E haverá casos em seu próprio trabalho de portabilidade em que uma classe de runtime será definitivamente a opção mais adequada. Por exemplo, se você precisar tornar o tipo de elemento observável (consulte controles XAML; associar 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.
Como, neste passo a passo, não estamos trabalhando com uma classe de runtime para o tipo Scenario, precisamos pensar em conversão boxing. Se tivéssemos tornado Scenario um struct C++ regular, não seria possível empregar a conversão boxing. Mas declaramos Scenario como um struct em IDL e, portanto, podemos empacotá-lo.
Temos a opção de fazer a conversão boxing de Scenario antecipadamente ou aguardar até estarmos prestes a atribuir ao ItemsSource e fazer a conversão boxing just-in-time. Aqui estão algumas considerações sobre essas duas opções.
- Conversão boxing antecipada. Para essa opção, nosso membro de dados é uma coleção de IInspectable pronta para atribuição à interface do usuário. Na inicialização, armazenamos os objetos Scenario nesse membro de dados. Precisamos de apenas uma cópia dessa coleção, mas é necessário fazer a conversão unboxing de um elemento toda vez que precisamos ler seus campos.
- Conversão boxing just-in-time. Para esta opção, nosso elemento de dados é uma coleção de Scenario. Quando chega a hora de atribuir à interface do usuário, fazemos a conversão boxing dos objetos de Scenario do membro de dados para uma nova coleção de IInspectable. Podemos ler os campos dos elementos no membro de dados sem fazer a conversão unboxing, mas precisamos de duas cópias da coleção.
Como você pode ver, para uma coleção pequena como essa, os prós e os contras fazem com que pareça um esforço desnecessário. Portanto, para este estudo de caso, usaremos a opção just-in-time.
O membro de cenários é 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 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 dar a eles o nome de scenariosInner e scenarios, respectivamente. Vamos declarar scenariosInner em MainPage.h. E, como ele tem duração de armazenamento estático, vamos defini-lo/inicializá-lo em um .cpp arquivo (SampleConfiguration.cppnesse caso).
Edite MainPage.h e SampleConfiguration.cpp para corresponder às listas 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, exclua o corpo da função existente de MainPage.cpp para MainPage::scenarios() , porque agora estamos definindo esse método no arquivo de cabeçalho.
Como você pode ver, em SampleConfiguration.cpp, inicializamos o membro de dados estático scenariosInner ao chamar uma função auxiliar do C++/WinRT chamada winrt::single_threaded_observable_vector. Essa função cria um novo objeto de coleção Windows Runtime para nós e retorna-o como uma interface IObservableVector. Como, neste exemplo, a coleção não é observável (não precisa ser, porque não adiciona nem remove elementos após a inicialização), poderíamos ter optado por chamar winrt::single_threaded_vector. Essa função retorna a coleção como uma interface IVector .
Para obter mais informações sobre coleções e sobre como vincular-se a elas, consulte Controles de itens XAML; vincular a uma coleção C++/WinRT e 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 em frente e adicionar cinco novas páginas XAML em branco ao projeto.
Adicionar cinco novas páginas XAML em branco
Adicione um novo item Visual C++>Página em Branco (C++/WinRT) ao projeto (certifique-se de que seja o modelo de item Página em Branco (C++/WinRT), e não o Página em Branco). Nomeie como CopyText. A nova página XAML é definida dentro do namespace SDKTemplate , que é o que queremos.
Repita o processo acima mais quatro vezes e dê às páginas XAML os nomes CopyImage, CopyFiles, HistoryAndRoaming e OtherScenarios.
Agora você poderá compilar novamente, se desejar.
NotifyUser
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 em elementos XAML que ainda não portamos. Portanto, por enquanto, vamos apenas executar um método UpdateStatus no projeto C++/WinRT e vamos portar isso 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 envia atualizações de interface do usuário para o thread principal. No WinUI 3, isso usa Microsoft. UI. Dispatching.DispatcherQueue em vez do mais antigoCoreDispatcher. No C++/WinRT, sempre que você quiser usar um tipo de um namespace Windows ou Microsoft, será necessário incluir o arquivo de cabeçalho do namespace C++/WinRT correspondente (para obter mais informações sobre isso, consulte Introdução ao C++/WinRT). Nesse caso, como você verá na listagem de código abaixo, o cabeçalho é winrt/Microsoft.UI.Dispatching.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 deve ser chamado na classe de runtime, portanto, não o declararemos no IDL.
Depois de portar MainPage.NotifyUser e remover MainPage.UpdateStatus, é isso que temos no projeto C++/WinRT. Após essa listagem de código, examinaremos alguns dos detalhes.
// pch.h
...
#include <winrt/Microsoft.UI.Dispatching.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 (DispatcherQueue().HasThreadAccess())
{
UpdateStatus(strMessage, type);
}
else
{
DispatcherQueue().TryEnqueue([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 pontuar as propriedades aninhadas. Portanto, o tipo C# MainPage pode acessar sua própria propriedade Dispatcher com a sintaxe Dispatcher. E C# pode pontuar esse valor com sintaxe como Dispatcher.HasThreadAccess. No C++/WinRT, as propriedades são implementadas como funções de acessador, portanto, a sintaxe é diferente apenas na adição de parênteses para cada chamada de função.
| C# | C++/WinRT |
|---|---|
Dispatcher.HasThreadAccess |
DispatcherQueue().HasThreadAccess() |
Quando a versão C# do NotifyUser chama Dispatcher.RunAsync, o equivalente do WinUI 3 usa DispatcherQueue.TryEnqueue. A versão C++/WinRT implementa o delegado de retorno de chamada como uma função lambda. No C++/WinRT, capturamos os dois parâmetros que vamos usar, bem como o this ponteiro (pois vamos chamar uma função 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.
Implementar os membros restantes do MainPage
Vamos fazer uma lista completa dos membros da classe MainPage (implementados em MainPage.xaml.cs e SampleConfiguration.cs) para que possamos ver quais portamos até agora e quais ainda estão pendentes.
| Membro | Access | Status |
|---|---|---|
| Construtor da MainPage | public |
Portabilidade efetuada |
| Propriedade atual | public |
Portabilidade efetuada |
| propriedade FEATURE_NAME | public |
Portabilidade efetuada |
| Propriedade IsClipboardContentChangedEnabled | public |
Não iniciado |
| Propriedade Cenários | public |
Portabilidade efetuada |
| Método BuildClipboardFormatsOutputString | public |
Não iniciado |
| Método DisplayToast | public |
Não iniciado |
| Método EnableClipboardContentChangedNotifications | public |
Não iniciado |
| Método NotifyUser | public |
Portabilidade efetuada |
| Método OnNavigatedTo | protected |
Não iniciado |
| campo isApplicationWindowActive | private |
Não iniciado |
| campo needToPrintClipboardFormat | private |
Não iniciado |
| campo "scenarios" | private |
Portabilidade efetuada |
| 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 |
| método ScenarioControl_SelectionChanged | private |
Não iniciado |
| Método UpdateStatus | private |
Removido |
Falaremos sobre os membros ainda não portados nas próximas subseções.
Note
De tempos em tempos, nos depararemos com referências no código-fonte aos elementos da interface do usuário na marcação XAML (in MainPage.xaml). À medida que chegarmos a essas referências, trabalharemos temporariamente com elas adicionando elementos simples de espaço reservado ao XAML. Dessa forma, o projeto continuará a ser compilado após cada subseção. A alternativa é resolver as referências copiando todo o conteúdo MainPage.xaml do projeto C# para o projeto C++/WinRT agora. Mas, se fizermos isso, vai demorar muito até podermos fazer uma parada e compilar de novo, o que pode acabar ocultando erros de digitação ou outros problemas que cometermos ao longo do caminho.
Quando terminarmos de portar o código imperativo para a classe MainPage, em seguida, copiaremos o conteúdo do arquivo XAML e teremos certeza de que o projeto ainda será compilado.
IsClipboardContentChangedEnabled
Esta é uma propriedade C# get-set que usa como padrão false. É um membro do MainPage e é definido em SampleConfiguration.cs.
Para C++/WinRT, precisaremos de uma função acessora, uma função mutadora e um membro de dados subjacente na forma de campo. Como IsClipboardContentChangedEnabled representa o estado de um dos cenários no exemplo, em vez do estado de MainPage em si, criaremos os novos membros em um novo tipo de utilitário chamado SampleState. E implementaremos isso em nosso SampleConfiguration.cpp arquivo de código-fonte e faremos 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 nosso SampleConfiguration.cpp no projeto C++/WinRT, adicione um novo item Visual C++>Código>Arquivo de Cabeçalho (.h) com o nome SampleConfiguration.h. Edite SampleConfiguration.h e SampleConfiguration.cpp para que correspondam à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 nesse caso).
BuildClipboardFormatsOutputString
Esse método é um membro público do 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();
}
...
No C++/WinRT, faremos do BuildClipboardFormatsOutputString um método estático público de SampleState. Podemos torná-lo static porque ele não acessa nenhum membro de instância.
Para usar os tipos Clipboard e DataPackageView no C++/WinRT, precisaremos incluir o arquivo de cabeçalho winrt/Windows.ApplicationModel.DataTransfer.h do namespace Windows do C++/WinRT.
Em C#, a propriedade DataPackageView.AvailableFormats é um IReadOnlyList, para que possamos acessar a propriedade Count disso. No C++/WinRT, a função acessador DataPackageView::AvailableFormats retorna um IVectorView, que tem uma função de acessador size que podemos chamar.
Para portar o uso do tipo C# System.Text.StringBuilder , 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 você faz com um StringBuilder, use o operador de inserção (<<) com um fluxo de saída, como wostringstream. Para obter mais informações, consulte a programação iostream e a formatação de cadeias de caracteres C++/WinRT.
O código C# constrói um StringBuilder com a new palavra-chave. No C#, os objetos são tipos de referência por padrão, declarados no heap com new. No padrão C++ moderno, os objetos são tipos de valor por padrão, declarados na pilha (sem usar new). Portanto, portamos StringBuilder output = new StringBuilder(); para C++/WinRT como simples std::wostringstream output;.
A palavra-chave C# var instrui o compilador a inferir um tipo. Você migra var para auto em C++/WinRT. Mas, no 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 você deseja um tipo especial de referência que associa corretamente se ele é inicializado com um lvalue ou com um rvalue. E você expressa isso com auto&&. Esse é o formulário que você vê usado no for loop no código portado abaixo. Para obter uma introdução a lvalues e rvalues, consulte categorias de valor e referências a elas.
Edite pch.h, SampleConfiguration.h e 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() };
}
Note
A sintaxe na linha de código DataPackageView clipboardContent{ Clipboard::GetContent() }; usa um recurso do C++ padrão moderno chamado de inicialização uniforme, com o uso característico de chaves em vez de um sinal de =. Essa sintaxe deixa claro que a inicialização, em vez de atribuição, está ocorrendo. Se você preferir a forma de sintaxe que se parece com a atribuição (mas na verdade não é), você pode substituir a sintaxe acima pelo equivalente DataPackageView clipboardContent = Clipboard::GetContent();. No entanto, é uma boa ideia se sentir confortável com as duas maneiras de expressar a inicialização, pois é provável que você veja ambos usados com frequência no código encontrado.
DisplayToast
DisplayToast é um método estático público da classe C# MainPage e você o encontrará definido em SampleConfiguration.cs. No C++/WinRT, faremos dele um método estático público de SampleState.
Já encontramos a maioria dos dados e técnicas relevantes para portar esse método. Um novo item a ser observado é a portabilidade de uma literal de cadeia de caracteres textual C# (@) para uma literal de cadeia de caracteres bruta C++ padrão (LR).
Além disso, ao referenciar os tipos ToastNotification e XmlDocument no C++/WinRT, você pode qualificá-los pelo nome do namespace ou editar SampleConfiguration.cpp e adicionar using namespace diretivas, como o exemplo a seguir.
using namespace Windows::UI::Notifications;
Você tem a mesma escolha quando faz referência ao tipo XmlDocument e sempre que faz referência a qualquer outro tipo de Windows Runtime.
Além desses itens, basta seguir as mesmas diretrizes que você fez anteriormente para realizar as etapas a seguir.
- Declare o método em
SampleConfiguration.he defina-o emSampleConfiguration.cpp. - Edite
pch.hpara incluir todos os arquivos de cabeçalho do namespace Windows C++/WinRT necessários. - Construa objetos C++/WinRT na pilha, não no heap.
- Substitua chamadas para acessadores de propriedade pela sintaxe de chamada de função (
()).
Uma causa muito comum de erros de compilador/vinculador é esquecer de incluir os arquivos de cabeçalho do namespace do Windows C++/WinRT de 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 fazer a portabilidade do DisplayToast por conta própria, poderá comparar seus resultados com o código na versão C++/WinRT no ZIP do código-fonte da amostra de Clipboard que você baixou.
EnableClipboardContentChangedNotifications
EnableClipboardContentChangedNotifications é um método estático público da classe C# MainPage 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) { ... }
...
No C++/WinRT, faremos dele um método estático público de SampleState.
Em C#, você usa a sintaxe dos operadores += e -= para registrar e cancelar o registro de delegados de tratamento de eventos. No C++/WinRT, você tem várias opções sintacticas para registrar/revogar um delegado, conforme descrito em Manipular eventos usando delegados em C++/WinRT. Mas a forma geral é que você se registra e cancela esse registro por meio de chamadas a um par de funções nomeadas de acordo com o evento. Para se registrar, passe seu representante para a função de registro e recupere um token de revogação em troca (um winrt::event_token). Para revogar, você passa esse token para a função de revogação. Nesse caso, o manipulador é estático, e (como você pode ver na listagem de códigos a seguir) a sintaxe da chamada de função é direta.
Tokens semelhantes são de fato usados, internamente, em C#. Mas a linguagem torna esse detalhe implícito. O C++/WinRT torna isso explícito.
O tipo de objeto aparece nas assinaturas do manipulador de eventos em C#. Na linguagem C#, o objeto é um alias para o tipo .NET System.Object. 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 que correspondam à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, Microsoft::UI::Xaml::WindowActivatedEventArgs const& e);
...
// SampleConfiguration.cpp
...
using namespace Windows::Foundation;
using namespace Microsoft::UI;
using namespace Microsoft::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 em nossa lista de membros para portar, portanto, nós os acessaremos em subseções posteriores.
OnNavigatedTo
OnNavigatedTo é um método protegido da classe C# MainPage e é definido em MainPage.xaml.cs. Aqui está, junto com o ListBox em XAML ao qual faz referência.
<!-- 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, pois aqui é onde nossa coleção de objetos Scenario é atribuída à interface do usuário. O código C# cria uma System.Collections.Generic.List de objetos Scenario e a atribui à 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 Scenario (observe o uso do $ caractere especial).
No C++/WinRT, tornaremos OnNavigatedTo um método público de MainPage. E adicionaremos um elemento ListBox stub ao XAML para que um build seja bem-sucedido. Após a listagem de código, examinaremos alguns dos detalhes.
<!-- MainPage.xaml -->
...
<StackPanel ...>
...
<ListBox x:Name="ScenarioControl" />
</StackPanel>
...
// MainPage.h
...
void OnNavigatedTo(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
...
// MainPage.cpp
...
using namespace winrt::Microsoft::UI::Xaml;
using namespace winrt::Microsoft::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_vector, mas desta vez para criar uma coleção de IInspectable. Isso fez parte da decisão que tomamos de realizar a conversão boxing de nossos objetos de Scenario just-in-time.
E, em vez de usar a interpolação de strings do C# aqui, usamos a combinação da função to_hstring com o operador de concatenação de winrt::hstring.
isApplicationWindowActive
Em C#, isApplicationWindowActive é um campo privado bool simples que pertence à classe MainPage e é definido em SampleConfiguration.cs. O padrão é false. No C++/WinRT, faremos dele um campo estático privado de SampleState (pelos motivos que já descrevemos) nos arquivos SampleConfiguration.h e SampleConfiguration.cpp, com o mesmo valor padrão.
Já vimos como declarar, definir e inicializar um campo estático. Para relembrar, veja o que fizemos com o campo isClipboardContentChangedEnabled e faça o mesmo com isApplicationWindowActive.
needToPrintClipboardFormat
Mesmo padrão que isApplicationWindowActive (consulte o título imediatamente antes deste).
Clique no botão
Button_Click é um método privado (manipulação de eventos) da classe C# MainPage e é definido em MainPage.xaml.cs. Aqui está ele, junto com o XAML SplitView ao qual ele faz referência e 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 das private:declarações). 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 a acesse. Por outro lado, se você registrar um manipulador de eventos no 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, Microsoft::UI::Xaml::RoutedEventArgs const& e);
private:
...
// MainPage.cpp
void MainPage::Button_Click(Windows::Foundation::IInspectable const& /* sender */, Microsoft::UI::Xaml::RoutedEventArgs const& /* e */)
{
Splitter().IsPaneOpen(!Splitter().IsPaneOpen());
}
DisplayChangedFormats
Em C#, DisplayChangedFormats é um método privado que pertence à classe MainPage e é definido em SampleConfiguration.cs.
private void DisplayChangedFormats()
{
string output = "Clipboard content has changed!" + Environment.NewLine;
output += BuildClipboardFormatsOutputString();
NotifyUser(output, NotifyType.StatusMessage);
}
No C++/WinRT, vamos torná-lo um campo estático privado de SampleState (ele não acessa nenhum membro de instância), nos arquivos SampleConfiguration.h e SampleConfiguration.cpp. O código C# para esse método não usa System.Text.StringBuilder; mas ele faz formatação de cadeia de caracteres suficiente que para a versão C++/WinRT este é outro bom lugar para usar std::wostringstream.
Em vez da propriedade System.Environment.NewLine estática, 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 seu fluxo em um winrt::hstring e retorna isso. Chamamos a função hstring::c_str para transformar o hstring retornado novamente 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 ele adiante, para que os métodos possam inserir cadeias de caracteres diretamente nele.
É isso que fazemos na versão em C++/WinRT do código-fonte do exemplo Clipboard (no arquivo 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. Em seguida, os métodos SampleState::D isplayChangedFormats e SampleState::BuildClipboardFormatsOutputString são refatorados para chamar esse novo método. Ele é funcionalmente equivalente às listagens de código neste tópico, mas é mais eficiente.
Footer_Click
Footer_Click é um manipulador de eventos assíncrono que pertence à classe C# MainPage 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, ela foi descompactada de uma linha para quatro, a fim de facilitar a visualização do que está fazendo e, consequentemente, de como devemos portá-la.
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 após o await, portanto, ele não precisa da await (nem da async palavra-chave). Ele provavelmente os usa para evitar a mensagem do IntelliSense em Visual Studio.
O método C++/WinRT equivalente também será assíncrono (porque chama Launcher.LaunchUriAsync). Mas ele não precisa co_await, nem retornar um objeto assíncrono. Para obter informações sobre co_await e 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 esse é um manipulador de eventos para o evento Click de um HyperlinkButton, o objeto chamado remetente é, na verdade, um HyperlinkButton. Portanto, a conversão de tipo é segura (como alternativa, poderíamos ter expressado essa 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 isso está definido como uma cadeia de caracteres que representa uma url da Web). Embora a propriedade FrameworkElement.Tag (HyperlinkButton seja um FrameworkElement) seja do tipo objeto, em C# podemos stringify isso com Object.ToString. Na cadeia de caracteres resultante, criamos um objeto Uri . Por fim (com a ajuda do Shell), iniciamos um navegador e navegamos até a URL.
Aqui está o método portado para C++/WinRT (novamente, expandido para maior 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, Microsoft::UI::Xaml::RoutedEventArgs const& e);
private:
...
// MainPage.cpp
...
using namespace winrt::Windows::Foundation;
using namespace winrt::Microsoft::UI::Xaml::Controls;
...
void MainPage::Footer_Click(Windows::Foundation::IInspectable const& sender, Microsoft::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 as no objeto remetente para convertê-la em HyperlinkButton. No C++/WinRT, a propriedade Tag é um IInspectable (o equivalente a Object). Mas não há tostring no IInspectable. Em vez disso, precisamos fazer a conversão unboxing de IInspectable para um valor escalar (uma cadeia de caracteres, neste caso). Mais uma vez, para saber mais sobre conversões boxing e unboxing, confira Fazer conversão boxing e unboxing de valores para IInspectable.
As duas últimas linhas repetem os padrões de portabilidade que já vimos antes e elas praticamente ecoam a versão do C#.
HandleClipboardChanged
Não há nada de novo na adaptação deste método. Você pode comparar as versões em C# e C++/WinRT no arquivo ZIP do código-fonte do exemplo Clipboard que você baixou.
OnClipboardChanged e OnWindowActivated
Até o momento, temos apenas stubs vazios para esses dois manipuladores de eventos. Mas portá-los é simples, e isso não traz nada de novo para discutir.
ScenarioControl_SelectionChanged
Este é outro manipulador de eventos privado pertencente à classe C# MainPage e definido em MainPage.xaml.cs. No C++/WinRT, vamos torná-lo público e implementá-lo dentro MainPage.h e MainPage.cpp.
Para esse método, precisaremos de MainPage::navigating, que é um campo booliano privado, inicializado para false. E você precisará de um Frame in MainPage.xaml, chamado ScenarioFrame. Mas, além desses detalhes, a adaptação deste método não traz nenhuma técnica nova.
Se, em vez de portar manualmente, você estiver copiando código da versão C++/WinRT no arquivo ZIP do código-fonte do exemplo Clipboard que você baixou, verá que MainPage::NavigateTo é usado ali. Por enquanto, basta refatorar o conteúdo de NavigateTo para ScenarioControl_SelectionChanged.
Updatestatus
Temos apenas um stub até agora para MainPage.UpdateStatus. A portabilidade da implementação, novamente, cobre uma ampla área. Um novo ponto a observar é que, enquanto em C# podemos comparar uma cadeia de caracteres com String.Empty, em C++/WinRT, chamamos a função winrt::hstring::empty . Outra é que nullptr é o equivalente padrão em C++ para null do C#.
Você pode executar o restante da portabilidade 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 uma Border chamada StatusBorder. - Para
MainPage.xamladicionar um TextBlock chamado StatusBlock. - Para
MainPage.xaml, adicione um StackPanel com o nome StatusPanel. - Para
pch.h, adicionar#include "winrt/Microsoft.UI.Xaml.Media.h". - Para
pch.h, adicionar#include "winrt/Microsoft.UI.Xaml.Automation.Peers.h". - Para
MainPage.cppadicionarusing namespace winrt::Microsoft::UI::Xaml::Media;. - Para
MainPage.cppadicionarusing namespace winrt::Microsoft::UI::Xaml::Automation::Peers;.
Copiar o XAML e os estilos necessários para finalizar a migração de MainPage
Para XAML, o caso ideal é que você possa usar a mesma marcação XAML em um C# e um projeto C++/WinRT. E a amostra de Clipboard consiste em um desses casos.
Em seu arquivo Styles.xaml, a amostra de Clipboard tem um XAML ResourceDictionary de estilos, que são aplicados aos botões, menus e outros elementos da interface do usuário do aplicativo. A Styles.xaml página é mesclada em App.xaml. E depois há o ponto de partida padrão MainPage.xaml para a interface do usuário, que já vimos brevemente. Agora podemos reutilizar esses três arquivos .xaml, sem alterações, na versão C++/WinRT do projeto.
Assim como acontece com os arquivos de recursos, você pode optar por referenciar os mesmos arquivos XAML compartilhados em várias versões do seu aplicativo. Neste passo a passo, apenas para simplificar, 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, em seguida, cole esses dois arquivos na pasta \Clipboard\Clipboard em seu projeto C++/WinRT, escolhendo substituir os arquivos quando solicitado.
No projeto C++/WinRT em Visual Studio, clique em Mostrar Todos os Arquivos para ative-o. Agora, adicione uma nova pasta, imediatamente no nó do projeto e nomeie-a Styles. No Explorador de Arquivos, navegue até a \Clipboard_sample\SharedContent\xaml pasta, selecione e copie Styles.xamle cole-a na \Clipboard\Clipboard\Styles pasta que você 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, diretamente sob o nó do projeto, chamada Styles. Navegue até a 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 (em 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 desativar.
Agora terminamos de portar a MainPage e, se você acompanhou as etapas, seu projeto em C++/WinRT agora vai compilar e executar.
Consolidar seus arquivos .idl
Além do ponto de partida padrão MainPage.xaml da interface do usuário, o exemplo Clipboard tem outras cinco páginas XAML específicas para cada cenário, juntamente com seus arquivos de código subjacente correspondentes. Usaremos novamente a marcação XAML real de todas essas páginas, inalterada, na versão C++/WinRT do projeto. E examinaremos como portar o code-behind nas próximas seções principais. Mas antes disso, vamos falar sobre IDL.
Há valor na consolidação de IDL para suas classes de runtime em um único arquivo IDL. Para saber mais sobre esse valor, consulte Como separar classes de runtime em arquivos Midl (.idl). Em seguida, consolidaremos o conteúdo de CopyFiles.idl, , CopyImage.idl, CopyText.idlHistoryAndRoaming.idle OtherScenarios.idl movendo essa IDL para um único arquivo chamado Project.idl (e, em seguida, excluindo os arquivos originais).
Enquanto estamos fazendo isso, vamos também 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. Nomeie como Project.idl. Substitua todo o conteúdo de Project.idl pelo seguinte código.
// Project.idl
namespace SDKTemplate
{
[default_interface]
runtimeclass CopyFiles : Microsoft.UI.Xaml.Controls.Page
{
CopyFiles();
}
[default_interface]
runtimeclass CopyImage : Microsoft.UI.Xaml.Controls.Page
{
CopyImage();
}
[default_interface]
runtimeclass CopyText : Microsoft.UI.Xaml.Controls.Page
{
CopyText();
}
[default_interface]
runtimeclass HistoryAndRoaming : Microsoft.UI.Xaml.Controls.Page
{
HistoryAndRoaming();
}
[default_interface]
runtimeclass OtherScenarios : Microsoft.UI.Xaml.Controls.Page
{
OtherScenarios();
}
}
Como você pode ver, essa é apenas uma cópia do conteúdo dos arquivos .idl individuais, tudo dentro de um namespace e com MyProperty removido de cada classe de runtime.
Em Gerenciador de Soluções em Visual Studio, selecione múltiplamente todos os arquivos IDL originais (CopyFiles.idl, , CopyImage.idl, CopyText.idlHistoryAndRoaming.idle ) e OtherScenarios.idlEdite>Remova-os (escolha Excluir na caixa de diálogo).
Por fim — e para concluir a remoção de MyProperty —, nos arquivos .h e .cpp de cada um desses mesmos cinco tipos de página XAML, exclua as declarações e definições das funções acessora int32_t MyProperty() e modificadora void MyProperty(int32_t).
Aliás, é sempre uma boa ideia fazer com que o nome dos arquivos XAML corresponda 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 lidar com nomes diferentes para o mesmo artefato tornará seu projeto mais compreensível, mais fácil de manter e mais fácil de trabalhar nele.
CopyFiles
No projeto C#, o tipo de página XAML CopyFiles é implementado nos arquivos de código-fonte CopyFiles.xaml e CopyFiles.xaml.cs. Vamos examinar cada um dos membros de CopyFiles, um por um.
rootPage
Este é um campo privado.
// CopyFiles.xaml.cs
...
public sealed partial class CopyFiles : Page
{
MainPage rootPage = MainPage.Current;
...
}
...
No 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 desejado (ele chama apenas InitializeComponent).
CopyButton_Click
O método CopyButton_Click C# é um manipulador de eventos e, na async palavra-chave em sua assinatura, podemos dizer que o método faz um trabalho assíncrono. No C++/WinRT, implementamos um método assíncrono como uma coroutina. Para obter uma introdução à simultaneidade no C++/WinRT, juntamente com uma descrição do que é uma coroutina , consulte Simultaneidade e operações assíncronas com C++/WinRT.
É comum querer agendar mais trabalho após a conclusão de uma corrotina e, para tais casos, a corrotina retornaria um tipo de objeto assíncrono que pode ser esperado e que, opcionalmente, relata o progresso. Mas essas considerações normalmente não se aplicam a um manipulador de eventos. Portanto, quando você tem um manipulador de eventos que executa operações assíncronas, você pode implementá-lo como uma coroutina que retorna winrt::fire_and_forget. Para obter mais informações, consulte Fire and forget.
Embora a ideia de uma corrotina do tipo disparar e esquecer seja você não se importar com a conclusão, o trabalho ainda continua (ou é suspenso, aguardando a continuação) em segundo plano. Você pode ver na implementação em C# que CopyButton_Click depende do ponteiro this (ele acessa o membro de dados da instância rootPage). Portanto, devemos ter certeza de que o ponteiro this (um ponteiro para um objeto CopyFiles) dura mais que a corrotina CopyButton_Click. Em uma situação como este aplicativo 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. Caso a página CopyFiles seja destruída (ao navegar para fora dela) enquanto CopyButton_Click ainda estiver em execução em uma thread em segundo plano, não será seguro acessar rootPage. Para tornar a corrotina correta, ela precisa obter uma referência forte ao ponteiro this e manter essa referência durante a corrotina. Para obter mais informações, consulte referências fortes e fracas em C++/WinRT.
Se você examinar a versão do 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() };
...
}
Vamos examinar os outros aspectos do código portado que são notáveis.
No código, criamos uma instância de um objeto FileOpenPicker e duas linhas depois acessamos a 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 aspecto interessante é o valor que estamos passando para esse método, em que espera-se uma matriz. Aqui está a linha de código.
filePicker.FileTypeFilter().ReplaceAll({ L"*" });
O valor que estamos passando ({ L"*" }) é uma lista de inicializadores C++ padrão. Ele contém um único objeto, nesse caso, mas uma lista de inicializadores pode conter qualquer número de objetos separados por vírgulas. As partes do C++/WinRT que permitem a conveniência de passar uma lista de inicializadores para um método como esse são explicadas em listas de inicializadores Standard.
Portamos a palavra-chave await de C# para co_await em C++/WinRT. Este é o exemplo do código.
auto storageItems{ co_await filePicker.PickMultipleFilesAsync() };
Em seguida, considere essa linha de código C#.
dataPackage.SetStorageItems(storageItems);
O C# é capaz de converter implicitamente o IReadOnlyList<StorageFile> representado por storageItems em IEnumerable<IStorageItem>, esperado por DataPackage.SetStorageItems. Mas, em C++/WinRT, precisamos converter explicitamente de IVectorView<StorageFile> para IIterable<IStorageItem>. Portanto, 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
Esse é outro manipulador de eventos na forma de uma corrotina do tipo disparar e esquecer. Vamos examinar os aspectos do código portado que são notáveis.
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 erro com C++/WinRT.
Um exemplo de como testar se um objeto C# é null ou não é if (storageItems != null). No C++/WinRT, podemos contar com um operador de conversão para bool, que faz internamente o teste em relação a nullptr.
Aqui está uma versão ligeiramente simplificada de um fragmento de código da versão do C++/WinRT portada do exemplo.
std::wostringstream output;
output << std::wstring_view(ApplicationData::Current().LocalFolder().Path());
Construir um std::wstring_view de um winrt::hstring como esse ilustra uma alternativa para chamar a função hstring::c_str (para transformar o winrt::hstring em uma cadeia de caracteres no estilo C). Essa alternativa funciona graças ao operador de conversão do hstringpara std::wstring_view.
Considere esse fragmento de C#.
var file = storageItem as StorageFile;
if (file != null)
...
Para portar a palavra-chave C# as para C++/WinRT, até agora vimos a função como usada algumas vezes. Essa função gerará uma exceção se a conversão de tipo falhar. Mas se quisermos que a conversão retorne nullptr se ela falhar (para que possamos lidar com essa condição no código), em vez disso, usaremos a função try_as .
auto file{ storageItem.try_as<StorageFile>() };
if (file)
...
Copie o XAML necessário para concluir a migração de CopyFiles
Agora você pode selecionar todo o conteúdo do arquivo CopyFiles.xaml na pasta shared do download original do código-fonte de exemplo 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 ClickHandler, já que acabamos de substituir a marcação XAML correspondente.
Agora concluímos a portabilidade de CopyFiles e, se você estiver acompanhando as etapas, seu projeto C++/WinRT agora será compilado e executado e o cenário CopyFiles estará funcional.
CopyImage
Para portar o tipo de página XAML copyImage , siga o mesmo processo que para CopyFiles. Ao portar CopyImage, você encontrará o uso da instrução using do C#, 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 de 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();
}
Objetos C++/WinRT implementam IClosable principalmente em benefício de linguagens que não têm 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. Todavia, há ocasiões em que é bom chamá-lo, e essa é uma delas. Aqui, o identificador imageStream é um wrapper com contagem de referências para um objeto subjacente do Windows Runtime (nesse caso, um objeto que implementa IRandomAccessStreamWithContentType). Embora possamos determinar que o finalizador de imageStream (o destruidor dele) será executado no final do escopo delimitador (as chaves), não podemos ter certeza de que esse finalizador chamará Fechar. Isso ocorre porque passamos imageStream para outras APIs e elas ainda podem estar contribuindo para a contagem de referência do objeto Windows Runtime subjacente. Portanto, este é um caso em que é uma boa ideia chamar Close explicitamente. Para obter mais informações, confira Preciso chamar IClosable::Close em classes de tempo de execução que utilizo?.
Em seguida, considere a expressão C# (uint)(imageDecoder.OrientedPixelWidth * 0.5), que você encontrará no manipulador de eventos OnDeferredImageRequestedHandler. Essa expressão multiplica um uint por um double, resultando em um double. Em seguida, ele converte isso em um uint. No C++/WinRT, poderíamos usar um elenco de estilo C 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 C# de CopyImage.OnDeferredImageRequestedHandler tem uma finally cláusula, mas não uma catch cláusula. Fomos um pouco mais longe na versão do C++/WinRT e implementamos uma catch cláusula para que possamos relatar se a renderização atrasada foi ou não bem-sucedida.
Portar o restante desta página XAML não traz nada de novo para discutir. Lembre-se de excluir a função ClickHandler fictícia. E, assim como com CopyFiles, a última etapa da migração é selecionar todo o conteúdo de CopyImage.xaml e colá-lo no mesmo arquivo do projeto C++/WinRT.
CopyText
Você pode portar CopyText.xaml e CopyText.xaml.cs usando técnicas que já abordamos.
HistoryAndRoaming
Há alguns pontos de interesse que surgem ao portar o tipo de página XAML HistoryAndRoaming.
Primeiro, examine o código-fonte C# e siga o fluxo de controle de OnNavigatedTo até o manipulador de eventos OnHistoryEnabledChanged e finalmente até a função assíncrona CheckHistoryAndRoaming (que não é esperada, portanto, é basicamente disparar e esquecer). Como CheckHistoryAndRoaming é assíncrono, precisaremos ter cuidado no C++/WinRT com o tempo de vida do ponteiro this. Você pode ver o resultado se observar a implementação no arquivo de código-fonte HistoryAndRoaming.cpp. Primeiro, quando anexamos representantes aos eventos Clipboard::HistoryEnabledChanged e Clipboard::RoamingEnabledChanged, usamos apenas uma referência fraca ao objeto da página HistoryAndRoaming. Fazemos isso criando o representante com uma dependência do valor retornado de winrt::get_weak, em vez de uma dependência do ponteiro this. Isso significa que o próprio delegado, que eventualmente chama o código assíncrono, não manterá a página HistoryAndRoaming ativa se sairmos dela.
E, em segundo lugar, quando finalmente atingirmos nossa corrotina CheckHistoryAndRoaming do tipo disparar e esquecer, a primeira coisa que deveremos fazer é realizar uma referência forte a this para garantir que a página HistoryAndRoaming sobreviva pelo menos até que a corrotina seja finalmente concluída. Para obter mais informações sobre os dois aspectos descritos, consulte referências fortes e fracas no C++/WinRT.
Encontramos 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. A thread que inicialmente invoca um manipulador de eventos é a 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. No C#, a solução é expedir o trabalho para o thread da interface do usuário. Em C++/WinRT, podemos usar a função winrt::resume_foreground juntamente com a this fila de despachantes do ponteiro DispatcherQueue para suspender a corrotina e retomá-la imediatamente na thread principal da interface do usuário.
A expressão relevante é co_await winrt::resume_foreground(DispatcherQueue());. A versão mais curta é obtida cortesia de um operador de conversão fornecido pelo C++/WinRT.
Portar o restante desta página XAML não traz nada de novo para discutir. Lembre-se de excluir a função fictícia ClickHandler e copiar sobre a marcação XAML.
OtherScenarios
Você pode portar OtherScenarios.xaml e OtherScenarios.xaml.cs usando técnicas que já abordamos.
Conclusion
Espero que este passo a passo tenha armado você com informações e técnicas de portabilidade suficientes que agora você pode ir em frente e portar seus próprios aplicativos C# para C++/WinRT. Como lembrete, você pode continuar consultando as versões antes (C#) e depois (C++/WinRT) do código-fonte no exemplo Clipboard e compará-las lado a lado para ver a correspondência.
Tópicos relacionados:
Windows developer