Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Toto téma představuje případovou studii přenosu jedné z ukázek aplikací pro univerzální platformu Windows (UPW) z C# do C++/WinRT. Během procházení návodu můžete získat praxi a zkušenosti s přenosem tím, že si sami vyzkoušíte přenos ukázky.
Úplný katalog technických podrobností, které se týkají přenosu do C++/WinRT z C#, najdete v doprovodném tématu Přesunout do C++/WinRT z C#.
Stručný přehled o souborech zdrojového kódu C# a C++
V projektu C# jsou soubory zdrojového kódu primárně .cs soubory. Když přejdete na C++, všimnete si, že existuje více druhů souborů zdrojového kódu, na které se můžete zvyknout. Důvodem je rozdíl mezi kompilátory, způsob opětovného použití zdrojového kódu jazyka C++ a pojmy deklarování a definování typu a jeho funkcí (jeho metod).
Deklarace funkce popisuje pouze podpis funkce (návratový typ, jeho název a typy a názvy parametrů). Definice funkce zahrnuje tělo funkce (její implementaci).
Je to trochu jiné, pokud jde o typy. Definujete typ tak, že zadáte jeho název a (minimálně) deklarujete všech jejích členských funkcí (a dalších členů). To je správné, můžete definovat typ, i když nedefinujete jeho členské funkce.
- Běžné soubory zdrojového kódu C++ jsou
.h(tečka) a.cppsoubory. Soubor.hje hlavička a definuje jeden nebo více typů. I když můžete definovat členské funkce v hlavičkovém souboru, soubor.cppje k tomu obvykle určen. Takže pro hypotetický typ C++ MyClassbyste definovali MyClass vMyClass.ha definovali byste jeho členské funkce vMyClass.cpp. Aby ostatní vývojáři mohli vaše třídy znovu používat, sdíleli byste jenom soubory.ha kód objektu. Vaše.cppsoubory byste měli uchovávat v tajnosti, protože implementace představuje vaše duševní vlastnictví. - Předkompilovaná hlavička (
pch.h). Obvykle existuje sada souborů hlaviček, které zahrnete do aplikace, a tyto soubory se často nemění. Takže namísto zpracování obsahu této sady hlaviček při každém kompilaci můžete tyto hlavičky agregovat do jednoho souboru, zkompilovat ho jednou a pak použít výstup tohoto kroku předkompilace pokaždé, když sestavíte. Provedete to prostřednictvím předkompilovaného hlavičkového souboru (obvykle pojmenovanéhopch.h). -
.idlsoubory. Tyto soubory obsahují IDL (Interface Definition Language). IDL si můžete představit jako soubory hlaviček pro typy prostředí Windows Runtime. Budeme více mluvit o IDL v části IDL pro MainPage typ.
Stáhněte a otestujte ukázku schránky
Navštivte ukázkovou webovou stránku schránky a klikněte na Stáhnout ZIP. Rozbalte stažený soubor a podívejte se na strukturu složek.
- Verze C# ukázkového zdrojového kódu je obsažena ve složce s názvem
cs. - Verze C++/WinRT ukázkového zdrojového kódu je obsažena ve složce s názvem
cppwinrt. - Další soubory používané verzí jazyka C# a verzí C++/WinRT najdete ve složkách
sharedaSharedContent.
Názorný postup v tomto tématu ukazuje, jak můžete znovu vytvořit variantu ukázky schránky C++/WinRT převedením z původního zdrojového kódu v jazyce C#. Díky tomu můžete do C++/WinRT přenést vlastní projekty C#.
Pokud chcete zjistit, co ukázka dělá, otevřete řešení jazyka C# (\Clipboard_sample\cs\Clipboard.sln), změňte konfiguraci podle potřeby (třeba na x64), sestavte a spusťte. Vlastní uživatelské rozhraní (UI) ukázky vás provede různými funkcemi krok za krokem.
Návod
Kořenová složka ukázky, kterou jste stáhli, se může jmenovat Clipboard místo Clipboard_sample. Na tuto složku ale budeme dál odkazovat jako na Clipboard_sample, abychom ji odlišili od verze C++/WinRT, kterou budete vytvářet v pozdějším kroku.
Vytvořte prázdnou aplikaci (C++/WinRT) s názvem Schránka
Poznámka:
Informace o instalaci a používání rozšíření C++/WinRT Visual Studio (VSIX) a balíčku NuGet (které společně poskytují podporu šablony projektu a sestavení), najdete v tématu podpora sady Visual Studio pro C++/WinRT.
Zahajte proces přenosu vytvořením nového projektu C++/WinRT v sadě Microsoft Visual Studio. Vytvořte nový projekt pomocí šablony projektu Blank App (C++/WinRT). Nastavte jeho název na Clipboarda (aby struktura složek odpovídala postupu), ujistěte se, že Umístit řešení a projekt do stejné složky není zaškrtnuto.
Ujistěte se, že tento nový, prázdný projekt lze sestavit a spustit, abyste získali základní přehled.
Package.appxmanifest a soubory prostředků
Pokud verze C# a C++/WinRT ukázky nemusí být nainstalované vedle sebe na stejném počítači, můžou být zdrojové soubory manifestu balíčku aplikace obou projektů (Package.appxmanifest) shodné. V takovém případě stačí zkopírovat Package.appxmanifest z projektu C# do projektu C++/WinRT a máte hotovo.
Aby mohly obě verze ukázky spoluexistovat, potřebují různé identifikátory. V takovém případě v projektu C++/WinRT otevřete soubor Package.appxmanifest v editoru XML a poznamenejte si tyto tři hodnoty.
- Uvnitř elementu /Package/Identity si poznamenejte hodnotu atributu Name. Toto je název balíčku . U nově vytvořeného projektu mu projekt poskytne počáteční hodnotu jedinečného identifikátoru GUID.
- Uvnitř elementu /Package/Applications/Application si poznamenejte hodnotu atributu ID. Toto je ID aplikace .
- Uvnitř elementu /Package/mp:PhoneIdentity si poznamenejte hodnotu atributu PhoneProductId. U nově vytvořeného projektu bude opět nastaven stejný identifikátor GUID jako název balíčku.
Potom zkopírujte Package.appxmanifest z projektu C# do projektu C++/WinRT. Nakonec můžete obnovit tři hodnoty, které jste si poznamenali. Nebo můžete zkopírované hodnoty upravit tak, aby byly jedinečné a/nebo vhodné pro aplikaci a pro vaši organizaci (jako obvykle byste to udělali pro nový projekt). Například v tomto případě místo obnovení hodnoty názvu balíčku můžeme změnit zkopírovanou hodnotu z Microsoft.SDKSamples.Clipboard.CS na Microsoft.SDKSamples.Clipboard.CppWinRT. Id aplikace můžeme nechat nastavené na App. Pokud se název balíčku nebo ID aplikace liší, budou mít tyto dvě aplikace různá ID modelu uživatele (AUMID). A to je to, co je potřeba, aby se dvě aplikace nainstalovaly vedle sebe na stejném počítači.
Pro účely tohoto návodu je vhodné provést několik dalších změn v Package.appxmanifest. Existují tři výskyty řetězce Schránka C# Příklad. Změňte ho na ukázku C++/WinRT.
V projektu C++/WinRT se teď soubor Package.appxmanifest a projekt nesynchronizují s ohledem na soubory prostředků, na které odkazují. Pokud chcete tento postup napravit, nejprve odeberte prostředky z projektu C++/WinRT tak, že vyberete všechny soubory ve složce Assets (v Průzkumníku řešení v sadě Visual Studio) a odeberete je (v dialogovém okně zvolte Odstranit).
Projekt C# odkazuje na soubory prostředků ze sdílené složky. Stejný postup můžete provést v projektu C++/WinRT nebo můžete zkopírovat soubory, jak to uděláme v tomto názorném postupu.
Přejděte do složky \Clipboard_sample\SharedContent\media. Vyberte sedm souborů, které projekt C# obsahuje (microsoft-sdk.png, smalltile-sdk.png, splash-sdk.png, squaretile-sdk.png, storelogo-sdk.png, tile-sdk.pnga windows-sdk.png), zkopírujte je a vložte je do složky \Clipboard\Clipboard\Assets v novém projektu.
Klikněte pravým tlačítkem na složku Assets (v Průzkumníku řešení v projektu C++/WinRT) >Přidat>Existující položku... a přejděte na \Clipboard\Clipboard\Assets. V nástroji pro výběr souborů vyberte sedm souborů a klikněte na Přidat.
Package.appxmanifest je nyní zpět v synchronizaci se soubory assetů projektu.
MainPage, včetně funkce, která konfiguruje vzorek
Ukázka schránky – stejně jako všechny ukázky aplikací pro Univerzální platformu Windows (UPW)
Pokud se ve verzi C# ukázky podíváte do souboru zdrojového kódu SampleConfiguration.cs, uvidíte dvě třídy. Většina logiky konfigurace je ve třídě MainPage, což je částečná třída (tvoří úplnou třídu v kombinaci se značením v MainPage.xaml a imperativním kódem v MainPage.xaml.cs). Druhá třída v tomto souboru zdrojového kódu je Scenario, se svými vlastnostmi Title a ClassType.
V několika dalších dílčích oddílech se podíváme na to, jak portovat MainPage a scénář.
IDL pro typ MainPage
Začněme touto částí tím, že stručně povíme o jazyce IDL (Interface Definition Language) a o tom, jak nám pomůže při programování pomocí jazyka C++/WinRT. IDL je druh zdrojového kódu, který popisuje volatelnou plochu typu prostředí Windows Runtime. Volatelný (nebo veřejný) povrch typu je promítnut na veřejnost, aby se typ mohl využívat. Tato projektovaná část typu kontrastuje se skutečnou interní implementací typu, která se samozřejmě nedá volat a není veřejná. Je to jenom promítaná část, kterou definujeme v IDL.
Po vytvoření zdrojového kódu IDL (v souboru .idl) pak můžete IDL zkompilovat do souborů metadat s možností čtení počítače (označované také jako metadata Systému Windows). Tyto soubory metadat mají příponu .winmd, a zde jsou některé z jejich použití.
-
.winmdmůže popisovat typy prostředí Windows Runtime v komponentě. Když odkazujete na komponentu prostředí Windows Runtime (WRC) z projektu aplikace, přečte projekt aplikace metadata Windows patřící do WRC (tato metadata mohou být v samostatném souboru nebo mohou být zabalena do stejného souboru jako samotný WRC), abyste mohli využívat typy WRC z aplikace. -
.winmdmůže popsat typy prostředí Windows Runtime v jedné části aplikace, aby mohly být konzumovány jinou částí téže aplikace. Například typ prostředí Windows Runtime, který využívá stránka XAML ve stejné aplikaci. - Aby bylo snazší využívat typy prostředí Windows Runtime (integrované nebo třetí strany), používá systém sestavení C++/WinRT soubory
.winmdk vygenerování typů obálky, které představují předpokládané části těchto typů prostředí Windows Runtime. - Aby bylo pro vás jednodušší implementovat vlastní typy prostředí Windows Runtime, systém sestavení C++/WinRT změní váš IDL na soubor
.winmda použije ho k vygenerování obálky pro vaši projekci a zástupné procedury, na kterých se má vaše implementace založit (o těchto zástupných procedurách budeme hovořit dále v tomto tématu).
Konkrétní verze IDL, kterou používáme s C++/WinRT, je Microsoft Interface Definition Language 3.0. Ve zbývající části tohoto tématu prozkoumáme jazyk C# MainPage podrobně. Rozhodneme se, které části by měly být v projekci C++/WinRT MainPage typu (tj. ve volatelném, tedy veřejném povrchu) a které můžou být jen součástí jeho implementace. Tento rozdíl je důležitý, protože když se pustíme do vytváření našeho IDL (který uděláme v oddílu po tomto), definujeme tam jenom volatelné části.
Zdrojové soubory kódu jazyka C#, které společně implementují typ
Ve verzi C++/WinRT rozdělíme náš typ MainPage na soubory zdrojového kódu podobným způsobem. Logiku vezmeme ve MainPage.xaml.cs a přeložíme ji převážně na MainPage.h a MainPage.cpp. A pro logiku v SampleConfiguration.csto přeložíme na SampleConfiguration.h a SampleConfiguration.cpp.
Třídy v aplikaci pro univerzální platformu Windows (UPW) jazyka C# jsou samozřejmě typy prostředí Windows Runtime. Když ale vytvoříte typ v aplikaci C++/WinRT, můžete zvolit, jestli je tento typ typem prostředí Windows Runtime, nebo běžnou třídou/strukturou/výčtem jazyka C++.
Každá stránka XAML v našem projektu musí být typem prostředí Windows Runtime, takže MainPage musí být typem prostředí Windows Runtime. V projektu C++/WinRT je MainPage již typ prostředí Windows Runtime, takže tento aspekt nemusíme měnit. Konkrétně se jedná o třídu runtime .
- Další podrobnosti o tom, zda byste měli vytvořit runtime třídu pro daný typ, najdete v tématu Tvorba API pomocí C++/WinRT.
- V jazyce C++/WinRT existují interní implementace třídy modulu runtime a předpokládané (veřejné) části, které jsou ve formě dvou různých tříd. Toto jsou typ implementace a projektovaný typ . Další informace o nich najdete v tématu uvedeném v předchozím bodu a také v Využití rozhraní API pomocí C++/WinRT.
- Další informace o připojení mezi třídami modulu runtime a IDL (soubory
.idl) si můžete přečíst a sledovat v tématu ovládacích prvků XAML, kde se probírá připojení k vlastnosti C++/WinRT. Toto téma vás provede procesem vytváření nové třídy modulu runtime. Prvním krokem je přidání nové položky Midl File (.idl) do projektu.
Pro MainPage máme ve skutečnosti potřebný MainPage.idl soubor již v projektu C++/WinRT. Šablona projektu to pro nás vytvořila. Později v tomto názorném postupu ale do projektu přidáme další .idl soubory.
Za chvíli uvidíme seznam přesně toho, co potřebujeme přidat do existujícího souboru MainPage.idl. Předtím se musíme zamyslet nad tím, co do IDL patří a co ne.
Abychom zjistili, které členy MainPage musíme deklarovat v MainPage.idl (aby se staly součástí třídy runtime MainPage), a které mohou být jednoduše členy typu implementace MainPage, pojďme vytvořit seznam členů třídy MainPage. Tyto členy najdeme tak, že se podíváme na MainPage.xaml.cs a v SampleConfiguration.cs.
Najdeme celkem dvanáct protected a private polí a metod. A najdeme následující public členy.
- Výchozí konstruktor
MainPage(). - Statická pole Aktuální a FEATURE_NAME.
- Vlastnosti IsClipboardContentChangedEnabled a scénáře .
- Metody BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotificationsa NotifyUser.
Jsou to ti členové public, kteří jsou kandidáti na prohlášení v MainPage.idl. Pojďme prozkoumat každý z nich a uvidíme, zda potřebují být součástí třídy modulu runtime MainPage, nebo zda potřebují být jen součástí její implementace.
- Výchozí konstruktor
MainPage(). Pro stránku XAML je normální deklarovat výchozí konstruktor ve svém IDL. Tímto způsobem může XAML framework pro uživatelské rozhraní aktivovat typ. - Statické pole Current se používá na stránkách XAML jednotlivých scénářů pro přístup k instanci aplikace MainPage. Vzhledem k tomu, že Current se nepoužívá k spolupráci s architekturou XAML (ani se nepoužívá napříč kompilačními jednotkami), můžeme si ji rezervovat pouze jako člena typu implementace. S vlastními projekty v takových případech se můžete rozhodnout tak učinit. Vzhledem k tomu, že pole je instancí projektovaného typu, je logické ho deklarovat v IDL. To je to, co tady uděláme (a děláme to také mírně čistějším kódem).
- Je to podobný případ pro statické pole FEATURE_NAME, ke kterému se přistupuje v typu MainPage. Opětovné deklarování v IDL dělá náš kód o něco čistší.
- Vlastnost IsClipboardContentChangedEnabled se používá pouze ve třídě OtherScenarios. Během portu tedy věci trochu zjednodušíme a uděláme z něj soukromé pole třídy OtherScenarios runtime. Takže ten nepůjde do IDL.
- Vlastnost Scenarios je kolekce objektů typu Scenario (typ, který jsme zmínili dříve). Budeme mluvit o Scénáři v dalším pododdílu, takže ponecháme vlastnost Scénáře až do té doby.
- Metody BuildClipboardFormatsOutputString, DisplayToasta EnableClipboardContentChangedNotifications jsou pomocné funkce, které se více týkají obecného stavu ukázky než hlavní stránky. Během portu tedy tyto tři metody refaktorujeme na nový typ nástroje s názvem SampleState (což nemusí být typ prostředí Windows Runtime). Z tohoto důvodu tyto tři metody nebudou v IDL.
- Metoda NotifyUser je volána ze stránek XAML jednotlivých scénářů na instanci MainPage, která je vrácena ze statického pole Current. Vzhledem k tomu, že Aktuální je instance projekovaného typu, musíme deklarovat NotifyUser v IDL. NotifyUser přebírá parametr typu NotifyType. Budeme o tom mluvit v další pododdílu.
Každý člen, který chcete provázat s daty, musí být také deklarován v IDL (bez ohledu na to, jestli používáte {x:Bind} nebo {Binding}). Další informace najdete v tématu datové vazby.
Pracujeme na tom: vyvíjíme seznam členů, které se mají přidat, a které se do MainPage.idl souboru nepřidají. Stále ale musíme probrat scénář vlastnost a typ NotifyType. Tak to uděláme příště.
IDL pro typy Scenario a Notify Type
Třída Scenario je definována v SampleConfiguration.cs. Rozhodli jsme se, jak přenést danou třídu do C++/WinRT. Ve výchozím nastavení bychom pravděpodobně zvolili obyčejný C++ struct. Pokud se ale scénář používá mezi binárními soubory nebo spolupracuje s architekturou XAML, musí být deklarován v IDL jako typ prostředí Windows Runtime.
Při studiu zdrojového kódu jazyka C# zjistíme, že v tomto kontextu se používá Scénář .
<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;
Kolekce objektů Scenario je právě přiřazována k vlastnosti ItemsSource ovládacího prvku ListBox (který je ovládacím prvkem pro položky). Vzhledem k tomu, že scénář musí spolupracovat s XAML, musí to být typ prostředí Windows Runtime. Proto je potřeba ho definovat v IDL. Definování scénáře typu v IDL způsobí, že systém sestavení C++/WinRT za vás vygeneruje v zákulisním hlavičkovém souboru definici zdrojového kódu scénáře (název a umístění, které nejsou pro tento návod důležité).
A vzpomeňte si, že MainPage.Scenarios je kolekce objektů Scenario, které musí být v IDL, jak jsme právě uvedli. Z tohoto důvodu musí být také samotné MainPage.Scenarios deklarováno v IDL.
NotifyType je enum deklarovaný v MainPage.xaml.csjazyka C#. Protože předáme NotifyType metodě patřící do třídy modulu runtime MainPage, NotifyType musí být také typ prostředí Windows Runtime; a musí být definován v MainPage.idl.
Teď přidáme do souboru MainPage.idl nové typy a nového člena Mainpage, kterého jsme se rozhodli deklarovat v IDL. Zároveň odstraníme z IDL zástupné členy Mainpage, které nám poskytla šablona projektu Visual Studio.
Proto v projektu C++/WinRT otevřete MainPage.idla upravte ho tak, aby vypadal jako následující výpis. Všimněte si, že jednou z úprav je změna názvu prostoru názvů z Clipboard na SDKTemplate. Pokud chcete, stačí nahradit celý obsah MainPage.idl následujícím kódem. Další změna, kterou je třeba si všimnout, je, že měníme název Scenario::ClassType na Scenario::ClassName.
// MainPage.idl
namespace SDKTemplate
{
struct Scenario
{
String Title;
Windows.UI.Xaml.Interop.TypeName ClassName;
};
enum NotifyType
{
StatusMessage,
ErrorMessage
};
[default_interface]
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
MainPage();
static MainPage Current{ get; };
static String FEATURE_NAME{ get; };
static Windows.Foundation.Collections.IVector<Scenario> scenarios{ get; };
void NotifyUser(String strMessage, NotifyType type);
};
}
Poznámka:
Další informace o obsahu souboru .idl v rámci projektu C++/WinRT naleznete v tématu jazyka Microsoft Interface Definition Language 3.0.
Při práci s vlastním portováním možná nebudete chtít ani potřebovat změnit jméno oboru názvů, jako jsme to udělali výše. Děláme to jen proto, že výchozí obor názvů projektu C#, který portujeme, je SDKTemplate, zatímco název projektu a sestavení je Clipboard.
Když ale budeme pokračovat v tomto návodu, změníme každý výskyt ve zdrojovém kódu, kde je použit název oboru názvů schránka, na SDKTemplate. Ve vlastnostech projektu C++/WinRT je také místo, kde se objevuje název oboru názvů Clipboard, takže využijeme této příležitosti ke změně.
V sadě Visual Studio pro projekt C++/WinRT nastavte vlastnost projektu Společné vlastnosti>C++/WinRT>Kořenový obor názvů na hodnotu SDKTemplate.
Uložte IDL a znovu vygenerujte zástupní soubory.
Téma ovládací prvky XAML; svázání s vlastností C++/WinRT zavádí pojem zástupných souborůa nabízí jejich praktickou ukázku. Také jsme dříve v tomto tématu zmínili stuby, když jsme uvedli, že systém sestavení C++/WinRT převádí obsah vašich .idl souborů na metadata Windows a z těchto metadat pak nástroj s názvem cppwinrt.exe generuje stuby, na kterých můžete založit vaši implementaci.
Pokaždé, když přidáte, odeberete nebo změníte něco v IDL a sestavíte, aktualizuje systém sestavení implementace zástupné procedury v těchto souborech. Proto při každé změně IDL a sestavení doporučujeme zobrazit tyto zástupné soubory, zkopírovat všechny změněné podpisy a vložit je do projektu. Za chvíli dáme konkrétnější informace a příklady toho, jak to udělat. Výhodou toho ale je, že vám poskytnete způsob, jak bez chyb zjistit, co má být tvar vašeho typu implementace a jaký by měl být podpis jeho metod.
V tomto okamžiku v názorném postupu jsme dokončili úpravy souboru MainPage.idl, takže byste ho teď měli uložit. Projekt se v tuto chvíli nedokončí, ale provést sestavení nyní je užitečné, protože znovu vygeneruje zástupné soubory pro MainPage. Proto teď sestavte projekt a ignorujte všechny chyby sestavení.
Pro projekt C++/WinRT se soubory stubů generují ve složce \Clipboard\Clipboard\Generated Files\sources. Najdete je tam po ukončení částečného sestavení (jak se očekává, sestavení se zcela nezdaří. Ale krok, který nás zajímá – generování stubů –bude úspěšný). Soubory, které nás zajímají, jsou MainPage.h a MainPage.cpp.
V těchto dvou souborech stubů uvidíte nové implementace členů MainPage, které jsme přidali do IDL (Aktuální a FEATURE_NAME, například). Budete chtít zkopírovat tyto zástupné implementace do souborů MainPage.h a MainPage.cpp, které už jsou v projektu. Současně, stejně jako jsme to udělali s IDL, odebereme z těchto existujících souborů zástupné členy Mainpage, které nám poskytla šablona projektu Visual Studio (fiktivní vlastnost MyPropertya obslužná rutina události ClickHandler).
Ve skutečnosti je jediným členem aktuální verze MainPage, kterou chceme zachovat, je konstruktor.
Po zkopírování nových členů ze zástupných souborů, odstranění členů, které nechceme, a aktualizaci oboru názvů by soubory MainPage.h a MainPage.cpp ve vašem projektu měly vypadat jako výpisy kódu níže. Všimněte si, že existují dva typy
// 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();
}
}
Pro řetězce používá jazyk C# System.String. Příklad najdete v metodě MainPage.NotifyUser. V našem IDL deklarujeme řetězec s Stringa když nástroj cppwinrt.exe vygeneruje kód C++/WinRT, použije winrt::hstring typ. Kdykoli v kódu jazyka C# narazíme na řetězec, převedeme ho na winrt::hstring. Další informace najdete v tématu zpracování řetězců v jazyce C++/WinRT.
Viz vysvětlení parametrů const& v signaturách metody v sekci předávání parametrů.
Aktualizovat všechny zbývající deklarace a odkazy na obor názvů a poté sestavit.
Před vytvořením projektu C++/WinRT vyhledejte všechny deklarace a odkazy na Clipboard oboru názvů a změňte je na SDKTemplate.
-
MainPage.xamlaApp.xaml. Obor názvů se zobrazí v hodnotách atributůx:Classaxmlns:local. -
App.idl. -
App.h. -
App.cpp. Existují dvě direktivyusing namespace(vyhledejte podřetěžkusing namespace Clipboard) a dvě kvalifikace typu MainPage (vyhledejteClipboard::MainPage). Ty se potřebují změnit.
Vzhledem k tomu, že jsme odebrali obslužnou rutinu události z MainPage, přejděte také do MainPage.xaml a odstraňte prvek Button ze značkování.
Uložte všechny soubory. Vyčistěte řešení (Sestavení>Clean Solution ) a pak ho sestavte. Pokud byly všechny dosud provedené změny následovány přesně tak, jak jsou napsány, očekává se, že sestavení bude úspěšné.
Implementovat členy MainPage, které jsme deklarovali v IDL
Konstruktor, Currenta FEATURE_NAME
Tady je relevantní kód (z projektu C#), který potřebujeme portovat.
<!-- 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";
...
}
...
Brzy budeme znovu používat MainPage.xaml v plném rozsahu (zkopírováním). Prozatím (níže) dočasně přidáme prvek TextBlock s odpovídajícím názvem do MainPage.xaml projektu C++/WinRT.
Pokud bychom portovali vlastnost jen pro čtení v jazyce C#, udělali bychom to samé. V případě zapisovatelné vlastnosti v C# je způsob vyjádření setteru v C++/WinRT jako funkce void, která přebírá hodnotu vlastnosti jako parametr (mutator). V obou případech, pokud je pole nebo vlastnost v jazyce C# statické, pak je také statickým přístupovým a/nebo mutačním objektem v C++/WinRT.
Aktuální je statické pole (nikoliv konstantní) třídy MainPage. Znovu ho nastavíme (výraz C++/WinRT) jako vlastnost jen pro čtení a znovu ji nastavíme jako statickou. Kde FEATURE_NAME je konstantní, Current není. V C++/WinRT tedy budeme potřebovat záložní pole a náš přístupový objekt ho vrátí. V projektu C++/WinRT tedy deklarujeme v MainPage.h privátní statické pole s názvem aktuální, definujeme nebo inicializujeme aktuální v MainPage.cpp (protože má dobu trvání statického úložiště) a budeme k němu přistupovat prostřednictvím veřejné funkce statického přístupového objektu s názvem Current.
Samotný konstruktor provádí několik přiřazení, která jsou jednoduchá pro port.
V projektu C++/WinRT přidejte novou položku Visual C++>Code>C++ File (.cpp) položky s názvem SampleConfiguration.cpp.
Upravte MainPage.xaml, MainPage.h, MainPage.cppa SampleConfiguration.cpp tak, aby odpovídaly následujícím výpisům.
<!-- 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";
}
Nezapomeňte také odstranit existující těla funkcí z MainPage.cpp pro MainPage::Current() a MainPage::FEATURE_NAME(), protože teď definujeme tyto metody jinde.
Jak vidíte, MainPage::current je deklarován jako typ SDKTemplate::MainPage, což je projektovaný typ. Nejedná se o typ SDKTemplate::implementation::MainPage, což je typ implementace. Projektovaný typ je ten, který je navržený tak, aby byl spotřebován buď v rámci projektu pro spolupráci XAML, nebo mezi binárními soubory. Typ implementace je to, co používáte k implementaci funkcí, které jste zpřístupnili na vašem projektovaném typu. Protože se deklarace MainPage::current (v MainPage.h) nachází ve jmenném prostoru implementace (winrt::SDKTemplate::implementation), nepřiměřeně kvalifikovaný MainPage by odkazoval na typ implementace. Proto máme nárok na SDKTemplate::, abychom měli jistotu, že chceme MainPage::current být instancí projektu typu winrt::SDKTemplate::MainPage.
V konstruktoru existují některé body související s MainPage::current = *this;, které si zaslouží vysvětlení.
- Pokud použijete ukazatel
thisuvnitř člena implementačního typu, ukazatelthisje samozřejmě ukazatelem na implementační typ. - Pro převod ukazatele
thisna odpovídající projektovaný typ ho dereferencujte. Za předpokladu, že jste vygenerovali váš typ implementace z IDL (jak jsme zde uvedli), typ implementace má operátor převodu, který se převede na jeho projektovaný typ. Proto tady zadání funguje.
Další informace o těchto podrobnostech najdete v tématu Vytvoření instance a vrácení typů implementace a rozhraní.
V konstruktoru je také SampleTitle().Text(FEATURE_NAME());. Součástí SampleTitle() je volání jednoduché funkce přístupového objektu s názvem SampleTitle, která vrátí TextBlock, kterou jsme přidali do XAML. Pokaždé, když x:Name prvek XAML, kompilátor XAML vygeneruje přístupovou metodu pojmenovanou podle tohoto prvku. Část .Text(...) volá mutační funkci Text pro objekt TextBlock, který vrátil přístupový nástroj SampleTitle. A FEATURE_NAME() volá naši statickou MainPage::FEATURE_NAME funkce přístupového objektu k vrácení řetězcového literálu. Tento řádek kódu nastaví vlastnost Text pro TextBlock s názvem SampleTitle.
Vzhledem k tomu, že řetězce jsou v prostředí Windows Runtime široké, pro portování řetězcového literálu použijeme předponu širokého znakového kódování L. Proto změníme (například) "řetězcový literál" na L"řetězcový literál". Viz také široké řetězcové literály.
Scénáře
Tady je relevantní kód jazyka C#, který potřebujeme portovat.
// 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) }
};
...
}
...
Z předchozího šetření víme, že tato kolekce objektů Scenario se zobrazuje v ListBox. V jazyce C++/WinRT existují omezení druh kolekce, kterou můžeme přiřadit k vlastnosti ItemsSource ovládacího prvku položky. Kolekce musí být vektor nebo pozorovatelný vektor a jeho prvky musí být jedním z následujících:
- Buď třídy modulu runtime, nebo
- IInspectable.
Pro IInspectable případě, pokud prvky nejsou samy o sobě třídami prostředí runtime, pak tyto prvky musí být takového typu, který lze uzavírat a rozbalovat do a ze IInspectable. A to znamená, že musí být typy prostředí Windows Runtime (viz Boxing a rozbalování hodnot na IInspectable).
V této případové studii jsme neprovedli, aby Scénář byl třídou runtime. To je ale stále rozumná možnost. Budou případy ve vaší vlastní práci na portování, kde runtime třída bude určitě tou správnou volbou. Pokud například potřebujete, aby byl typ elementu pozorovatelným (viz ovládací prvky XAML; připojení na vlastnost C++/WinRT), nebo pokud prvek musí mít metody z jakéhokoli jiného důvodu a jde o víc než jen o sadu datových členů.
Vzhledem k tomu, že v tomto návodu jít s modulem runtime třídy pro Scénář typ, pak musíme přemýšlet o boxování. Pokud bychom Scénář normální structjazyka C++, pak bychom ho nemohli rámeček. V IDL jsme ale deklarovali scénář jako struct, takže ho můžeme .
Máme na výběr zaboxovat scénář předem, nebo čekat, až se chystáme přiřadit ItemsSource, a za běhu je zabalit. Tady jsou některé aspekty těchto dvou možností.
- Boxování předem. Pro tuto možnost je náš datový člen kolekcí IInspectable, která je připravená k přiřazení uživatelskému rozhraní. Při inicializaci zabalíme objekty Scenario do příslušného datového členu. Potřebujeme jenom jednu kopii této kolekce, ale pokaždé, když potřebujeme číst její pole, musíme prvek rozbalit.
- Box v pravou chvíli. Pro tuto možnost je náš datový člen kolekce Scénář. Když nastane čas přiřazení k uživatelskému rozhraní, zařadíme Scénář objekty z datového členu do nové kolekce IInspectable. Pole prvků v datovém členu můžeme číst bez rozbalení, ale potřebujeme dvě kopie kolekce.
Jak můžete vidět, u takové malé kolekce se výhody a nevýhody navzájem vyvažují. Takže pro tuto případovou studii půjdeme s možností just-in-time.
scénáře prvek je položka pole MainPage, která je definována a inicializována v SampleConfiguration.cs. A Scenarios je vlastnost pouze pro čtení MainPage, definována v MainPage.xaml.cs (a implementována k jednoduchému vrácení pole scénáře). V projektu C++/WinRT provedeme něco podobného. ale oba členy nastavíme jako statické (protože potřebujeme pouze jednu instanci v rámci aplikace, a abychom k nim měli přístup bez nutnosti instance třídy). A pojmenujeme je scénáře Vnitřní a scénáře , v tomto pořadí. Deklarujeme scénáře v MainPage.h. A protože má statickou dobu trvání úložiště, definujeme ji a inicializujeme v souboru .cpp (v tomto případěSampleConfiguration.cpp).
Upravte MainPage.h a SampleConfiguration.cpp tak, aby odpovídaly následujícím výpisům.
// 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>() },
});
Nezapomeňte také odstranit existující tělo funkce z MainPage.cpp pro MainPage::scenarios(), protože teď tuto metodu definujeme v souboru hlaviček.
Jak vidíte, v SampleConfiguration.cppinicializujeme statický datový člen scénářeInner voláním pomocné funkce C++/WinRT s názvem winrt::single_threaded_observable_vector. Tato funkce pro nás vytvoří nový objekt kolekce prostředí Windows Runtime a vrátí ho jako IObservableVector rozhraní. Vzhledem k tomu, že v této ukázce není kolekce pozorovatelná (nemusí to být, protože po inicializaci nepřidá ani neodebere prvky), mohli jsme se místo toho rozhodnout volat winrt::single_threaded_vector. Tato funkce vrátí kolekci jako rozhraní IVector.
Další informace o kolekcích a vazbách na ně najdete v tématu ovládacích prvků položek XAML, vázaných na kolekci C++/WinRT, a Kolekce s C++/WinRT.
Inicializační kód, který jste právě přidali, odkazuje na typy, které ještě nejsou v projektu (například winrt::SDKTemplate::CopyText). Abychom to mohli napravit, pojďme do projektu přidat pět nových prázdných stránek XAML.
Přidání pěti nových prázdných stránek XAML
Přidejte do projektu novou položku Visual C++>prázdná stránka (C++/WinRT), ujistěte se, že jde o šablonu položky prázdná stránka (C++/WinRT), a nikoli o prázdnou stránku. Pojmenujte ho CopyText. Nová stránka XAML je definována v oboru názvů SDKTemplate, což je to, co chceme.
Opakujte výše uvedený proces ještě čtyřikrát a pojmenujte stránky XAML CopyImage, CopyFiles, HistoryAndRoaminga OtherScenarios.
Teď budete moct znovu sestavit, pokud chcete.
UpozornitUživatele
V projektu C# najdete implementaci metody MainPage.NotifyUser v MainPage.xaml.cs.
MainPage.NotifyUser má závislost na MainPage.UpdateStatusa tato metoda zase obsahuje závislosti na elementech XAML, které jsme ještě nepřevedli. Prozatím tedy v projektu C++/WinRT vytvoříme prozatímní verzi metody UpdateStatus a portujeme ji později.
Tady je relevantní kód jazyka C#, který potřebujeme portovat.
// 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 používá Windows.UI.Core.CoreDispatcherPriority výčtový typ. V C++/WinRT, kdykoli chcete použít typ z oborů názvů Systému Windows, musíte zahrnout odpovídající soubor hlavičky oboru názvů C++/WinRT systému Windows (další informace o tom najdete v tématu Začínáme s C++/WinRT). V tomto případě, jak uvidíte v následujícím výpisu kódu, je hlavička winrt/Windows.UI.Core.ha my ji zahrneme do pch.h.
UpdateStatus je soukromý. Proto vytvoříme privátní metodu na našem typu implementace MainPage. UpdateStatus nemá být volána na třídu runtime, takže ji nebudeme deklarovat v IDL.
Po přenesení MainPage.NotifyUsera zastavení MainPage.UpdateStatus, to je to, co máme v projektu C++/WinRT. Po tomto výpisu kódu se podíváme na některé podrobnosti.
// pch.h
...
#include <winrt/Windows.UI.Core.h>
...
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
void NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type);
...
private:
void UpdateStatus(hstring const& strMessage, SDKTemplate::NotifyType const& type);
...
};
// MainPage.cpp
...
void MainPage::NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
if (Dispatcher().HasThreadAccess())
{
UpdateStatus(strMessage, type);
}
else
{
Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [strMessage, type, this]()
{
UpdateStatus(strMessage, type);
});
}
}
void MainPage::UpdateStatus(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
throw hresult_not_implemented();
}
...
V jazyce C# můžete pomocí zápisu tečky tečku do vnořených vlastností. Takže typ MainPage jazyka C# má přístup k vlastní vlastnosti Dispatcher s syntaxí Dispatcher. C# může dále použít k přístupu k této hodnotě pomocí syntaxe, například Dispatcher.HasThreadAccess. V jazyce C++/WinRT se vlastnosti implementují jako funkce příslušenství, takže syntaxe se liší pouze v tom, že přidáte závorky pro každé volání funkce.
| C# | C++/WinRT |
|---|---|
Dispatcher.HasThreadAccess |
Dispatcher().HasThreadAccess() |
Když verze C# NotifyUser volá CoreDispatcher.RunAsync, implementuje asynchronní delegát zpětného volání jako funkci lambda. Verze C++/WinRT dělá totéž, ale syntaxe se trochu liší. V C++/WinRT zachytit dva parametry, které použijeme, a také ukazatel this (protože budeme volat členovou funkci). Další informace o implementaci delegátů jako lambda a příklady kódu najdete v tématu Zpracování událostí pomocí delegátů v C++/WinRT. Také můžeme v tomto konkrétním případu ignorovat část var task =. Nečekáme na vrácený asynchronní objekt, takže ho nemusíte ukládat.
Implementace zbývajících členů mainpage
Pojďme vytvořit úplný seznam členů MainPage (implementovaných přes MainPage.xaml.cs a SampleConfiguration.cs), abychom viděli, které z nich jsme již dokončili, a které z nich ještě musíme provést.
| Člen | Přístup | Stav |
|---|---|---|
| konstruktor |
public |
Portovaný |
| vlastnost Current | public |
Portovaný |
| vlastnost FEATURE_NAME | public |
Portovaný |
| Vlastnost IsClipboardContentChangedEnabled | public |
Nezahájeno |
| scénáře vlastnost | public |
Portovaný |
| metoda BuildClipboardFormatsOutputString | public |
Nezahájeno |
| metoda DisplayToast | public |
Nezahájeno |
| Metoda EnableClipboardContentChangedNotifications | public |
Nezahájeno |
| metoda NotifyUser | public |
Portovaný |
| Metoda OnNavigatedTo | protected |
Nezahájeno |
| pole isApplicationWindowActive | private |
Nezahájeno |
| pole potřebaTisknoutFormátSchránky | private |
Nezahájeno |
| scénáře pole | private |
Portovaný |
| metoda Button_Click | private |
Nezahájeno |
| metoda DisplayChangedFormats | private |
Nezahájeno |
| metoda Footer_Click | private |
Nezahájeno |
| Metoda HandleClipboardChanged | private |
Nezahájeno |
| metoda OnClipboardChanged | private |
Nezahájeno |
| Metoda OnWindowActivated | private |
Nezahájeno |
| metoda ScenarioControl_SelectionChanged | private |
Nezahájeno |
| Metoda UpdateStatus | private |
Uhašeno |
V následujících několika pododdílech si promluvíme o dosud nepřevedených členech.
Poznámka:
Občas najdeme odkazy ve zdrojovém kódu na prvky uživatelského rozhraní v kódu XAML (v MainPage.xaml). Až na tyto odkazy přijdeme, dočasně je budeme obcházet přidáním jednoduchých zástupných prvků do XAML. Tímto způsobem bude projekt dále rozvíjen po každé dílčí části. Alternativou je přeložit odkazy zkopírováním celého obsahu MainPage.xaml z projektu C# do projektu C++/WinRT. Ale pokud to uděláme, bude to trvat dlouho, než se dostaneme na pit stop a znovu sestavíme, což potenciálně zakryje jakékoli překlepy nebo jiné chyby, které jsme cestou udělali.
Jakmile dokončíme přenos imperativního kódu pro třídu MainPage, pak zkopírujeme obsah souboru XAML a budeme mít jistotu, že projekt bude i nadále sestavovat.
IsClipboardContentChangedEnabled
Toto je vlastnost jazyka C#, která je ve výchozím nastavení nastavena na false. Je členem MainPagea je definován v SampleConfiguration.cs.
Pro C++/WinRT budeme potřebovat přístupovou funkci, funkci mutátoru a záložního datového člena jako pole. Vzhledem k tomu, že IsClipboardContentChangedEnabled představuje stav jednoho ze scénářů v ukázce, nikoli stav MainPage samotný, vytvoříme nové členy na novém pomocném typu s názvem SampleState. A implementujeme ho v souboru zdrojového kódu SampleConfiguration.cpp a vytvoříme členy static (protože potřebujeme pouze jednu instanci v celé aplikaci; a abychom k nim měli přístup bez nutnosti instance třídy).
Pokud chcete připojit náš SampleConfiguration.cpp v projektu C++/WinRT, přidejte novou položku Visual C++>Code>Header File (.h) položky s názvem SampleConfiguration.h. Upravte SampleConfiguration.h a SampleConfiguration.cpp tak, aby odpovídaly následujícím výpisům.
// 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;
}
}
Pole s úložištěm static (například SampleState::isClipboardContentChangedEnabled) musí být v aplikaci definováno jednou a soubor .cpp je vhodným místem pro to (SampleConfiguration.cpp v tomto případě).
buildClipboardFormatsOutputString
Tato metoda je veřejným členem MainPagea je definována v 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();
}
...
V jazyce C++/WinRT vytvoříme BuildClipboardFormatsOutputString veřejnou statickou metodu SampleState. Můžeme ho nastavit static, protože nemá přístup k žádným členům instance.
Pokud chcete použít schránku a typy DataPackageView v C++/WinRT, budeme muset zahrnout soubor hlaviček oboru názvů C++/WinRT Windows winrt/Windows.ApplicationModel.DataTransfer.h.
V jazyce C# je vlastnost DataPackageView.AvailableFormats typu IReadOnlyList, takže můžeme přistupovat k vlastnosti Count tohoto objektu. V jazyce C++/WinRT vrátí funkce DataPackageView::AvailableFormatsIVectorView, která má přístupovou funkci Velikost, kterou můžeme zavolat.
K přenesení používání typu C# System.Text.StringBuilder použijeme standardní typ C++ std::wostringstream. Tento typ je výstupní datový proud pro široké řetězce (a abychom ho mohli použít, musíme zahrnout soubor hlavičky sstream). Místo toho, abyste jako u StringBuilderpoužili metodu Append, použijte vkládací operátor (<<) s výstupním proudem, jako je například wostringstream. Další informace najdete v tématu programování iostreama Formátování C++/WinRT řetězců.
Kód jazyka C# vytvoří StringBuilder s klíčovým slovem new. V jazyce C# jsou objekty ve výchozím nastavení odkazové typy deklarované v haldě s new. V moderním standardu C++ jsou objekty ve výchozím nastavení typy hodnot deklarované v zásobníku (bez použití new). Proto portujeme StringBuilder output = new StringBuilder(); do C++/WinRT jako jednoduše std::wostringstream output;.
Klíčové slovo jazyka C# var požádá kompilátor o odvození typu. Portujete var do auto v jazyce C++/WinRT. V jazyce C++/WinRT však existují případy, kdy (aby se předešlo kopírování), chcete odkaz na odvozený (nebo vyvolaný) typ a vyjádříte lvalue odkaz na vyvolaný typ pomocí auto&. Existují také případy, kdy chcete speciální druh odkazu, který se správně sváže, zda je inicializován s lvalue nebo s rvalue. Vyjadřujete to pomocí auto&&. To je forma, která se používá ve smyčce for v níže převedeném kódu. Pro úvod do lvalues a rvalues, vizte Kategorie hodnot a odkazy na ně.
Upravte pch.h, SampleConfiguration.ha SampleConfiguration.cpp tak, aby odpovídaly následujícím výpisům.
// 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() };
}
Poznámka:
Syntaxe v řádku kódu DataPackageView clipboardContent{ Clipboard::GetContent() }; používá prvek moderního standardu jazyka C++ známý jako jednotná inicializace, která se vyznačuje použitím složených závorek namísto obvyklého znaménka rovnítka =. Tato syntaxe jasně vyjasňuje, že probíhá inicializace, nikoli přiřazení. Pokud dáváte přednost formě syntaxe, která vypadá jako přiřazení (ale ve skutečnosti není), pak můžete výše uvedenou syntaxi nahradit ekvivalentem DataPackageView clipboardContent = Clipboard::GetContent();. Je vhodné se seznámit s oběma způsoby vyjádření inicializace, protože pravděpodobně uvidíte oba často používané v kódu, na který narazíte.
ZobrazitOznámení
DisplayToast je veřejná statická metoda třídy MainPage jazyka C# a najdete ji definovanou v SampleConfiguration.cs. V jazyce C++/WinRT ji nastavíme jako veřejnou statickou metodu SampleState.
Už jsme zjistili většinu podrobností a technik, které jsou relevantní pro přenos této metody. Jednou novou položkou, kterou je potřeba poznamenat, je, že doslovný řetězec jazyka C# (@) přeportujete do standardního nezpracovaného řetězcového literálu C++ (,LR).
Při odkazování na typy ToastNotification a XmlDocument v jazyce C++/WinRT je můžete buď kvalifikovat podle názvu oboru názvů, nebo můžete upravit SampleConfiguration.cpp a přidat direktivy using namespace tak, jako je uvedeno v následujícím příkladu.
using namespace Windows::UI::Notifications;
Stejnou volbu máte, když odkazujete na typ XmlDocument a vždy, když odkazujete na jakýkoli jiný typ prostředí Windows Runtime.
Kromě těchto položek postupujte podle stejných pokynů, které jste provedli dříve, abyste provedli následující kroky.
- Deklarujte metodu v
SampleConfiguration.ha definujte ji vSampleConfiguration.cpp. - Upravte
pch.htak, aby zahrnoval všechny nezbytné soubory hlaviček oboru názvů C++/WinRT systému Windows. - Vytvořte objekty C++/WinRT v zásobníku, ne v haldě.
- Nahraďte volání přístupových metod get vlastností syntaxí volání funkcí (
()).
Velmi častou příčinou chyb kompilátoru nebo linkeru je, že zapomenete zahrnout soubory hlaviček oboru názvů C++/WinRT systému Windows, které potřebujete. Další informace o jedné možné chybě viz C3779: Proč mi kompilátor hlásí chybu "consume_Something: funkce, která vrací 'auto', nemůže být použita, než je definována"?.
Pokud chcete postupovat podle návodu a sami přenést DisplayToast, můžete porovnat výsledky s kódem ve verzi C++/WinRT v ZIP archivu zdrojového kódu ukázky Clipboard, který jste stáhli.
PovolteOznámeníZměnObsahuSchránky
EnableClipboardContentChangedNotifications je veřejná statická metoda třídy MainPage a je definovaná v 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) { ... }
...
V jazyce C++/WinRT ji nastavíme jako veřejnou statickou metodu SampleState.
V jazyce C# použijete syntaxi operátoru += a -= k registraci a odvolání delegátů zpracování událostí. V C++/WinRT máte několik syntaktických možností registrace nebo odvolání delegáta, jak je popsáno v tématu Zpracování událostí pomocí delegátů v C++/WinRT. Obecně ale platí, že se zaregistrujete a odvoláte voláním dvojice funkcí pojmenovaných pro danou událost. Pokud se chcete zaregistrovat, předáte svého delegáta registrující funkci a na oplátku získáte token pro odvolání (winrt::event_token). Pokud chcete tento token odvolat, předáte ho funkci odvolání. V tomto případě je obslužná rutina statická a (jak vidíte v následujícím výpisu kódu) syntaxe volání funkce je přímočará.
Podobné tokeny se ve skutečnosti používají na pozadí v jazyce C#. Ale jazyk tento detail implicitně obsahuje. C++/WinRT je explicitní.
Objekt typ se zobrazí v podpisech obslužné rutiny události jazyka C#. V jazyce C# je objekt aliasem pro typ System.Object rozhraní .NET. Ekvivalentem v jazyce C++/WinRT je winrt::Windows::Foundation::IInspectable. V obslužných rutinách událostí C++/WinRT se zobrazí IInspectable.
Upravte SampleConfiguration.h a SampleConfiguration.cpp tak, aby odpovídaly následujícím výpisům.
// SampleConfiguration.h
...
static bool EnableClipboardContentChangedNotifications(bool enable);
...
private:
...
static event_token clipboardContentChangedToken;
static event_token activatedToken;
static void OnClipboardChanged(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
static void OnWindowActivated(Windows::Foundation::IInspectable const& sender, Windows::UI::Core::WindowActivatedEventArgs const& e);
...
// SampleConfiguration.cpp
...
using namespace Windows::Foundation;
using namespace Windows::UI::Core;
using namespace Windows::UI::Xaml;
...
event_token SampleState::clipboardContentChangedToken;
event_token SampleState::activatedToken;
...
bool SampleState::EnableClipboardContentChangedNotifications(bool enable)
{
if (isClipboardContentChangedEnabled == enable)
{
return false;
}
IsClipboardContentChangedEnabled(enable);
if (enable)
{
clipboardContentChangedToken = Clipboard::ContentChanged(OnClipboardChanged);
activatedToken = Window::Current().Activated(OnWindowActivated);
}
else
{
Clipboard::ContentChanged(clipboardContentChangedToken);
Window::Current().Activated(activatedToken);
}
return true;
}
void SampleState::OnClipboardChanged(IInspectable const&, IInspectable const&){}
void SampleState::OnWindowActivated(IInspectable const&, WindowActivatedEventArgs const& e){}
Delegáty zpracování událostí zatím ponechte jako zástupné procedury (OnClipboardChanged a OnWindowActivated). Jsou už na našem seznamu členů, kteří se mají přenést, takže se k nim dostaneme v pozdějších dílčích oddílech.
OnNavigatedTo
OnNavigatedTo je chráněná metoda třídy MainPage jazyka C# a je definována v MainPage.xaml.cs. Tady je, společně s XAML ListBox, na který odkazuje.
<!-- 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;
}
}
Je to důležitá a zajímavá metoda, protože tady je naše kolekce objektů Scenario přiřazena k uživatelskému rozhraní. Kód C# vytvoří System.Collections.Generic.ListScenario objektů a přiřadí je ItemsSource vlastnosti ListBox (což je ovládací prvek pro položky). A v jazyce C# používáme interpolaci řetězců k vytvoření názvu pro každý objekt Scénář (všimněte si použití $ speciálního znaku).
V C++/WinRT vytvoříme OnNavigatedTo veřejnou metodu MainPage. A do XAML přidáme zástupný kód ListBox element, aby sestavení proběhlo úspěšně. Po výpisu kódu se podíváme na některé podrobnosti.
<!-- MainPage.xaml -->
...
<StackPanel ...>
...
<ListBox x:Name="ScenarioControl" />
</StackPanel>
...
// MainPage.h
...
void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
...
// MainPage.cpp
...
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Navigation;
...
void MainPage::OnNavigatedTo(NavigationEventArgs const& /* e */)
{
auto itemCollection = winrt::single_threaded_observable_vector<IInspectable>();
int i = 1;
for (auto s : MainPage::scenarios())
{
s.Title = winrt::to_hstring(i++) + L") " + s.Title;
itemCollection.Append(winrt::box_value(s));
}
ScenarioControl().ItemsSource(itemCollection);
if (Window::Current().Bounds().Width < 640)
{
ScenarioControl().SelectedIndex(-1);
}
else
{
ScenarioControl().SelectedIndex(0);
}
}
...
Znovu voláme funkci winrt::single_threaded_observable_vector, tentokrát ale vytvoříme kolekci IInspectable . Bylo to součástí rozhodnutí, které jsme přijali, abychom provedli zabalení našich objektů pro scénáře v reálném čase.
A místo použití interpolace řetězců c# zde používáme kombinaci funkce to_hstring a operátoru zřetě zení winrt::hstring.
jeOknoAplikaceAktivní
V jazyce C# je isApplicationWindowActive jednoduché privátní bool pole, které patří do třídy MainPage a je definováno v SampleConfiguration.cs. Výchozí hodnota je false. V jazyce C++/WinRT ho nastavíme jako privátní statické pole
Už jsme viděli, jak deklarovat, definovat a inicializovat statické pole. Pro připomenutí, podívejte se zpět na to, co jsme udělali s polem isClipboardContentChangedEnabled, a udělejte to samé s isApplicationWindowActive.
je třeba vytisknout formát schránky
Stejný vzor jako isApplicationWindowActive (viz nadpis bezprostředně před tímto vzorem).
Tlačítko_Klik
Button_Click je privátní metoda (zpracování událostí) třídy MainPage jazyka C# a je definovaná v MainPage.xaml.cs. Tady je, společně s XAML SplitView, který odkazuje, a ToggleButton, který jej registruje.
<!-- MainPage.xaml -->
...
<SplitView x:Name="Splitter" ... />
...
<ToggleButton Click="Button_Click" .../>
...
private void Button_Click(object sender, RoutedEventArgs e)
{
Splitter.IsPaneOpen = !Splitter.IsPaneOpen;
}
A tady je ekvivalent portovaný do C++/WinRT. Všimněte si, že ve verzi C++/WinRT je obslužná rutina události public (jak vidíte, deklarujete ji před deklaracemi a private:). Důvodem je to, že obslužná rutina události zaregistrovaná v kódu XAML, například tato, musí být public v jazyce C++/WinRT, aby k němu kód XAML získal přístup. Pokud na druhou stranu zaregistrujete obslužnou rutinu události v imperativním kódu (jako jsme to udělali v MainPage::EnableClipboardContentChangedNotifications dříve), obslužná rutina události nemusí být public.
<!-- MainPage.xaml -->
...
<StackPanel ...>
...
<SplitView x:Name="Splitter" />
</StackPanel>
...
// MainPage.h
...
void Button_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
private:
...
// MainPage.cpp
void MainPage::Button_Click(Windows::Foundation::IInspectable const& /* sender */, Windows::UI::Xaml::RoutedEventArgs const& /* e */)
{
Splitter().IsPaneOpen(!Splitter().IsPaneOpen());
}
Změněné formáty zobrazení
V jazyce C# je DisplayChangedFormats soukromou metodou, která patří do třídy MainPage a je definovaná v SampleConfiguration.cs.
private void DisplayChangedFormats()
{
string output = "Clipboard content has changed!" + Environment.NewLine;
output += BuildClipboardFormatsOutputString();
NotifyUser(output, NotifyType.StatusMessage);
}
V jazyce C++/WinRT ho nastavíme jako privátní statické pole
Místo statické vlastnosti System.Environment.NewLine, která se používá v kódu C#, vložíme do výstupního datového proudu standardní značku nového řádku std::endl C++.
// 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);
}
Návrh verze C++/WinRT výše má malou neefektivitu. Nejprve vytvoříme std::wostringstream. Ale také voláme metodu BuildClipboardFormatsOutputString, kterou jsme dříve portovali. Tato metoda vytvoří vlastní std::wostringstream. Změní svůj proud na winrt::hstring a poté to vrátí. Zavoláme funkci hstring::c_str, která vrátí hstring zpět na řetězec ve stylu jazyka C, a pak ji vložíme do našeho datového proudu. Bylo by efektivnější vytvořit pouze jeden std::wostringstreama předat ho (jako odkaz), aby metody mohly vkládat řetězce přímo do něj.
To je to, co děláme ve verzi C++/WinRT v části zdrojového kódu ukázky schránky (v ZIP souboru, který jste stáhli). Ve zdrojovém kódu existuje nová privátní statická metoda s názvem SampleState::AddClipboardFormatsOutputString, která přebírá a pracuje s odkazem na výstupní datový proud. A pak jsou metody SampleState::DisplayChangedFormats a SampleState::BuildClipboardFormatsOutputString refaktorovány tak, aby volaly tuto novou metodu. Funkčně odpovídá výpisům kódu v tomto tématu, ale je efektivnější.
Footer_Click
Footer_Click je asynchronní obslužná rutina událostí, která patří do třídy MainPage a je definovaná v MainPage.xaml.cs. Níže uvedený výpis kódu je funkčně ekvivalentní metodě ve zdrojovém kódu, který jste stáhli. Ale tady jsem ho rozbalil z jednoho řádku na čtyři, aby bylo snazší vidět, co to dělá, a následně, jak bychom měli přenést kód.
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);
}
I když je technicky metoda asynchronní, neprovádí nic po await, takže nepotřebuje await (ani klíčové slovo async). Pravděpodobně je používá, aby se vyhnulo zprávě IntelliSense v programu Visual Studio.
Ekvivalentní metoda C++/WinRT bude také asynchronní (protože volá Launcher.LaunchUriAsync). Nemusí ale co_awaitani vracet asynchronní objekt. Informace o co_await a asynchronních objektech najdete v Souběžnost a asynchronní operace s C++/WinRT.
Teď si promluvme o tom, co metoda dělá. Vzhledem k tomu, že jde o obsluha události Click hypertextového tlačítka HyperlinkButton, objekt s názvem odesílatel je ve skutečnosti HyperlinkButton. Převod typu je tedy bezpečný (tento převod bychom mohli případně vyjádřit jako sender as HyperlinkButton). Dále načteme hodnotu vlastnosti Tag (pokud se podíváte na kód XAML v projektu C#, uvidíte, že je nastavená na řetězec představující webovou adresu URL). Ačkoli je vlastnost FrameworkElement.Tag (HyperlinkButton je FrameworkElement) typu objekt, v jazyce C# ji můžeme převést na řetězec pomocí Object.ToString. Z výsledného řetězce vytvoříme URI objektu. A nakonec (s pomocí Shellu) spustíme prohlížeč a přejdeme na adresu URL.
Zde je metoda portovaná do C++/WinRT (opět rozšířena pro přehlednost), po které je popis podrobností.
// pch.h
...
#include "winrt/Windows.System.h"
...
// MainPage.h
...
void Footer_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
private:
...
// MainPage.cpp
...
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI::Xaml::Controls;
...
void MainPage::Footer_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const&)
{
auto hyperlinkButton{ sender.as<HyperlinkButton>() };
hstring tagUrl{ winrt::unbox_value<hstring>(hyperlinkButton.Tag()) };
Uri uri{ tagUrl };
Windows::System::Launcher::LaunchUriAsync(uri);
}
Jak je obvyklé, vytváříme handler události public. Používáme jako funkci u odesílatele objekt převést na HyperlinkButton. V C++/WinRT je vlastnost TagIInspectable (což odpovídá objektu). Ale na IInspectablenení Tostring. Místo toho musíme rozbalit IInspectable na skalární hodnotu (v tomto případě řetězec). Znovu, pro více informací o boxování a rozbalování, viz boxování a rozbalování hodnot do IInspectable.
Poslední dva řádky opakují vzory portování, které jsme viděli dříve, a prakticky se shodují s verzí v jazyce C#.
HandleClipboardChanged
Při portování této metody není nic nového. Můžete porovnat verze C# a C++/WinRT ve zdrojovém kódu ukázky Schránky , který jste stáhli v souboru ZIP.
OnClipboardChanged a OnWindowActivated
Zatím máme pouze prázdné zástupné procedury pro tyto dva obslužné rutiny událostí. Ale jejich portování je jednoduché a nezpůsobí nic nového, co by se mělo diskutovat.
ScenarioControl_SelectionChanged (Změna výběru řízení scénáře)
Toto je další obslužná rutina privátní události, která náleží k třídě MainPage a je definována v MainPage.xaml.cs. V jazyce C++/WinRT ho zpřístupníme jako veřejný a implementujeme ho v MainPage.h a MainPage.cpp.
Pro tuto metodu budeme potřebovat MainPage::navigating, což je soukromé booleovské pole, inicializováno na false. Budete potřebovat rám v MainPage.xaml, nazvaný ScenarioFrame. Kromě těchto podrobností ale portování této metody odhalí žádné nové techniky.
Pokud místo ručního přenášení kopírujete kód z verze C++/WinRT v souboru ZIP obsahujícím zdrojový kód ukázky Schránky, který jste stáhli, uvidíte, že se tam používá MainPage::NavigateTo. Prozatím stačí refaktorovat obsah NavigateTo do ScenarioControl_SelectionChanged.
AktualizovatStav
Zatím máme pouze základní strukturu pro MainPage.UpdateStatus. Přenesení jeho implementace opět převážně pokrývá již známou půdu. Jedním z nových bodů na vědomí je, že zatímco v jazyce C# můžeme porovnat řetězec s String.Empty, v jazyce C++/WinRT místo toho voláme funkci winrt::hstring::empty. Dalším je, že nullptr je standardním ekvivalentem nullz jazyka C# v jazyce C++.
Zbytek portu můžete dokončit pomocí technik, které jsme už probrali. Zde je seznam věcí, které budete muset udělat, než portovaná verze této metody půjde zkompilovat.
- Chcete-li
MainPage.xaml, přidejte ohraničení s názvem StatusBorder. - Chcete-li
MainPage.xaml, přidejte TextBlock pojmenovaný jako StatusBlock. - Chcete-li
MainPage.xaml, přidejte StackPanel s názvem StatusPanel. - Chcete-li
pch.h, přidejte#include "winrt/Windows.UI.Xaml.Media.h". - Chcete-li
pch.h, přidejte#include "winrt/Windows.UI.Xaml.Automation.Peers.h". - Chcete-li
MainPage.cpppřidatusing namespace winrt::Windows::UI::Xaml::Media;. - Chcete-li
MainPage.cpppřidatusing namespace winrt::Windows::UI::Xaml::Automation::Peers;.
Zkopírujte XAML a styly potřebné k dokončení přenosu MainPage.
V případě XAML je ideálním případem použití stejných kódu XAML v jazyce C# a projektu C++/WinRT. A ukázka schránky je jedním z těchto případů.
Ve svém souboru Styles.xaml obsahuje ukázka schránky XAML ResourceDictionary stylů, které se aplikují na tlačítka, nabídky a další prvky v uživatelském rozhraní celé aplikace. Stránka Styles.xaml je sloučena do App.xaml. A pak je tu standardní MainPage.xaml výchozí bod uživatelského rozhraní, který jsme už krátce viděli. Nyní můžeme tyto tři .xaml soubory znovu použít beze změny ve verzi projektu C++/WinRT.
Stejně jako u souborů prostředků se můžete rozhodnout, že chcete odkazovat na stejné sdílené soubory XAML z více verzí aplikace. V tomto názorném postupu z důvodu jednoduchosti zkopírujeme soubory do projektu C++/WinRT a přidáme je tímto způsobem.
Přejděte do složky \Clipboard_sample\SharedContent\xaml, vyberte a zkopírujte App.xaml a MainPage.xamla vložte tyto dva soubory do složky \Clipboard\Clipboard v projektu C++/WinRT a po zobrazení výzvy zvolte nahrazení souborů.
V projektu C++/WinRT v sadě Visual Studio klikněte na Zobrazit všechny soubory a zapněte ho. Teď přidejte novou složku okamžitě pod uzel projektu a pojmenujte ji Styles. V Průzkumníku souborů přejděte do složky \Clipboard_sample\SharedContent\xaml, vyberte a zkopírujte Styles.xamla vložte ji do složky \Clipboard\Clipboard\Styles, kterou jste právě vytvořili. Zpět v Průzkumníku řešení v projektu C++/WinRT klikněte pravým tlačítkem na složku Styles>Přidat>Existující položku... a přejděte na \Clipboard\Clipboard\Styles. V nástroji pro výběr souboru vyberte Styles a klikněte na Přidat.
Přidejte novou složku do projektu C++/WinRT okamžitě pod uzlem projektu a pojmenujte ji Styles. Přejděte do složky \Clipboard_sample\SharedContent\xaml, vyberte a zkopírujte Styles.xamla vložte ji do složky \Clipboard\Clipboard\Styles v projektu C++/WinRT. Klikněte pravým tlačítkem na složku Styles (v Průzkumníku řešení v projektu C++/WinRT) >Přidat>Existující položku... a přejděte na \Clipboard\Clipboard\Styles. V nástroji pro výběr souboru vyberte Styles a klikněte na Přidat.
Dalším kliknutím na Zobrazit všechny soubory ho vypnete.
Dokončili jsme portování MainPage a pokud jste postupovali podle kroků, projekt C++/WinRT se teď sestaví a spustí.
Sloučení souborů .idl
Kromě standardního MainPage.xaml výchozího bodu pro uživatelské rozhraní obsahuje ukázka schránky pět dalších stránek XAML specifických pro scénář společně s odpovídajícími soubory kódu. Ve verzi projektu C++/WinRT znovu použijeme skutečný kód XAML všech těchto stránek beze změny. V následujících několika hlavních částech se podíváme na to, jak portovat kód na pozadí. Ale před tím si promluvme o IDL.
Sloučení IDL tříd modulu runtime do jednoho souboru IDL má hodnotu. Další informace o této hodnotě najdete v tématu Rozdělení tříd runtime do souborů Midl (.idl). V dalším kroku sloučit obsah CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idla OtherScenarios.idl přesunutím IDL do jednoho souboru s názvem Project.idl (a odstraněním původních souborů).
Zatímco to děláme, odebereme také automaticky vygenerovanou fiktivní vlastnost (Int32 MyProperty;a její implementaci) z každého z těchto pěti typů stránek XAML.
Nejprve přidejte do projektu C++/WinRT novou položku Midl File (.idl). Pojmenujte ho Project.idl. Celý obsah Project.idl nahraďte následujícím kódem.
// Project.idl
namespace SDKTemplate
{
[default_interface]
runtimeclass CopyFiles : Windows.UI.Xaml.Controls.Page
{
CopyFiles();
}
[default_interface]
runtimeclass CopyImage : Windows.UI.Xaml.Controls.Page
{
CopyImage();
}
[default_interface]
runtimeclass CopyText : Windows.UI.Xaml.Controls.Page
{
CopyText();
}
[default_interface]
runtimeclass HistoryAndRoaming : Windows.UI.Xaml.Controls.Page
{
HistoryAndRoaming();
}
[default_interface]
runtimeclass OtherScenarios : Windows.UI.Xaml.Controls.Page
{
OtherScenarios();
}
}
Jak vidíte, je to jen kopie obsahu jednotlivých souborů .idl, všechny uvnitř jednoho oboru názvů a s tím, že MyProperty je odebrán z každé běhové třídy.
V Průzkumníku řešení v sadě Visual Studio vyberte vícenásobný výběr všech původních souborů IDL (CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idla OtherScenarios.idl) a Upravit>Odebrat (v dialogovém okně zvolte Odstranit).
Nakonec, pro dokončení odebrání MyProperty, v souborech .h a .cpp pro každý z těchto pěti typů stránek XAML odstraňte deklarace a definice int32_t MyProperty() přístupových funkcí a void MyProperty(int32_t) mutatorových funkcí.
Mimochodem, vždy je vhodné, aby název souborů XAML odpovídal názvu třídy, kterou představují. Pokud máte například x:Class="MyNamespace.MyPage" v souboru značek XAML, měl by mít tento soubor název MyPage.xaml. Přestože to není technický požadavek, vyhnout se nutnosti žonglovat s různými názvy pro stejný artefakt pomůže váš projekt lépe pochopit, snadněji udržovat a lépe s ním pracovat.
KopírovatSoubory
V projektu C# je typ stránky XAML CopyFiles implementován ve zdrojových souborech kódu CopyFiles.xaml a CopyFiles.xaml.cs. Pojďme se podívat na všechny členy CopyFiles.
rootPage
Toto je soukromé pole.
// CopyFiles.xaml.cs
...
public sealed partial class CopyFiles : Page
{
MainPage rootPage = MainPage.Current;
...
}
...
V jazyce C++/WinRT ji můžeme definovat a inicializovat takto.
// CopyFiles.h
...
struct CopyFiles : CopyFilesT<CopyFiles>
{
...
private:
SDKTemplate::MainPage rootPage{ MainPage::Current() };
};
...
Znovu (stejně jako u MainPage::current), CopyFiles::rootPage je deklarován jako typ SDKTemplate::MainPage, což je projektovaný typ, a ne jako typ implementace.
CopyFiles (konstruktor)
V projektu C++/WinRT už typ CopyFiles obsahuje konstruktor obsahující požadovaný kód (pouze volá InitializeComponent).
CopyButton_Click
Metoda CopyButton_Click jazyka C# je obsluhující metoda události a z klíčového slova async ve své signatuře můžeme poznat, že metoda pracuje asynchronně. V jazyce C++/WinRT implementujeme asynchronní metodu jako korutina. Úvod do souběžnosti v jazyce C++/WinRT spolu s popisem toho, co je korutina, najdete v tématu Souběžnost a asynchronní operace s C++/WinRT.
Je běžné toužit naplánovat další práci po dokončení korutiny, a v takových případech by korutina vrátila určitý asynchronní typ objektu, na který lze čekat, a která volitelně hlásí průběh. Tyto aspekty se ale obvykle nevztahují na obslužnou rutinu události. Takže pokud máte obslužnou rutinu události, která provádí asynchronní operace, můžete ji implementovat jako korutinu, která vrací winrt::fire_and_forget. Další informace naleznete v sekci Fire and forget.
I když myšlenka fire-and-forget korutiny je, že vás nezajímá, kdy se dokončí, práce stále pokračuje (nebo je pozastavena a čeká na obnovení) na pozadí. Z implementace jazyka C# můžete vidět, že CopyButton_Click závisí na ukazateli this (přistupuje k datovému členu instance rootPage). Abychom si byli jistí, že ukazatel this (ukazatel na objekt CopyFiles) žije déle než korutina CopyButton_Click. V takové situaci, jako je tato ukázková aplikace, kdy uživatel prochází mezi stránkami uživatelského rozhraní, nemůžeme přímo řídit životnost těchto stránek. Pokud stránku CopyFiles zničíte (tak, že přejdete mimo ni), zatímco CopyButton_Click stále běží na pozadí, přístup k rootPagenebude bezpečný. Aby korutina fungovala správně, musí získat silný odkaz na ukazatel this a udržovat tento odkaz po dobu trvání korutiny. Další informace naleznete v části Silné a slabé odkazy v jazyce C++/WinRT.
Podíváte-li se do verze C++/WinRT ukázky, na CopyFiles::CopyButton_Click, uvidíte, že je provedena s jednoduchou deklarací v zásobníku.
fire_and_forget CopyFiles::CopyButton_Click(IInspectable const&, RoutedEventArgs const&)
{
auto lifetime{ get_strong() };
...
}
Pojďme se podívat na další aspekty portovaného kódu, které jsou důležité.
V kódu vytvoříme instanci objektu typu FileOpenPicker a o dva řádky později přistupujeme k vlastnosti FileTypeFilter tohoto objektu. Návratový typ této vlastnosti implementuje IVector řetězců. A na tom IVector, nazýváme IVector<T>. ReplaceAll(T[]) metoda. Zajímavý je na tom aspekt hodnoty, kterou předáváme této metodě, kde se očekává pole. Tady je řádek kódu.
filePicker.FileTypeFilter().ReplaceAll({ L"*" });
Hodnota, kterou předáváme ({ L"*" }), je standardní seznam inicializátorů C++ . Obsahuje jeden objekt, v tomto případě ale seznam inicializátorů může obsahovat libovolný počet objektů oddělených čárkami. Části jazyka C++/WinRT, které umožňují usnadnění předávání seznamu inicializátorů metodě, jako je tato, jsou vysvětleny v seznamu inicializátorů standardu.
Klíčové slovo await jazyka C# portujeme do co_await v jazyce C++/WinRT. Tady je příklad z kódu.
auto storageItems{ co_await filePicker.PickMultipleFilesAsync() };
Dále zvažte tento řádek kódu jazyka C#.
dataPackage.SetStorageItems(storageItems);
Jazyk C# dokáže implicitně převést IReadOnlyList<StorageFile> reprezentované pomocí storageItems na IEnumerable<IStorageItem>, jak očekává DataPackage.SetStorageItems. Ale v C++/WinRT musíme explicitně převést z IVectorView<StorageFile> na IIterable<IStorageItem>. A tak máme další příklad funkce jako v akci.
dataPackage.SetStorageItems(storageItems.as<IVectorView<IStorageItem>>());
Pokud používáme klíčové slovo null v jazyce C# (například Clipboard.SetContentWithOptions(dataPackage, null)), používáme nullptr v jazyce C++/WinRT (například Clipboard::SetContentWithOptions(dataPackage, nullptr)).
VložitTlačítko_Click
Toto je další obslužná rutina události ve formě korutiny typu fire-and-forget. Pojďme se podívat na aspekty portovaného kódu, které jsou důležité.
Ve verzi ukázky v jazyce C# zachytáváme výjimky s catch (Exception ex). V převedeném kódu C++/WinRT uvidíte výraz catch (winrt::hresult_error const& ex). Další informace o winrt::hresult_error a o tom, jak s ním pracovat, najdete v tématu Zpracování chyb pomocíC++/WinRT .
Příklad testování, zda je objekt jazyka C# null nebo není if (storageItems != null). V jazyce C++/WinRT můžeme spoléhat na operátor převodu na bool, který provádí test proti nullptr interně.
Tady je mírně zjednodušená verze fragmentu kódu z portované verze C++/WinRT ukázky.
std::wostringstream output;
output << std::wstring_view(ApplicationData::Current().LocalFolder().Path());
Vytvoření std::wstring_view z winrt::hstring, což představuje alternativu k volání funkce hstring::c_str (pro převod winrt::hstring na řetězec ve stylu jazyka C). Tato alternativa funguje díky hstringoperátor převodu na std::wstring_view.
Zvažte tento fragment jazyka C#.
var file = storageItem as StorageFile;
if (file != null)
...
K přenosu klíčového slova jazyka C# as do C++/WinRT jsme zatím viděli jako funkci, která byla použita několikrát. Tato funkce vyvolá výjimku, pokud převod typu selže. Pokud ale chceme, aby převod vrátil nullptr, pokud selže (abychom mohli tuto podmínku zpracovat v kódu), použijeme místo toho funkci try_as.
auto file{ storageItem.try_as<StorageFile>() };
if (file)
...
Zkopírujte XAML potřebné k dokončení přenosu CopyFiles
Teď můžete vybrat celý obsah souboru CopyFiles.xaml ze složky shared původního zdrojového kódu a vložit ho do souboru CopyFiles.xaml v projektu C++/WinRT, čímž nahradíte existující obsah tohoto souboru v projektu C++/WinRT.
Nakonec upravte CopyFiles.h a .cpp a odstraňte fiktivní funkci ClickHandler, protože jsme právě přepsali odpovídající kód XAML.
Dokončili jsme portování CopyFilesa pokud jste postupovali podle kroků, bude váš projekt C++/WinRT nyní sestavovat a spouštět a CopyFiles scénář bude funkční.
KopírovatObrázek
Pokud chcete portovat typ stránky CopyImage XAML, postupujte stejně jako u CopyFiles. Při přenosu copyImagese setkáte s použitím jazyka C# pomocí příkazu, který zajistí, že objekty, které implementují IDisposable rozhraní, budou uvolněny správně.
if (imageReceived != null)
{
using (var imageStream = await imageReceived.OpenReadAsync())
{
... // Pass imageStream to other APIs, and do other work.
}
}
Ekvivalentní rozhraní v jazyce C++/WinRT je IClosables jednou metodou Close. Tady je ekvivalent C++/WinRT výše uvedeného kódu jazyka C#.
if (imageReceived)
{
auto imageStream{ co_await imageReceived.OpenReadAsync() };
... // Pass imageStream to other APIs, and do other work.
imageStream.Close();
}
Objekty C++/WinRT implementují IClosable primárně pro výhody jazyků, které nemají deterministické finalizace. C++/WinRT má deterministické řízení finalizace, takže často nemusíme volat IClosable::Close, když píšeme v C++/WinRT. Ale tam jsou chvíle, kdy je dobré to volat, a to je jeden z těchto časů. Identifikátor imageStream je obálka s počítáním odkazů okolo základního objektu Windows Runtime, v tomto případě objektu, který implementuje IRandomAccessStreamWithContentType. Ačkoli můžeme určit, že finalizátor imageStream (jeho destruktor) se spustí na konci ohraničujícího oboru (složené závorky), nemůžeme si být jistí, že finalizátor skutečně zavolá Zavřít. Je to proto, že jsme předali imageStream jiným rozhraním API a můžou stále přispívat do referenčního počtu podkladového objektu prostředí Windows Runtime. Proto je vhodné použít Zavřít explicitně. Další informace najdete v tématu Musím volat IClosable::Close u runtime tříd, které používám?.
Dále zvažte C# výraz (uint)(imageDecoder.OrientedPixelWidth * 0.5), který najdete v obslužné rutině události OnDeferredImageRequestedHandler. Tento výraz vynásobí uint s double, což vede k double. Pak to přetypuje na uint. V jazyce C++/WinRT bychom mohli použít podobné přetypování ve stylu jazyka C ((uint32_t)(imageDecoder.OrientedPixelWidth() * 0.5)), ale je vhodnější jasně určit, jaký typ přetypování máme v úmyslu, a v tomto případě bychom to udělali s static_cast<uint32_t>(imageDecoder.OrientedPixelWidth() * 0.5).
Verze jazyka C# CopyImage.OnDeferredImageRequestedHandler má klauzuli finally, ale ne klauzuli catch. Ve verzi C++/WinRT jsme šli o něco dál a implementovali jsme klauzuli catch, abychom mohli hlásit, jestli bylo zpožděné vykreslování úspěšné nebo ne.
Přenos zbytku této stránky XAML nezpůsobí nic nového, co by bylo potřeba probrat. Nezapomeňte odstranit fiktivní funkci ClickHandler. A stejně jako u CopyFilesje posledním krokem v portu výběr celého obsahu CopyImage.xamla jeho vložení do stejného souboru v projektu C++/WinRT.
Text ke kopírování
CopyText.xaml a CopyText.xaml.cs můžete přenést pomocí technik, které jsme už probrali.
HistorieAndRoaming
Při portování typu stránky XAML Historie a Roaming existují určitá zajímavá místa.
Nejprve se podívejte na zdrojový kód jazyka C# a sledujte tok řízení z OnNavigatedTo prostřednictvím OnHistoryEnabledChanged obslužné rutiny události a nakonec k asynchronní funkci CheckHistoryAndRoaming (která není očekávána, takže se v podstatě aktivuje a zapomene). Vzhledem k tomu, že CheckHistoryAndRoaming je asynchronní, budeme muset být v jazyce C++/WinRT opatrní ohledně doby života ukazatele this. Výsledek uvidíte, pokud se podíváte na implementaci v souboru zdrojového kódu HistoryAndRoaming.cpp. Nejprve, když připojujeme delegáty k událostem Schránka::HistoryEnabledChanged a Schránka::RoamingEnabledChanged, použijeme pouze slabý odkaz na objekt stránky HistoryAndRoaming. Děláme to tak, že vytvoříme delegáta se závislostí na hodnotě vrácené z winrt::get_weakmísto závislosti na ukazateli this. To znamená, že samotný delegát, který nakonec volá asynchronní kód, neudržuje stránku HistoryAndRoaming naživu, pokud se od ní vzdálíme.
A za druhé, když se konečně dostaneme k fire-and-forget CheckHistoryAndRoaming korutinu, první věc, kterou uděláme, je vzít si silný odkaz na this, aby HistorieAndRoaming stránka vydržela alespoň do doby, než korutina konečně dokončí. Další informace o obou právě popsaných aspektech najdete v tématu Silné a slabé odkazy v C++/WinRT.
Najdeme další zajímavý bod při přenosu CheckHistoryAndRoaming. Obsahuje kód pro aktualizaci uživatelského rozhraní; takže musíme mít jistotu, že to děláme v hlavním vlákně uživatelského rozhraní. Vlákno, které původně volá obslužnou rutinu události, je hlavní vlákno uživatelského rozhraní. Asynchronní metoda ale obvykle může provádět a/nebo pokračovat v libovolném vlákně. V jazyce C# je řešením volat CoreDispatcher.RunAsynca aktualizovat uživatelské rozhraní z funkce lambda. V jazyce C++/WinRT můžeme použít funkci winrt::resume_foreground společně s this ukazatelem Dispatcher k pozastavení korutiny a okamžitému obnovení v hlavním vlákně uživatelského rozhraní.
Relevantní výraz je co_await winrt::resume_foreground(Dispatcher());. Alternativně, i když s menší srozumitelností bychom to mohli vyjádřit jednoduše jako co_await Dispatcher();. Kratší verze je dosažena díky operátoru převodu, který poskytuje C++/WinRT.
Přenos zbytku této stránky XAML nezpůsobí nic nového, co by bylo potřeba probrat. Nezapomeňte odstranit fiktivní funkci ClickHandler a zkopírovat kód XAML.
JinéScénáře
OtherScenarios.xaml a OtherScenarios.xaml.cs můžete přenést pomocí technik, které jsme už probrali.
Závěr
Doufáme, že vás tento názorný postup vybavil dostatečnými informacemi a technikami pro portování, abyste nyní mohli portovat své vlastní aplikace z C# do C++/WinRT. Pomocí aktualizačního modulu můžete pokračovat v odkazování na