Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este tópico apresenta um estudo de caso sobre a portabilidade de um dos exemplos de aplicações da Plataforma Universal do Windows (UWP) de C# para C++/WinRT. Pode ganhar prática e experiência na adaptação acompanhando o guia passo a passo e adaptando você mesmo o exemplo à medida que progride.
Note
O código-fonte que está a ser portado é uma aplicação UWP C#. O código de destino C++/WinRT neste artigo foi escrito para WinUI 3 (SDK de Aplicações Windows). Sempre que a fonte UWP utiliza 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. Pode usar os padrões de código da coluna C++/WinRT diretamente na sua aplicação WinUI 3.
Para obter um catálogo completo 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 ficheiros de código-fonte em C# e C++
Num projeto C#, os ficheiros de código-fonte são principalmente .cs ficheiros. Quando passares para C++, vais notar que há mais tipos de ficheiros de código-fonte a que deves habituar-te. A razão está relacionada 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 as suas funções (os seus métodos).
Uma declaração de função descreve apenas a assinatura da função (o seu tipo de retorno, o seu nome e os seus tipos e nomes de parâmetros). Uma definição de função inclui o corpo da função (a sua implementação).
É um pouco diferente no que toca a tipos. Define um tipo fornecendo o respetivo nome e, declarando, no mínimo, todas as suas funções-membro (e outros membros). É isso mesmo, podes definir um tipo mesmo que não definas as suas funções membros.
- Os ficheiros de código-fonte C++ comuns são
.h(dot aitch) e.cppficheiros. Um.hficheiro é um ficheiro cabeçalho , e define um ou mais tipos. Embora possas definir funções membros num cabeçalho, normalmente é para isso que serve um.cppficheiro. Assim, para um tipo hipotético de C++ MyClass, definiria MyClass emMyClass.h, e definiria as suas funções membros emMyClass.cpp. Para outros programadores reutilizarem as tuas classes, partilhas apenas os.hficheiros e o código objeto. Manterias os teus.cppficheiros em segredo, porque a implementação constitui a tua propriedade intelectual. - Cabeçalho pré-compilado (
pch.h). Normalmente há um conjunto de ficheiros de cabeçalho que inclui na sua aplicação, e não muda esses ficheiros com muita frequência. Assim, em vez de processares o conteúdo desse conjunto de cabeçalhos cada vez que compilas, podes agregar esses cabeçalhos num ficheiro, compilar uma vez e depois usar a saída dessa etapa de pré-compilação cada vez que construíres. Faz-se isso através de um ficheiro de cabeçalho pré-compilado (normalmente chamadopch.h). -
.idlficheiros. Estes ficheiros contêm a Linguagem de Definição de Interface (IDL). Pode encarar o IDL como ficheiros de cabeçalho para tipos do Windows Runtime. Falaremos mais sobre o IDL na secção IDL para o tipo MainPage.
Descarregue e teste a amostra do Clipboard
Visite a página de exemplo do Clipboard e clique em Download ZIP. Descompacta o ficheiro descarregado e vê a estrutura das 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 ficheiros — usados tanto pela versão C# como pela versão C++/WinRT — podem ser encontrados nas
sharedpastas e.SharedContent
O guia neste tópico mostra como pode recriar a versão C++/WinRT do exemplo do Clipboard, portando-o a partir do código-fonte em C#. Assim, podes ver como podes portar os teus próprios projetos em C# para C++/WinRT.
Para perceberes o que a amostra faz, abre a solução em C# (\Clipboard_sample\cs\Clipboard.sln), altera a configuração conforme apropriado (talvez para x64), constrói e executa. A própria interface de utilizador (UI) do exemplo guia-o pelas suas várias funcionalidades, passo a passo.
Tip
A pasta raiz do exemplo que descarregou pode ter o nome Clipboard em vez de Clipboard_sample. Mas continuaremos a referir-nos a essa pasta como Clipboard_sample para a distinguir da versão C++/WinRT que vai criar numa etapa posterior.
Crie uma Aplicação em Branco, chamada Clipboard
Note
Para informações sobre a instalação e utilização da Extensão Visual Studio C++/WinRT (VSIX) e do pacote NuGet (que juntos fornecem suporte para templates de projeto e compilação), consulte o suporte Visual Studio para C++/WinRT.
Inicie o processo de portabilidade criando um novo projeto em C++/WinRT em Microsoft Visual Studio. Crie um novo projeto com o modelo de projeto para C++ Blank App, Packaged (WinUI 3 in Desktop). Define o nome para Clipboard e (para que a estrutura da pasta corresponda ao walkthrough) certifica-te de que Colocar solução e projeto no mesmo diretório estão desmarcados.
Só para estabelecer um ponto de referência, assegura-te 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 de ser instaladas lado a lado na mesma máquina, então os ficheiros de manifesto do pacote de aplicação dos dois projetos podemPackage.appxmanifest ser idênticos. Nesse caso, podes simplesmente copiar Package.appxmanifest do projeto C# para o projeto C++/WinRT, e está feito.
Para que as duas versões da amostra coexistam, precisam de identificadores diferentes. Nesse caso, no projeto C++/WinRT, abra o Package.appxmanifest ficheiro num editor XML e anote estes três valores.
- Dentro do elemento /Package/Identity , note o valor do atributo Nome . Este é o nome da embalagem. Para um projeto recém-criado, o projeto atribuir-lhe-á um valor inicial de um GUID único.
- Dentro do elemento /Package/Applications/Application , note o valor do atributo Id . Este é o ID da aplicação.
- Dentro do elemento /Package/mp:PhoneIdentity , note o valor do atributo PhoneProductId . Novamente, para um projeto recém-criado, isto será definido com o mesmo GUID com que o nome do pacote é definido.
Depois copie Package.appxmanifest do projeto C# para o projeto C++/WinRT. Finalmente, podes restaurar os três valores que referiste. Ou pode editar os valores copiados para os tornar únicos e/ou adequados para a aplicação e para a sua organização (como normalmente faria num projeto novo). Por exemplo, neste caso, em vez de restaurar o valor do nome do pacote, podemos simplesmente alterar o valor copiado da Microsoft. SDKSamples.Clipboard.CSa Microsoft. SDKSamples.Clipboard.CppWinRT. E podemos deixar o ID da aplicação definido como App. Desde que o nome do pacote ou o id da aplicação sejam diferentes, as duas aplicações terão IDs de Modelo de Utilizador de Aplicação (AUMIDs) diferentes. E é isso que é necessário para duas aplicações serem instaladas lado a lado na mesma máquina.
Para efeitos deste walkthrough, faz sentido fazer algumas outras alterações em Package.appxmanifest. Existem três ocorrências da cadeia de caracteres Clipboard C# Sample. Muda isso para Clipboard C++/WinRT Sample.
No projeto C++/WinRT, o Package.appxmanifest ficheiro e o projeto estão agora fora de sincronia em relação aos ficheiros de asset que referenciam. Para resolver isso, primeiro remova os assets do projeto C++/WinRT selecionando todos os ficheiros na Assets pasta (no Explorador de Soluções no Visual Studio) e removendo-os (escolha Delete no diálogo).
O projeto C# faz referência a ficheiros de assets a partir de uma pasta partilhada. Pode fazer o mesmo no projeto C++/WinRT, ou pode copiar os ficheiros como faremos neste guia.
Navega até à \Clipboard_sample\SharedContent\media pasta. Selecione os sete ficheiros que o projeto C# inclui (, , , , , , e microsoft-sdk.png), copie-os e cole-os na smalltile-sdk.png pasta do novo projeto. splash-sdk.pngsquaretile-sdk.pngstorelogo-sdk.pngtile-sdk.pngwindows-sdk.png\Clipboard\Clipboard\Assets
Clique com o botão direito na Assets pasta (no Explorador de Soluções no projeto C++/WinRT), >adicione>item existente... e navegue até \Clipboard\Clipboard\Assets. No seletor de ficheiros, selecione os sete ficheiros e clique em Adicionar.
Package.appxmanifest está agora novamente sincronizado com os ficheiros de assets do projeto.
MainPage, incluindo a funcionalidade que configura o exemplo
O exemplo do Clipboard — como todos os exemplos de aplicações da Universal Windows Windows Platform (UWP) — consiste numa coleção de cenários que o utilizador pode percorrer um a um. A coleção de cenários numa dada amostra está configurada no código-fonte da amostra. Cada cenário na coleção é um elemento de dados que armazena um título, bem como o tipo de classe no projeto que implementa o cenário.
Na versão C# do exemplo, se olhares para o SampleConfiguration.cs ficheiro do código-fonte, verás 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 suas propriedades Title e ClassType .
Nas próximas subsecções, vamos ver como portar o MainPage e o Scenario.
IDL para o tipo MainPage
Comecemos esta secção por falar brevemente sobre a Interface Definition Language (IDL) e como ela nos ajuda quando programamos com C++/WinRT. IDL é um tipo de código-fonte que descreve a superfície 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 porção projetada do tipo contrasta com a implementação interna real do tipo, que, claro, não é invocável nem pública. É apenas a parte projetada que definimos no IDL.
Depois de criar o código-fonte IDL (dentro de um .idl ficheiro), pode então compilar o IDL em ficheiros de metadados legíveis por máquina (também conhecidos como Metadados do Windows). Esses ficheiros de metadados têm a extensão .winmd, e aqui estão algumas das suas utilizações.
- A
.winmdpode descrever os tipos de Windows Runtime num componente. Quando referencias um Windows Runtime Component (WRC) de um projeto de aplicação, o projeto de aplicação lê os Metadados do Windows pertencentes ao WRC (esses metadados podem estar num ficheiro separado, ou podem ser empacotados no mesmo ficheiro que o próprio WRC) para que possas consumir os tipos do WRC dentro da aplicação. - A
.winmdpode descrever os tipos de Windows Runtime numa parte da sua aplicação para que possam ser consumidos por uma parte diferente da mesma aplicação. Por exemplo, um tipo Windows Runtime que é consumido por uma página XAML na mesma aplicação. - Para facilitar a utilização de tipos do Windows Runtime (incorporados ou de terceiros), o sistema de compilação do C++/WinRT usa ficheiros
.winmdpara gerar tipos de invólucro que representam as partes expostas desses tipos do Windows Runtime. - Para facilitar a implementação dos seus próprios tipos de Windows Runtime, o sistema de compilação C++/WinRT transforma o seu IDL num
.winmdficheiro, e depois usa isso para gerar wrappers para a sua projeção, bem como stubs sobre os quais basear a sua implementação (falaremos mais sobre estes 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 secção do tema, vamos analisar o tipo MainPage em C# com algum detalhe. Decidiremos quais as partes que precisam de estar na projeção do tipo MainPage C++/WinRT (ou seja, na sua superfície chamável, ou pública), e quais podem ser apenas parte da sua implementação. Essa distinção é importante porque, quando criarmos o nosso IDL (o que faremos na secção seguinte a esta), definiremos apenas as partes que podem ser chamadas lá dentro.
Os ficheiros de código-fonte C# que implementam juntos o tipo MainPage são: MainPage.xaml (que vamos portar em breve, copiando-o), MainPage.xaml.cs, e SampleConfiguration.cs.
Na versão C++/WinRT, vamos incorporar o nosso tipo MainPage em ficheiros de código-fonte de forma semelhante. Vamos pegar na lógica em MainPage.xaml.cs e traduzi-la, na maior 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 numa aplicação C# Plataforma Universal do Windows (UWP) são, claro, tipos de Windows Runtime. Mas quando crias um tipo numa aplicação C++/WinRT, podes escolher se esse tipo é um tipo de Windows Runtime ou uma classe/struct/enumeração normal em C++.
Qualquer página XAML no nosso projeto precisa de ser do tipo Windows Runtime, por isso a MainPage tem de ser do tipo Windows Runtime. No projeto C++/WinRT, o MainPage já é um tipo Windows Runtime, por isso não precisamos de alterar esse aspeto. Especificamente, é uma classe de runtime.
- Para mais detalhes sobre se deve ou não criar uma classe em tempo de execução para um determinado tipo, consulte o tópico APIs de Autor com C++/WinRT.
- Em C++/WinRT, a implementação interna de uma classe de runtime, e as partes projetadas (públicas) dela, existem sob a forma de duas classes diferentes. Estes são conhecidos como tipo de implementação e tipo projetado. Pode saber mais sobre eles no tópico mencionado no ponto com marca anterior e também em Consumir APIs com C++/WinRT.
- Para mais informações sobre a relação entre classes de runtime e ficheiros IDL (
.idl), pode ler e acompanhar o tópico Controlos XAML; associar a uma propriedade C++/WinRT. Esse tópico explica 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, já temos o ficheiro necessário MainPage.idl no projeto C++/WinRT. Isso acontece porque o modelo de projeto o criou por nós. Mas mais tarde, neste guia, vamos adicionar ficheiros adicionais .idl ao projeto.
Em breve veremos uma listagem de exatamente que IDL precisamos de adicionar ao ficheiro MainPage.idl existente. Antes disso, temos de refletir um pouco sobre o que tem, e o que não tem, de constar da IDL.
Para determinar quais os membros do MainPage que devemos declarar em MainPage.idl (para que se tornem parte da classe de runtime MainPage ), e quais podem simplesmente ser membros do tipo de implementação MainPage , vamos fazer uma lista dos membros da classe MainPage em C#. Encontramos esses membros consultando MainPage.xaml.cs e SampleConfiguration.cs.
Encontramos um total de doze protected e private campos e métodos. E encontramos os seguintes public membros.
- O construtor predefinido
MainPage(). - Os campos estáticos Current e FEATURE_NAME.
- As propriedades IsClipboardContentChangedEnabled e Scenarios.
- Os métodos BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotifications e NotifyUser.
São esses public membros que são candidatos a declarar em MainPage.idl. Por isso, vamos analisar cada um deles e ver se precisam de fazer parte da classe de runtime MainPage , ou se só precisam de fazer parte da sua implementação.
- O construtor predefinido
MainPage(). Para uma Page XAML, é normal declarar um construtor por defeito no respetivo IDL. Dessa forma, o framework da interface XAML pode ativar o tipo. - O campo estático Current é usado a partir das páginas XAML individuais do cenário para aceder à instância de MainPage da aplicação. Uma vez que Current não está a ser utilizado para interoperar com a estrutura XAML (nem é utilizado entre unidades de compilação), poderíamos reservá-lo para ser exclusivamente um membro do tipo de implementação. Com os teus próprios projetos, em casos como este, podes optar por fazer isso. Mas, como o campo é uma instância do tipo projetado, parece lógico declará-lo no IDL. Portanto, é isso que vamos fazer 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 é acedido no tipo MainPage. Mais uma vez, escolher declará-lo no IDL torna o nosso código um pouco mais limpo.
- A propriedade IsClipboardContentChangedEnabled é usada apenas na classe OtherScenarios . Assim, durante a migração, vamos simplificar um pouco e fazer dele um campo privado da classe de tempo de execução OtherScenarios. Assim, esse não entra no IDL.
- A propriedade Scenarios é uma coleção de objetos do tipo Scenario (um tipo que mencionámos anteriormente). Vamos falar sobre Cenário na próxima subsecção, por isso vamos também deixar a propriedade Scenarios para essa altura.
- Os métodos BuildClipboardFormatsOutputString, DisplayToast e EnableClipboardContentChangedNotifications são funções utilitárias que têm mais a ver com o estado geral da amostra do que com a página principal. Durante a portabilidade, vamos refatorar estes três métodos num novo tipo de utilitário chamado SampleState (que não precisa de ser do tipo Windows Runtime). Por essa razão, estes três métodos não serão incluídos no IDL.
- O método NotifyUser é chamado a partir das páginas XAML de cada cenário, na instância de MainPage que é devolvida pelo campo estático Current. Como (como já referido) Current é uma instância do tipo projetado, precisamos de declarar NotifyUser no IDL. O NotifyUser recebe um parâmetro do tipo NotifyType. Falaremos disso na próxima subsecção.
Qualquer membro ao qual pretendas associar dados também tem de ser declarado em IDL (quer utilizes {x:Bind} ou {Binding}). Para mais informações, consulte Ligação de dados.
Estamos a fazer progressos: estamos a desenvolver uma lista de quais membros adicionar e quais não adicionar ao MainPage.idl ficheiro. Mas ainda temos de discutir a propriedade Scenarios e o tipo NotifyType . Então vamos fazer isso a seguir.
IDL para os tipos Scenario e NotifyType
A classe de cenário é definida em SampleConfiguration.cs. Temos de decidir como portar essa classe para C++/WinRT. Por defeito, provavelmente faríamos dele um C++ comum struct. Mas se o Scenario estiver a ser usado entre binários, ou para interoperar com o framework XAML, então precisa de ser declarado no IDL como um tipo de Windows Runtime.
Ao estudar o código-fonte em C#, verificamos 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á a ser atribuída à propriedade ItemsSource de um ListBox (ou seja, um controlo de itens). Como o Scenarioprecisa de interoperar com o XAML, tem de ser do tipo Windows Runtime. Portanto, tem de ser definido em IDL. Definir o tipo Scenario em IDL faz com que o sistema de compilação do C++/WinRT gere por si uma definição de código-fonte de Scenario num ficheiro de cabeçalho interno (cujo nome e localização não são importantes para esta explicação).
E lembra-se que o MainPage.Scenarios é uma coleção de objetos Scenario , que acabámos de dizer que têm de estar no IDL. Por essa razão, o próprio MainPage.Scenarios também precisa de ser declarado no IDL.
NotifyType é um enum declarado em MainPage.xaml.cs de C#. Como passamos o NotifyType para um método pertencente à classe de runtime MainPage, o NotifyType também precisa de ser um tipo Windows Runtime; e precisa de ser definido em MainPage.idl.
Agora vamos adicionar ao MainPage.idl ficheiro os novos tipos e o novo membro da Mainpage que decidimos declarar no IDL. Ao mesmo tempo, vamos remover do IDL os membros provisórios do Mainpage que o modelo do projeto Visual Studio nos deu.
Portanto, no teu projeto C++/WinRT, abre MainPage.idl, e edita-o para que fique igual à listagem abaixo. Note que uma das edições é mudar o nome do espaço de nomes de Clipboard para SDKTemplate. Se quiser, pode simplesmente substituir todo o conteúdo de MainPage.idl pelo código seguinte. Outra alteração a notar é que estamos a mudar 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 mais informações sobre o conteúdo de um .idl ficheiro num projeto C++/WinRT, consulte Microsoft Interface Definition Language 3.0.
Com o seu próprio trabalho de portabilidade, pode não querer ou precisar de mudar o nome do namespace como fizemos acima. Estamos a fazê-lo aqui apenas porque o namespace padrão do projeto C# que estamos a portar é SDKTemplate; enquanto o nome do projeto e da assembleia é Clipboard.
Mas, à medida que avançamos com a adaptação neste guia passo a passo, iremos alterar, no código-fonte, todas as ocorrências do nome do espaço de nomes Clipboard para SDKTemplate. Há também um espaço nas propriedades do projeto C++/WinRT onde aparece o nome do espaço de nomes Clipboard , por isso vamos aproveitar para mudar 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.
Guarde o IDL e volte a gerar ficheiros stub
O tópico controlos XAML; vincular a uma propriedade C++/WinRT introduz a noção de ficheiros stub e mostra-te um guia deles em ação. Também mencionámos stubs anteriormente neste tópico quando mencionámos que o sistema de compilação C++/WinRT transforma o conteúdo dos teus .idl ficheiros em metadados do Windows, e depois, a partir desses metadados, uma ferramenta chamada cppwinrt.exe gera stubs nos quais podes basear a tua implementação.
Sempre que adicionas, removes ou alteras algo no teu IDL e compilas, o sistema de compilação atualiza as implementações dos stubs nesses ficheiros de stubs. Por isso, cada vez que muda o seu IDL e build, recomendamos que veja esses ficheiros stubs, copie quaisquer assinaturas alteradas e cole no seu projeto. Vamos dar mais detalhes e exemplos de como exatamente fazer isso daqui a pouco. Mas a vantagem de fazer isto é dar-lhe uma forma sem erros de saber em todos os momentos qual deve ser a forma do seu tipo de implementação e qual deve ser a assinatura dos seus métodos.
Neste ponto do walkthrough, já terminámos de editar o MainPage.idl ficheiro por agora, por isso deves guardá-lo agora. O projeto não vai ser concluído neste momento, mas fazer uma build agora é útil porque regenera os ficheiros stub do MainPage. Por isso, constrói o projeto agora e ignora quaisquer erros de compilação.
Para este projeto C++/WinRT, os ficheiros stub são gerados na \Clipboard\Clipboard\Generated Files\sources pasta. Vais encontrá-los lá depois de a construção parcial terminar (novamente, como esperado, a construção não terá sucesso total. Mas o passo que nos interessa — gerar esboços — terá sido um sucesso). Os ficheiros que nos interessam são MainPage.h e MainPage.cpp.
Nesses dois ficheiros stub, verá novas implementações stub dos membros do MainPage que adicionámos ao IDL (Current e FEATURE_NAME, por exemplo). Convém copiar essas implementações provisórias para os ficheiros MainPage.h e MainPage.cpp que já existem no projeto. Ao mesmo tempo, tal como fizemos com o IDL, vamos remover desses ficheiros existentes os membros marcadores do Mainpage que o modelo do projeto Visual Studio nos deu (a propriedade fictícia chamada MyProperty e o gestor de eventos chamado ClickHandler).
Na verdade, o único membro da versão atual de MainPage que queremos manter é o construtor.
Depois de copiares os novos membros dos ficheiros de stubs, apagares os membros que não queremos e atualizares o namespace, os ficheiros MainPage.h e MainPage.cpp no teu projeto devem corresponder às listagens de código abaixo. Note que existem dois tipos de MainPage . Um no espaço de nomes implementation e outro no espaço de nomes factory_implementation. A única alteração que fizemos ao factory_implementation foi adicionar SDKTemplate ao seu espaço de nomes.
// 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 um exemplo. No nosso IDL, declaramos uma string com String, e quando a cppwinrt.exe ferramenta gera código C++/WinRT para nós, usa o tipo winrt::hstring . Sempre que encontrarmos uma string em código C#, portamos para winrt::hstring. Para mais informações, consulte Gestão de cadeias em C++/WinRT.
Para obter uma explicação dos parâmetros const& nas assinaturas dos métodos, consulte Passagem de parâmetros.
Atualize todas as restantes declarações/referências de espaço de nomes e compile
Antes de construir o projeto C++/WinRT, encontre quaisquer declarações (e referências) ao espaço de nomes Clipboard e mude-as para SDKTemplate.
-
MainPage.xamleApp.xaml. O espaço de nomes aparece nos valores dos atributosx:Classexmlns:local. -
App.idl. -
App.h. -
App.cpp. Existem duasusing namespacediretivas (pesquisa pela substringusing namespace Clipboard) e duas qualificações do tipo MainPage (pesquisa porClipboard::MainPage). Esses precisam de ser alterados.
Como removemos o manipulador de eventos da MainPage, vá também a MainPage.xaml e elimine o elemento Button do código de marcação.
Guarda todos os ficheiros. Limpa a solução (Constrói>Solução Limpa) e depois constrói-a. Tendo seguido todas as mudanças até agora, exatamente como está escrito, espera-se que a construção tenha sucesso.
Implementar os membros de MainPage que declarámos no IDL
O construtor, Current, e FEATURE_NAME
Aqui está o código relevante (do projeto C#) que precisamos de 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, vamos reutilizar MainPage.xaml na totalidade (copiando-o). Por agora (abaixo), vamos adicionar 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 no seu comportamento), definido em SampleConfiguration.cs. Para C++/WinRT, em vez de um campo (estático), vamos usar a expressão C++/WinRT para uma propriedade (estática) só de leitura. A forma C++/WinRT de expressar um obtedor de propriedades é como uma função que devolve o valor da propriedade e não toma parâmetros (um acessor). Assim, o campo estático FEATURE_NAME do C# torna-se a função acessora estática FEATURE_NAME do C++/WinRT (neste caso, que devolve o literal de cadeia).
A propósito, faríamos o mesmo ao portar uma propriedade só de leitura em C#. Para uma propriedade escrevível em C#, a forma C++/WinRT de expressar um definidor de propriedades é como uma void função que toma o valor da propriedade como parâmetro (um mutator). Em qualquer dos casos, se o campo ou propriedade C# for estático, então também o é o acessor e/ou mutador C++/WinRT.
Corrente é um campo estático (não constante) da Página Principal. Mais uma vez, vamos fazer dela, na sua expressão em C++/WinRT, uma propriedade só de leitura e, mais uma vez, torná-la estática. Onde FEATURE_NAME é constante, Corrente não é. Por isso, em C++/WinRT precisamos de um campo de apoio, e o nosso acessor devolve-lo. Assim, no projeto C++/WinRT, vamos declarar num MainPage.h campo estático privado chamado corrente, definir/inicializar corrente em MainPage.cpp (porque tem duração de armazenamento estática), e acederemos a ela através de uma função pública de acesso estático chamada Corrente.
O próprio construtor faz um par de atribuições, que são fáceis de transpor.
No projeto C++/WinRT, adicione um novo item Visual C++>Code>C++ File (.cpp) com o nome SampleConfiguration.cpp.
Editar MainPage.xaml, MainPage.h, MainPage.cpp, e 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 eliminar os corpos de funções existentes de MainPage.cppMainPage::Current() e MainPage::FEATURE_NAME(), porque agora estamos a definir esses métodos noutros locais.
Como pode ver, MainPage::current é declarado 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 dentro do projeto para interoperabilidade XAML, quer entre binários. O tipo de implementação é aquilo que usas para implementar as funcionalidades que expuseste no tipo projetado. Como a declaração de MainPage::current (em MainPage.h) aparece dentro do espaço de nomes de implementação (winrt::SDKTemplate::implementation), uma MainPage não qualificada referir-se-ia ao tipo de implementação. Assim, qualificamo-nos 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 com MainPage::current = *this; que merecem ser explicados.
- Quando se utiliza o ponteiro
thisnum membro do tipo de implementação, o ponteirothisé, naturalmente, um ponteiro para o tipo de implementação. - Para converter o ponteiro
thisno tipo projetado correspondente, desreferencie-o. Desde que gere o 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 o tipo projetado. É por isso que o trabalho aqui funciona.
Para mais informações sobre esses detalhes, consulte Instanciar e devolver tipos e interfaces de implementação.
Também no construtor está SampleTitle().Text(FEATURE_NAME());. A SampleTitle() parte é uma chamada a uma função acessória simples chamada SampleTitle, que devolve o TextBlock que adicionámos ao XAML. Sempre que colocas x:Name um elemento XAML, o compilador XAML gera um acessório para ti que tem o nome do elemento. A parte .Text(...) invoca a função mutadora Text no objeto TextBlock que o acessor SampleTitle devolveu. E FEATURE_NAME() chama a nossa função acessora estática MainPage::FEATURE_NAME para retornar a cadeia literal. No conjunto, essa linha de código define a propriedade Text do TextBlock chamada SampleTitle.
Note que, uma vez que as cadeias de caracteres são de caracteres largos no Windows Runtime, para migrar um literal de cadeia de caracteres, antepomos-lhe o prefixo de codificação de caracteres largos L. Assim, mudamos (por exemplo) "um literal de cadeia de caracteres" para L"um literal de cadeia de caracteres". Veja também literais de cadeias largas.
Cenários
Aqui está o código C# relevante que precisamos de 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) }
};
...
}
...
Pela nossa investigação anterior, sabemos que esta coleção de objetos do Cenário está a ser exibida numa ListBox. Em C++/WinRT, existem limites ao tipo de coleção que podemos atribuir à propriedade ItemsSource de um controlo de items. A coleção deve ser ou um vetor ou um vetor observável, e os seus elementos devem ser um dos seguintes:
- Ou classes de runtime, ou
- IInspectable.
No caso de IInspectable, se os elementos não forem, eles próprios, classes em tempo de execução, então esses elementos têm de ser de um tipo que possa ser encapsulado e desencapsulado de e para IInspectable. E isso significa que têm de ser tipos do Windows Runtime (ver Boxing e unboxing de valores para IInspectable).
Para este estudo de caso, não fizemos do Cenário uma classe de execução. Ainda assim, essa é uma opção razoável. E haverá casos no teu próprio trabalho de portabilidade em que uma classe de tempo de execução será claramente a melhor opção. Por exemplo, se precisar de tornar o tipo de elemento observável (ver controlos XAML; vincular a uma propriedade C++/WinRT), ou se o elemento precisar de métodos por qualquer outra razão, e for mais do que apenas um conjunto de membros de dados.
Como, neste guia, não vamos optar por uma classe de runtime para o tipo Cenário , então precisamos de pensar no boxe. Se tivéssemos feito Scenario um C++ struct normal, não conseguiríamos empacotá-lo. Mas declarámos Scenario como um struct no IDL e, por isso, podemos encapsulá-lo.
Resta-nos a escolha entre encapsular o Scenario antecipadamente, ou esperar até estarmos prestes a atribuí-los ao ItemsSource e encapsulá-los de forma just-in-time. Aqui estão algumas considerações relativamente a essas duas opções.
- Boxe antecipadamente. Para esta opção, o nosso membro de dados é uma coleção de IInspectable pronta para ser atribuída à IU. Na inicialização, encaixamos os objetos Scenario nesse membro de dados. Precisamos apenas de uma cópia dessa coleção, mas temos de desembalar um elemento sempre que precisamos de ler os seus campos.
- Boxe mesmo a tempo. Para esta opção, o nosso membro de dados é uma coleção de Cenários. Quando chega a altura de atribuir à interface, agrupamos os objetos Scenario do membro de dados numa nova coleção de IInspectable. Podemos ler os campos dos elementos no membro de dados sem abrir a caixa, mas precisamos de duas cópias da coleção.
Como se pode ver, para uma coleção pequena deste tamanho, os prós e os contras acabam por se equivaler. Portanto, para este estudo de caso, vamos optar pela opção just-in-time.
O membro cenários é um campo de MainPage, definido e inicializado em SampleConfiguration.cs. E Scenarios é uma propriedade só de leitura de MainPage, definida em MainPage.xaml.cs (e implementada de modo a devolver simplesmente o campo scenarios). Faremos algo semelhante no projeto C++/WinRT; mas vamos tornar os dois membros estáticos (já que precisamos de apenas uma instância em toda a aplicação; e para que possamos aceder a eles sem necessidade de uma instância de classe). E vamos dar-lhes o nome de scenariosInner e scenarios, respetivamente. Vamos declarar scenariosInner em MainPage.h. E, como tem duração estática de armazenamento, vamos defini-lo/inicializá-lo num .cpp ficheiro (SampleConfiguration.cpp, neste caso).
Editar 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 apagar o corpo da função existente de MainPage.cpp para MainPage::scenarios(), porque agora estamos a definir o método no ficheiro de cabeçalho.
Como pode ver, em SampleConfiguration.cpp, inicializamos o membro de dados estático scenariosInner através de uma chamada a uma função auxiliar de C++/WinRT chamada winrt::single_threaded_observable_vector. Essa função cria um novo objeto de coleção Windows Runtime para nós e devolve-o como uma interface IObservableVector. Como neste exemplo a coleção não é observável (não precisa de ser, pois não adiciona nem remove elementos após a inicialização), poderíamos ter optado por chamar winrt::single_threaded_vector. Essa função devolve a coleção como uma interface IVector .
Para mais informações sobre coleções e sobre a vinculação às mesmas, consulte controlos de itens XAML; fazer a vinculação a uma coleção C++/WinRT e Coleções com C++/WinRT.
O código de inicialização que acabou de adicionar faz referência a tipos que ainda não estão no projeto (por exemplo, winrt::SDKTemplate::CopyText. Para resolver isso, vamos avançar 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++>Blank Page (C++/WinRT) ao projeto (certifique-se de que é o modelo de item Página em Branco (C++/WinRT ), e não o modelo de Página em Branco ). Dê-lhe o nome de CopyText. A nova página XAML está definida no espaço de nomes SDKTemplate, que é o pretendido.
Repetir o processo acima mais quatro vezes e nomear as páginas CopyImageXAML , CopyFiles, HistoryAndRoaming, e OtherScenarios.
Agora poderás construir novamente, se quiseres.
NotifyUser
No projeto C#, encontrará a implementação do método MainPage.NotifyUser em MainPage.xaml.cs.
O MainPage.NotifyUser tem uma dependência do MainPage.UpdateStatus, e esse método, por sua vez, tem dependências em elementos XAML que ainda não portámos. Por agora, vamos simplesmente criar um método UpdateStatus no projeto C++/WinRT, e depois portá-lo.
Aqui está o código C# relevante que precisamos de 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) { ... }{
...
O NotifyUser envia atualizações da interface para o tópico principal. No WinUI 3, isto usa a Microsoft. UI. Dispatching.DispatcherQueue em vez do mais antigo CoreDispatcher. Em C++/WinRT, sempre que quiseres usar um tipo de um namespace Windows ou Microsoft, precisas de incluir o ficheiro de cabeçalho correspondente do namespace C++/WinRT (para mais informações, vê Começar com C++/WinRT). Neste caso, como verá na lista de código abaixo, o cabeçalho é winrt/Microsoft.UI.Dispatching.h, e vamos incluí-lo em pch.h.
O UpdateStatus é privado. Assim, vamos transformá-lo num método privado no nosso tipo de implementação MainPage. O UpdateStatus não é suposto ser chamado na classe de runtime, por isso não o vamos declarar no IDL.
Depois de portar o MainPage.NotifyUser e eliminar o MainPage.UpdateStatus, isto é o que temos no projeto C++/WinRT. Após esta lista de códigos, vamos analisar alguns 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#, pode usar a notação por pontos para aceder a propriedades aninhadas. Assim, o tipo MainPage em C# pode aceder à sua própria propriedade Dispatcher com a sintaxe Dispatcher. E o C# pode ainda aceder com ponto a esse valor com sintaxe como Dispatcher.HasThreadAccess. Em C++/WinRT, as propriedades são implementadas como funções de acesso, por isso a sintaxe difere apenas porque se adicionam parênteses para cada chamada de função.
| C# | C++/WinRT |
|---|---|
Dispatcher.HasThreadAccess |
DispatcherQueue().HasThreadAccess() |
Quando a versão C# do NotifyUser chama o Dispatcher.RunAsync, o equivalente do WinUI 3 usa o DispatcherQueue.TryEnqueue. A versão C++/WinRT implementa o delegado de chamada de retorno como uma função lambda. Em C++/WinRT, capturamos os dois parâmetros que vamos usar, bem como o this ponteiro (já que vamos chamar uma função membro). Há mais informações sobre como implementar delegates como lambdas, e exemplos de código, no tópico Lidar com eventos usando delegates em C++/WinRT.
Implementar os restantes membros do MainPage
Vamos fazer uma lista completa dos membros de MainPage (implementados em MainPage.xaml.cs e SampleConfiguration.cs) para que possamos ver quais já portámos até agora e quais ainda faltam portar.
| Membro | Access | Status |
|---|---|---|
| construtor de MainPage | public |
Portado |
| Propriedade atual | public |
Portado |
| FEATURE_NAME propriedade | public |
Portado |
| Propriedade IsClipboardContentChangedEnabled | public |
Não iniciado |
| Propriedade dos cenários | public |
Portado |
| 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 |
Portado |
| Método OnNavigatedTo | protected |
Não iniciado |
| o campo isApplicationWindowActive | private |
Não iniciado |
| campo needToPrintClipboardFormat | private |
Não iniciado |
| campo cenários | private |
Portado |
| Button_Click método | private |
Não iniciado |
| Método DisplayChangedFormats | private |
Não iniciado |
| Footer_Click método | 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 |
Eliminado |
Falaremos dos elementos que ainda não foram migrados nas subseções seguintes.
Note
De tempos a tempos, deparamo-nos com referências no código-fonte a elementos da interface na marcação XAML (em MainPage.xaml). À medida que formos encontrando estas referências, iremos contorná-las temporariamente adicionando elementos simples de marcador ao XAML. Desta forma, o projeto continuará a crescer após cada subsecção. A alternativa consiste em resolver as referências copiando o conteúdo todo de MainPage.xaml do projeto C# para o projeto C++/WinRT agora. Mas se fizermos isso, vai demorar muito tempo até podermos fazer uma paragem e construir novamente (podendo assim ocultar quaisquer erros tipográficos ou outros que cometemos pelo caminho).
Quando terminarmos de portar o código imperativo para a classe MainPage , então copiaremos o conteúdo do ficheiro XAML e ficaremos confiantes de que o projeto continuará a ser construído.
IsClipboardContentChangedEnabled
Esta é uma propriedade get-set C# que por defeito é false. É um membro do MainPage, e está definido em SampleConfiguration.cs.
Para C++/WinRT, precisaremos de uma função de acesso, uma função mutadora e de um membro de dados subjacente como campo. Como IsClipboardContentChangedEnabled representa o estado de um dos cenários no exemplo, em vez do estado da própria MainPage, vamos criar os novos membros num novo tipo utilitário chamado SampleState. E vamos implementar isso no nosso SampleConfiguration.cpp ficheiro de código-fonte, e vamos criar os membros static (já que precisamos de apenas uma instância em toda a aplicação; e para podermos aceder a eles 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>Ficheiro de Cabeçalho (.h) com o nome de SampleConfiguration.h. Editar 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;
}
}
Mais uma vez, um campo com static armazenamento (como SampleState::isClipboardContentChangedEnabled) deve ser definido uma vez na aplicação, e um .cpp ficheiro é um bom local para isso (SampleConfiguration.cpp neste caso).
BuildClipboardFormatsOutputString
Este método é um membro público do MainPage, e está 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, vamos tornar o BuildClipboardFormatsOutputString um método público e estático do SampleState. Podemos torná-lo static porque não acede a nenhum membro de instância.
Para usar os tipos Clipboard e DataPackageView em C++/WinRT, teremos de incluir o ficheiro winrt/Windows.ApplicationModel.DataTransfer.hcabeçalho do namespace Windows C++/WinRT .
Em C#, a propriedade DataPackageView.AvailableFormats é uma IReadOnlyList, por isso podemos aceder à propriedade Count dessa área. Em C++/WinRT, a função de acesso DataPackageView::AvailableFormats devolve uma IVectorView, que tem uma função de acesso Size que podemos chamar.
Para portar o uso do tipo System.Text.StringBuilder em C#, vamos utilizar o tipo padrão em C++ std::wostringstream. Esse tipo é um fluxo de saída para strings largas (e para o usar teremos de incluir o sstream ficheiro de cabeçalho). Em vez de usar um método Append como se faz com um StringBuilder, usa o operador de inserção (<<) com um fluxo de saída como wostringstream. Para mais informações, consulte programação iostream e Formatação de cadeias C++/WinRT.
O código C# constrói um StringBuilder com a new palavra-chave. Em C#, os objetos são tipos de referência por defeito, declarados no heap com new. No C++ padrão moderno, os objetos são tipos por valor por predefinição, declarados na pilha (sem usar new). Assim, portámos StringBuilder output = new StringBuilder(); para C++/WinRT de forma tão simples como std::wostringstream output;.
A palavra-chave C# var pede ao compilador que infira um tipo. Migra-se de var para auto em C++/WinRT. Mas em C++/WinRT, há casos em que (para evitar cópias) se quer uma referência a um tipo inferido (ou deduzido), e se expressa uma referência de l-valor a um tipo deduzido com auto&. Também há casos em que se quer um tipo especial de referência que se vincule corretamente, quer seja inicializada com um valor l ou com um valor r. E expressas isso com auto&&. É essa a forma que vê ser usada no ciclo for no código adaptado abaixo. Para uma introdução a lvalues e rvalues, veja Categorias de valores e referências a elas.
Editar 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 da linha de código DataPackageView clipboardContent{ Clipboard::GetContent() }; utiliza uma característica do C++ padrão moderno chamada inicialização uniforme, com a sua característica utilização de colchetes curvados em vez de um = signo. Essa sintaxe deixa claro que está a ocorrer a inicialização, e não a atribuição. Se preferir a forma sintática que se assemelha a uma atribuição (mas que, na verdade, não o é), pode substituir a sintaxe acima pela construção equivalente DataPackageView clipboardContent = Clipboard::GetContent();. É uma boa ideia sentir-se confortável com ambas as formas de expressar a inicialização, porque provavelmente verá ambas usadas frequentemente no código que encontrar.
DisplayToast
DisplayToast é um método público e estático da classe C# MainPage , e vais encontrá-lo definido em SampleConfiguration.cs. Em C++/WinRT, vamos torná-lo um método público e estático do SampleState.
Já encontrámos a maioria dos detalhes e das técnicas que são relevantes para adaptar este método. Um aspeto novo a ter em conta é que se converte um literal verbatim de cadeia em C# (@) num literal de cadeia não processado padrão em C++ (LR).
Além disso, quando faz referência aos tipos ToastNotification e XmlDocument em C++/WinRT, pode qualificá-los pelo nome do namespace, ou pode editar SampleConfiguration.cpp e adicionar using namespace diretivas, como no exemplo seguinte.
using namespace Windows::UI::Notifications;
Tens a mesma escolha quando consultas o tipo XmlDocument e sempre que referencias qualquer outro tipo de Windows Runtime.
Para além dessas coisas, siga apenas as mesmas orientações que deu antes para completar os passos seguintes.
- Declare o método em
SampleConfiguration.h, e defina-o emSampleConfiguration.cpp. - Edite
pch.hpara incluir quaisquer ficheiros de cabeçalho do espaço de nomes Windows necessários em C++/WinRT. - Construa objetos C++/WinRT na pilha, não no heap.
- Substituir as chamadas a acessores de obtenção de propriedades pela sintaxe de chamada de funções (
()).
Uma causa muito comum de erros de compilação/ligação é esquecer-se de incluir os ficheiros de cabeçalho do espaço de nomes Windows do C++/WinRT de que necessita. Para mais informações sobre um possível erro, veja C3779: Porque é que o compilador me está a dar um erro "consume_Something: função que devolve 'auto' não pode ser usada antes de ser definida"?
Se quiseres acompanhar o tutorial e adaptar tu próprio o DisplayToast, podes comparar os teus resultados com o código da versão C++/WinRT no ficheiro ZIP do código-fonte do exemplo Clipboard sample que descarregaste.
EnableClipboardContentChangedNotifications
EnableClipboardContentChangedNotifications é um método público estático da classe MainPage em C#, e está 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 público e estático do SampleState.
Em C#, utiliza a sintaxe dos operadores += e -= para registar e anular o registo de delegados de processamento de eventos. Em C++/WinRT, tens várias opções sintáticas para registar/revogar um delegado, conforme descrito em Controlar eventos, usando delegados em C++/WinRT. Mas a forma geral consiste em fazer o registo e a anulação através de chamadas a um par de funções cujos nomes derivam do evento. Ao registar-se, passa o delegado para a função de registo e recebe em troca um token de revogação (um winrt::event_token). Para revogar, passa esse token para a função de revogação. Neste caso, o hander é estático e (como pode ver na lista de código seguinte) a sintaxe da chamada de função é simples.
Tokens semelhantes são efetivamente utilizados, internamente, em C#. Mas a linguagem torna esse detalhe implícito. C++/WinRT torna isso explícito.
O tipo de objeto aparece nas assinaturas do gestor de eventos C#. Na linguagem C#, object é um alias para o tipo System.Object .NET. O equivalente em C++/WinRT é winrt::Windows::Foundation::IInspectable. Portanto, verá IInspectable nos processadores de eventos do C++/WinRT.
Editar 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, 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){}
Por agora, deixem os próprios delegados responsáveis pelo evento (OnClipboardChanged e OnWindowActivated) como esboços. Já estão na nossa lista de membros para portar, por isso falaremos deles em subsecções futuras.
OnNavigatedTo
OnNavigatedTo é um método protegido da classe C# MainPage , e está definido em MainPage.xaml.cs. Aqui está, juntamente com a ListBox em XAML a que 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, porque é aqui que a nossa coleção de objetos de cenário é atribuída à interface. O código C# constrói um System.Collections.Generic.List de objetos Scenario e atribui-o à propriedade ItemsSource de um ListBox (que é um controlo de elementos). E, em C#, usamos interpolação de strings para construir o título de cada objeto Scenario (note-se a utilização do $ carácter especial).
Em C++/WinRT, vamos tornar o OnNavigatedTo um método público do MainPage. E vamos adicionar um elemento stub ListBox ao XAML para que uma build tenha sucesso. Depois da lista de códigos, vamos analisar alguns 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);
}
}
...
Mais uma vez, chamamos 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 tomámos de efetuar o boxing dos nossos objetos Scenario conforme necessário.
E, em vez do uso da interpolação de cadeias em C# aqui, usamos uma combinação da função to_hstring e do operador de concatenaçãowinrt::hstring.
isApplicationWindowActive
Em C#, isApplicationWindowActive é um campo privado bool simples pertencente à classe MainPage , e está definido em SampleConfiguration.cs. O padrão é false. Em C++/WinRT, vamos torná-lo num campo estático privado de SampleState (pelas razões que já descrevemos) nos ficheiros SampleConfiguration.h e SampleConfiguration.cpp, com o mesmo valor predefinido.
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
O mesmo padrão do isApplicationWindowActive (ver o título imediatamente antes deste).
Button_Click
Button_Click é um método privado (gestão de eventos) da classe MainPage de C#, e está definido em MainPage.xaml.cs. Aqui está, juntamente com o SplitView XAML que referencia, e o ToggleButton que o regista.
<!-- 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. Note que, na versão C++/WinRT, o processador de eventos é public (como pode ver, declara-o antes das declarações private:). Isto acontece porque um gestor de eventos registado em marcação XAML, como este, precisa de estar public em C++/WinRT para que a marcação XAML possa aceder a ele. Por outro lado, se registar um gestor de eventos em código imperativo (como fizemos anteriormente no MainPage::EnableClipboardContentChangedNotifications ), então o gestor de eventos não precisa de 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 pertencente à classe MainPage , e está definido em SampleConfiguration.cs.
private void DisplayChangedFormats()
{
string output = "Clipboard content has changed!" + Environment.NewLine;
output += BuildClipboardFormatsOutputString();
NotifyUser(output, NotifyType.StatusMessage);
}
Em C++/WinRT, vamos torná-lo um campo estático privado de SampleState (não acede a nenhum membro da instância), nos ficheiros SampleConfiguration.h e SampleConfiguration.cpp. O código C# deste método não utiliza System.Text.StringBuilder; mas faz formatação de strings suficiente para que, para a versão C++/WinRT, este seja outro bom local para usar std::wostringstream.
Em vez da propriedade estática System.Environment.NewLine, que é utilizada no código C#, vamos inserir o std::endl padrão do C++ (um carácter de mudança de 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);
}
Existe 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 portámos anteriormente). Esse método cria o seu próprio std::wostringstream. E transforma o seu fluxo num winrt::hstring e devolve isso. Chamamos à função hstring::c_str para transformar essa hstring devolvida de volta numa string ao estilo C, e depois inserimos essa função no nosso fluxo. Seria mais eficiente criar apenas um std::wostringstream e passar (uma referência a) ele, para que os métodos possam inserir cadeias diretamente nele.
É isso que fazemos na versão em C++/WinRT do código-fonte do exemplo Clipboard (no ficheiro ZIP que transferiu). Nesse código-fonte, existe um novo método estático privado chamado SampleState::AddClipboardFormatsOutputString, que recebe e opera numa referência a um fluxo de saída. E depois os métodos SampleState::D isplayChangedFormats 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
Footer_Click é um gestor de eventos assíncrono pertencente à classe MainPage de C#, e está definido em MainPage.xaml.cs. A lista de código abaixo é funcionalmente equivalente ao método do código-fonte que descarregou. Mas aqui desdobrei-o de uma linha para quatro, para ser mais fácil ver o que está a fazer e, consequentemente, como o devemos adaptar.
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, não faz nada depois do await, por isso não precisa do await (nem da async palavra-chave). Provavelmente usa-os para evitar a mensagem do IntelliSense no Visual Studio.
O método equivalente C++/WinRT também será assíncrono (porque chama Launcher.LaunchUriAsync). Mas não precisa de co_await, nem de devolver um objeto assíncrono. Para informações sobre co_await objetos assíncronos, veja Concorrência e operações assíncronas com C++/WinRT.
Agora vamos falar sobre o que o método está a fazer. Como este é um gestor de eventos para o evento Click de um HyperlinkButton, o objeto nomeado sender é na verdade um HyperlinkButton. Portanto, a conversão de tipos é segura (poderíamos alternativamente ter expresso esta conversão como sender as HyperlinkButton). De seguida, recuperamos o valor da propriedade Tag (se olhar para a marcação XAML no projeto C#, verá que esta está definida para uma string que representa uma URL web). Embora a propriedade FrameworkElement.Tag (HyperlinkButton é um FrameworkElement) seja do tipo object, em C# podemos stringificá-la com Object.ToString. A partir da cadeia resultante, construímos um objeto Uri . E finalmente (com a ajuda do Shell) abrimos um navegador e navegamos até à URL.
Aqui está o método portado para C++/WinRT (novamente, expandido para maior clareza), a seguir 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, fazemos o manipulador de eventos public. Usamos a função as no objeto sender para o converter para HyperlinkButton. Em C++/WinRT, a propriedade Tag é um IInspectable (o equivalente de Object). Mas não há Tostring no IInspectable. Em vez disso, temos de desempacotar o IInspectable para um valor escalar (uma cadeia de carateres, neste caso). Mais uma vez, para mais informações sobre boxe e unboxing, veja Valores de boxe e unboxing a IInnspectable.
As duas últimas linhas repetem padrões de portabilidade que já vimos antes, e ecoam praticamente a versão C#.
HandleClipboardChanged
Não há nada de novo em portar este método. Podes comparar as versões em C# e C++/WinRT no código ZIP do código fonte de exemplo do Clipboard que descarregaste.
OnClipboardChanged e OnWindowActivated
Até agora só temos stubs vazios para estes dois gestores de eventos. Mas migrá-los é simples e não levanta nenhuma questão nova.
ScenarioControl_SelectionChanged
Este é outro gestor privado de eventos pertencente à classe C# MainPage , e definido em MainPage.xaml.cs. Em C++/WinRT, torná-lo público e implementá-lo em MainPage.h e MainPage.cpp.
Para este método, vamos precisar de MainPage::navigating, que é um campo booleano privado, inicializado em false. E vais precisar de um Frame em MainPage.xaml, chamado ScenarioFrame. Mas, para além desses detalhes, a adaptação deste método não revela nenhuma técnica nova.
Se, em vez de portar manualmente, estiveres a copiar código da versão C++/WinRT no código ZIP do código fonte de exemplo do Clipboard que descarregaste, então vais ver lá um MainPage::NavigateTo a ser usado. Por agora, basta refatorar o conteúdo do NavigateTo para ScenarioControl_SelectionChanged.
Estado da atualização
Temos apenas um esboço até agora para a MainPage.UpdateStatus. A transposição da sua implementação, novamente, aborda em grande medida questões já conhecidas. Um ponto novo a notar é que, enquanto em C# podemos comparar uma string com String.Empty, em C++/WinRT chamamos, em vez disso, a função winrt::hstring::empty. Outra é que nullptr é o equivalente padrão em C++ do nullC# .
Podes fazer o resto do port com técnicas que já abordámos. Aqui está uma lista dos tipos de coisas que terá de fazer antes de a versão portada deste método ser compilada.
- Para
MainPage.xaml, adicione uma Borda denominada StatusBorder. - Para
MainPage.xaml, adicione um TextBlock chamado StatusBlock. - Para
MainPage.xaml, adicione um StackPanel chamado StatusPanel. - Para
pch.h, adicione#include "winrt/Microsoft.UI.Xaml.Media.h". - Para
pch.h, adicione#include "winrt/Microsoft.UI.Xaml.Automation.Peers.h". - Para
MainPage.cppacrescentarusing namespace winrt::Microsoft::UI::Xaml::Media;. - Para
MainPage.cppacrescentarusing namespace winrt::Microsoft::UI::Xaml::Automation::Peers;.
Copie o XAML e os estilos necessários para concluir a migração da MainPage
Para XAML, o caso ideal é que possas usar a mesma marcação XAML num projeto C# e num projeto C++/WinRT. E a amostra do Clipboard é um desses casos.
No ficheiro Styles.xaml, o exemplo Clipboard inclui um ResourceDictionary de estilos em XAML, aplicados aos botões, menus e outros elementos da interface do utilizador em toda a aplicação. A Styles.xaml página é fundida em App.xaml. E depois há o ponto de partida padrão MainPage.xaml para a interface, que já vimos brevemente. Agora podemos reutilizar esses três .xaml ficheiros, sem alterações, na versão C++/WinRT do projeto.
Tal como nos ficheiros de ativos, pode optar por referenciar os mesmos ficheiros XAML partilhados de várias versões da sua aplicação. Neste guia, só para simplificar, vamos copiar ficheiros para o projeto C++/WinRT e adicioná-los dessa forma.
Navega até à \Clipboard_sample\SharedContent\xaml pasta, seleciona e copia App.xaml e MainPage.xaml, e depois cola esses dois ficheiros na \Clipboard\Clipboard pasta do teu projeto C++/WinRT, escolhendo substituir os ficheiros quando solicitado.
No projeto C++/WinRT no Visual Studio, clica em Mostrar Todos os Ficheiros para ativar. Agora adicione uma nova pasta, imediatamente por baixo do nó do projeto, e nomee-lhe Styles. No Explorador de Ficheiros, navega até à \Clipboard_sample\SharedContent\xaml pasta, seleciona e copia Styles.xaml, e cola na \Clipboard\Clipboard\Styles pasta que acabaste de criar. De volta ao Explorador de Soluções no projeto C++/WinRT, clique com o botão direito na Styles pasta >Adicionar>item existente... e navegue até \Clipboard\Clipboard\Styles. No seletor de ficheiros, selecione Styles e clique em Adicionar.
Adicione uma nova pasta ao projeto C++/WinRT, imediatamente sob o nó do projeto, e nomeada Styles. Navega até à \Clipboard_sample\SharedContent\xaml pasta, seleciona e copia Styles.xaml, e cola-a na \Clipboard\Clipboard\Styles pasta do teu projeto C++/WinRT. Clique com o botão direito na Styles pasta (no Explorador de Soluções no projeto C++/WinRT), >adicione>item existente... e navegue até \Clipboard\Clipboard\Styles. No seletor de ficheiros, selecione Styles e clique em Adicionar.
Clique novamente em Mostrar Todos os Ficheiros para o desativar.
Concluímos agora a migração de MainPage e, se tiver seguido os passos, o seu projeto C++/WinRT deverá agora compilar e executar.
Consolide os seus .idl ficheiros
Para além do ponto de partida padrão MainPage.xaml para a interface, o exemplo do Clipboard tem cinco outras páginas XAML específicas de cenário, juntamente com os respetivos ficheiros code-behind. Vamos reutilizar a marcação XAML real de todas estas páginas, sem alterações, na versão C++/WinRT do projeto. E vamos ver como portar o code-behind nas próximas secções principais. Mas antes disso, vamos falar sobre o IDL.
Há valor em consolidar o IDL das suas classes de runtime num único ficheiro IDL. Para saber mais sobre esse valor, veja Fatoração de classes de runtime em ficheiros Midl (.idl). A seguir, vamos consolidar o conteúdo de CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idl e OtherScenarios.idl, movendo esse IDL para um único ficheiro denominado Project.idl (e, em seguida, eliminando os ficheiros originais).
Enquanto fazemos isso, vamos também remover a propriedade dummy gerada automaticamente (Int32 MyProperty;, e a 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 : 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 podes ver, isto é apenas uma cópia dos conteúdos dos ficheiros individuais .idl, tudo dentro de um namespace, e com MyProperty removido de cada classe de runtime.
No Explorador de Soluções do Visual Studio, selecione múltiplas vezes todos os ficheiros IDL originais (CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idl, e OtherScenarios.idl) e Editar>Removê-los (escolha Delete na diálogo).
Finalmente — e para completar a remoção de MyProperty —, nos ficheiros .h e .cpp de cada um desses mesmos cinco tipos de páginas XAML, elimine as declarações e definições das funções de acessor int32_t MyProperty() e de mutador void MyProperty(int32_t).
Por acaso, é sempre uma boa ideia que o nome dos seus ficheiros XAML corresponda ao nome da classe que representam. Por exemplo, se tiveres x:Class="MyNamespace.MyPage" num ficheiro de marcação XAML, então esse ficheiro deve ter o nome MyPage.xaml. Embora isto não seja um requisito técnico, não ter de andar a lidar com diferentes nomes para o mesmo artefacto tornará o seu projeto mais fácil de compreender e manter, e mais fácil de utilizar.
CopyFiles
No projeto C#, o tipo de página XAML CopyFiles é implementado nos ficheiros de código-fonte CopyFiles.xaml e CopyFiles.xaml.cs. Vamos analisar cada um dos membros de CopyFiles, por sua 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 (tal como no MainPage::current), o CopyFiles::rootPage é declarado como 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 (apenas chama InitializeComponent).
CopyButton_Click
O método C# CopyButton_Click é um gestor de eventos, e pela async palavra-chave na sua assinatura podemos perceber que o método faz trabalho assíncrono. Em C++/WinRT, implementamos um método assíncrono como corrotina. Para uma introdução à concorrência em C++/WinRT, juntamente com uma descrição do que é uma corrotina, veja Concorrência e operações assíncronas com C++/WinRT.
É comum querer agendar trabalho adicional para depois de uma corrotina terminar e, nesses casos, a corrotina retornaria algum tipo de objeto assíncrono que pode ser aguardado e que, opcionalmente, indica o progresso. Mas essas considerações normalmente não se aplicam a um gestor de eventos. Assim, quando tens um gestor de eventos que realiza operações assíncronas, podes implementá-lo como uma corrotina que devolve winrt::fire_and_forget. Para mais informações, consulte Fire and forget.
Embora a ideia de uma corrotina do tipo "disparar e esquecer" seja não se importar com quando é concluída, o trabalho continua (ou fica suspenso, à espera de ser retomado) em segundo plano. Pode-se ver pela implementação em C# que CopyButton_Click depende do ponteiro this (acede ao membro de dados da instância rootPage). Por isso, temos de garantir que o this ponteiro (um ponteiro para um objeto CopyFiles ) sobrevive à corrotina CopyButton_Click . Numa situação como esta aplicação de exemplo, onde o utilizador navega entre páginas da interface, não podemos controlar diretamente a vida útil dessas páginas. Se a página CopyFiles for destruída (ao navegar para outra página) enquanto CopyButton_Click ainda estiver em execução numa thread em segundo plano, não será seguro aceder a rootPage. Para corrigir a corrotina, é necessário obter uma referência forte ao this indicador e manter essa referência durante toda a corrotina. Para mais informações, veja Referências fortes e fracas em C++/WinRT.
Se olhar para a versão C++/WinRT do exemplo, em CopyFiles::CopyButton_Click, verá que isso é feito recorrendo a uma declaração simples na pilha.
fire_and_forget CopyFiles::CopyButton_Click(IInspectable const&, RoutedEventArgs const&)
{
auto lifetime{ get_strong() };
...
}
Vamos analisar 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. E nesse IVector, chamamos o método IVector<T>.ReplaceAll(T[]). O aspeto interessante é o valor que estamos a passar para esse método, quando é esperado um array. Aqui está a linha de código.
filePicker.FileTypeFilter().ReplaceAll({ L"*" });
O valor que estamos a passar ({ L"*" }) é uma lista padrão de inicializadores em C++. Contém um único objeto, neste caso, mas uma lista inicializadora pode conter qualquer número de objetos separados por vírgulas. As partes de C++/WinRT que permitem a conveniência de passar uma lista de inicializadores para um método como este são explicadas nas listas de inicializadores padrão.
Transpomos a palavra-chave C# await para co_await em C++/WinRT. Aqui está o exemplo do código.
auto storageItems{ co_await filePicker.PickMultipleFilesAsync() };
De seguida, considere esta linha de código C#.
dataPackage.SetStorageItems(storageItems);
C# consegue converter implicitamente o IReadOnlyList<StorageFile> representado por storageItems no IEnumerable<IStorageItem> esperado pelo DataPackage.SetStorageItems. Mas em C++/WinRT precisamos de 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>>());
Quando 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 gestor de eventos sob a forma de uma corrotina de disparar e esquecer. Vamos analisar 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 portado em C++/WinRT, verá a expressão catch (winrt::hresult_error const& ex). Para mais informações sobre winrt::hresult_error e como trabalhar com ele, veja Gestão de erros com C++/WinRT.
Um exemplo de testar se um objeto C# é null ou não é if (storageItems != null). Em C++/WinRT, podemos recorrer a um operador de conversão para bool, que faz internamente o teste a nullptr.
Aqui está uma versão ligeiramente simplificada de um fragmento de código da versão portada em C++/WinRT do exemplo.
std::wostringstream output;
output << std::wstring_view(ApplicationData::Current().LocalFolder().Path());
Construir um std::wstring_view a partir de um winrt::hstring dessa forma ilustra uma alternativa a invocar a função hstring::c_str (para transformar o winrt::hstring numa cadeia de caracteres no estilo C). Esta alternativa funciona graças ao operador de conversão do 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 a função as usada algumas 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), então usamos antes 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 pode selecionar todo o conteúdo do ficheiro CopyFiles.xaml da pasta shared da transferência original do código-fonte de exemplo e colá-lo no ficheiro CopyFiles.xaml no projeto C++/WinRT (substituindo o conteúdo existente desse ficheiro no projeto C++/WinRT).
Por fim, edite CopyFiles.h e .cpp e elimine a função simulada ClickHandler, já que acabámos de substituir a marcação XAML correspondente.
Agora terminámos de portar o CopyFiles e, se tens seguido os passos, o teu projeto C++/WinRT vai agora ser construído e executado, e o cenário CopyFiles estará funcional.
CopyImage
Para portar o tipo de página XAML CopyImage, segue o mesmo processo do que para CopyFiles. Ao portar CopyImage, irá deparar-se com a utilização da instrução using em C#, que garante que os objetos que implementam a interface IDisposable são corretamente libertados.
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 o seu único método Close . Aqui está o equivalente em C++/WinRT ao 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 benefício de linguagens que carecem de finalização determinística. C++/WinRT tem finalização determinística, por isso muitas vezes não precisamos de chamar IClosable::Close quando estamos a escrever C++/WinRT. Mas há momentos em que é bom dar o assunto por encerrado, e este é um desses momentos. Aqui, o identificador imageStream é um wrapper contado por referências em torno de um objeto subjacente do Windows Runtime (neste caso, um objeto que implementa IRandomAccessStreamWithContentType). Embora possamos determinar que o finalizador do imageStream (o seu destruidor) irá correr no final do âmbito anexo (os parênteses curvados), não podemos garantir que esse finalizador chamará Fechar. Isto porque passámos o imageStream para outras APIs, e elas podem continuar a contribuir para a contagem de referências do objeto Windows Runtime subjacente. Portanto, este é um caso em que é boa ideia chamar Close explicitamente. Para mais informações, veja Preciso de chamar IClosable::Close nas classes de runtime que utilizo?.
Em seguida, considere a expressão C# (uint)(imageDecoder.OrientedPixelWidth * 0.5), que encontrará no processador de eventos OnDeferredImageRequestedHandler. Essa expressão multiplica a uint por a double, resultando num double. Depois, faz a conversão disso para um uint. Em C++/WinRT, poderíamos usar um cast semelhante ao estilo C ((uint32_t)(imageDecoder.OrientedPixelWidth() * 0.5)), mas é preferível deixar claro exatamente que tipo de cast pretendemos, e neste caso faríamos isso com static_cast<uint32_t>(imageDecoder.OrientedPixelWidth() * 0.5).
A versão C# do 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 implementámos uma catch cláusula para podermos reportar se a renderização atrasada foi bem-sucedida ou não.
Portar o restante desta página XAML não traz nada de novo para discutir. Lembra-te de eliminar a função fictícia do ClickHandler . E, tal como no CopyFiles, o último passo na porta é selecionar todo o conteúdo de CopyImage.xaml, e colá-lo no mesmo ficheiro no projeto C++/WinRT.
CopyText
Podes portar CopyText.xaml e CopyText.xaml.cs usar técnicas que já abordámos.
Histórico e roaming
Existem alguns pontos de interesse que surgem ao portar o tipo de página HistoryAndRoaming em XAML.
Primeiro, observe o código-fonte em C# e siga o fluxo de controlo a partir de OnNavigatedTo, passando pelo processador de eventos OnHistoryEnabledChanged e, finalmente, até à função assíncrona CheckHistoryAndRoaming (que não é aguardada, pelo que é essencialmente executar e esquecer). Como CheckHistoryAndRoaming é assíncrono, teremos de ter cuidado, em C++/WinRT, com o tempo de vida do ponteiro this. Pode ver o resultado se olhar para a implementação no ficheiro de código-fonte HistoryAndRoaming.cpp. Primeiro, quando associamos os delegados aos eventos Clipboard::HistoryEnabledChanged e Clipboard::RoamingEnabledChanged, mantemos apenas uma referência fraca ao objeto de página HistoryAndRoaming. Fazemos isso criando o delegado com base no valor devolvido por winrt::get_weak, em vez de uma dependência no apontador this. O que significa que o próprio delegado, que acaba por chamar código assíncrono, não mantém a página HistoryAndRoaming em memória, caso naveguemos para fora dela.
E, em segundo lugar, quando finalmente chegamos à nossa corrotina CheckHistoryAndRoaming do tipo fire-and-forget, a primeira coisa que fazemos é obter uma referência forte a this para garantir que a página HistoryAndRoaming permanece ativa pelo menos até que a corrotina seja concluída. Para mais informações sobre ambos os aspetos descritos, veja Referências fortes e fracas em C++/WinRT.
Encontramos outro ponto de interesse ao migrar CheckHistoryAndRoaming. Contém código para atualizar a interface; por isso precisamos de ter a certeza de que estamos a fazer isso no tópico principal da interface. O thread que inicialmente liga a um gestor de eventos é o thread principal da interface. Mas tipicamente, um método assíncrono pode ser executado e/ou retomado em qualquer thread arbitrário. Em C#, a solução é despachar o trabalho para o thread da interface. Em C++/WinRT, podemos usar a função winrt::resume_foreground juntamente com a thisDispatcherQueue do ponteiro para suspender a corrotina e retomar imediatamente a thread principal da interface.
A expressão relevante é co_await winrt::resume_foreground(DispatcherQueue());. A versão mais curta é conseguida graças a um operador de conversão fornecido por C++/WinRT.
Portar o restante desta página XAML não traz nada de novo para discutir. Lembra-te de apagar a função fictícia ClickHandler e de copiar a marcação XAML.
OutrosCenários
Podes portar OtherScenarios.xaml e OtherScenarios.xaml.cs usar técnicas que já abordámos.
Conclusion
Espero que este guia te tenha dado informações e técnicas suficientes de portabilidade para que agora possas avançar e portar as tuas próprias aplicações C# para C++/WinRT. Para relembrar, pode continuar a consultar as versões antes (C#) e depois (C++/WinRT) do código-fonte no exemplo do Clipboard, e compará-las lado a lado para ver a correspondência.
Tópicos relacionados
Windows developer