Převedení ukázky práce se schránkou z C# do C++/WinRT — případová studie

Toto téma představuje případovou studii přenosu jedné z ukázek aplikace Univerzální platforma Windows (UPW) z C# do C++/WinRT. Praxi a zkušenosti s portováním můžete získat tak, že budete postupovat podle tohoto návodu a průběžně si sami portovat ukázkovou aplikaci.

Note

Zdrojový kód, který se portuje, je aplikace C# pro UPW. Cílový kód C++/WinRT v tomto článku je napsaný pro WinUI 3 (Windows App SDK). Všude, kde zdroj UPW používá rozhraní API, která se liší ve WinUI 3 (například Windows.UI.Core.CoreDispatcher vs. Microsoft.UI.Dispatching.DispatcherQueue), tento článek explicitně zobrazuje správný ekvivalent WinUI 3 ve výstupu C++/WinRT. Vzory kódu ze sloupce C++/WinRT můžete použít přímo v aplikaci WinUI 3.

Úplný katalog technických podrobností, které se týkají přenosu do C++/WinRT z C#, najdete v doprovodném tématu Přesunutí 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 (jeho návratový typ, jeho název a jeho typy a názvy parametrů). Definice funkce zahrnuje tělo funkce (její implementaci).

Je to trochu jiné, pokud jde o typy. Typ definujete tak, že zadáte jeho název a (minimálně) jenom deklarujete všechny její členské funkce (a další členy). To je správné, můžete definovat typ, i když nedefinujete jeho členské funkce.

  • Běžnými soubory zdrojového kódu C++ jsou .h (tečka h) a soubory .cpp. .h Soubor je hlavičkový soubor a definuje jeden nebo více typů. I když můžete definovat členské funkce v hlavičkovém souboru, k tomu obvykle slouží soubor .cpp. Takže pro hypotetický typ C++ MyClass byste definovali MyClass v MyClass.ha definovali byste jeho členské funkce v MyClass.cpp. Aby ostatní vývojáři mohli vaše třídy znovu používat, sdíleli byste jenom soubory a kód objektu .h . Vaše .cpp soubory byste zachovali 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 mí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. Uděláte to pomocí předkompilovaného souboru hlaviček (obvykle pojmenovaného pch.h).
  • .idl soubory. Tyto soubory obsahují IDL (Interface Definition Language). IDL si můžete představit jako soubory hlaviček pro typy prostředí Windows Runtime. Budeme mluvit více o IDL v části IDL pro typ MainPage.

Stáhněte a otestujte ukázku Schránka

Přejděte na ukázkovou webovou stránku schránky a klikněte na stáhnout soubor 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 v souborech shared a SharedContent složkách.

Postup v tomto tématu ukazuje, jak můžete znovu vytvořit verzi ukázky Schránka pro C++/WinRT jejím převedením ze 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í 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, může být pojmenována Clipboard spíše než Clipboard_sample. Budeme ale tuto složku i nadále označovat jako Clipboard_sample, abychom ji odlišili od verze C++/WinRT, kterou vytvoříte v pozdějším kroku.

Vytvořte prázdnou aplikaci s názvem Clipboard

Note

Informace o instalaci a použití rozšíření C++/WinRT Visual Studio (VSIX) a balíčku NuGet (které společně poskytují podporu šablony projektu a sestavení) najdete v tématu Visual Studio podpora pro C++/WinRT.

Zahajte proces přenosu vytvořením nového projektu C++/WinRT v Microsoft Visual Studio. Pomocí šablony projektu pro C++ Blank App, Packaged (WinUI 3 in Desktop) vytvořte nový projekt. Pojmenujte ho Clipboard a (aby struktura složek odpovídala postupu) se ujistěte, že políčko Umístit řešení a projekt do stejného adresáře není zaškrtnuté.

Pro výchozí ověření se ujistěte, že se tento nový prázdný projekt sestaví a spustí.

Package.appxmanifest a soubory assetů

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) identické. 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 koexistovat, potřebují různé identifikátory. V takovém případě v projektu C++/WinRT otevřete Package.appxmanifest soubor 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 všimněte hodnoty 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). V tomto případě například místo obnovení hodnoty názvu balíčku můžeme zkopírovanou hodnotu změnit z Microsoft. SDKSamples.Clipboard.CS na Microsoft. SDKSamples.Clipboard.CppWinRT. Id aplikace můžeme nechat nastavené na Aplikaci. 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. Řetězec Clipboard C# Sample se vyskytuje třikrát. Změňte ji na ukázku Clipboard C++/WinRT.

V projektu C++/WinRT už soubor Package.appxmanifest a projekt nejsou ve vztahu k souborům prostředků, na které odkazují, synchronizované. Pokud chcete tento postup napravit, nejprve odeberte prostředky z projektu C++/WinRT tak, že vyberete všechny soubory ve Assets složce (v Průzkumník řešení v Visual Studio) a odeberete je (v dialogovém okně zvolte Odstranit).

Projekt C# odkazuje na soubory assetů 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.pngsquaretile-sdk.png, storelogo-sdk.png, tile-sdk.png, a windows-sdk.png), zkopírujte a vložte je do \Clipboard\Clipboard\Assets složky v novém projektu.

Klikněte pravým tlačítkem myši na Assets složku (v Průzkumník řešení v projektu C++/WinRT) >Přidejte>existující položku... a přejděte na \Clipboard\Clipboard\Assets. V nástroji pro výběr souboru vyberte sedm souborů a klikněte na Přidat.

Package.appxmanifest je nyní znovu synchronizován se soubory assetů projektu.

MainPage, včetně funkcí, které konfigurují ukázku

Ukázka Clipboardu – stejně jako všechny ukázky aplikací platformy Univerzální platforma Windows (UPW) – se skládá ze sady scénářů, které může uživatel postupně procházet jeden po druhém. Kolekce scénářů v dané ukázce je nakonfigurovaná ve zdrojovém kódu ukázky. Každý scénář v kolekci je datová položka, která ukládá název, stejně jako typ třídy v projektu, který implementuje scénář.

Pokud se podíváte do souboru zdrojového kódu ve SampleConfiguration.cs verzi C# ukázky, uvidíte dvě třídy. Většina konfigurační logiky je ve třídě MainPage, což je částečná třída (úplnou třídu tvoří v kombinaci se značkami v MainPage.xaml a imperativním kódem v MainPage.xaml.cs). Druhá třída v tomto souboru zdrojového kódu je Scenario s jeho Title a ClassType vlastnosti.

V několika následujících pododdílech se podíváme na to, jak portovat MainPage a Scenario.

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 volatelné rozhraní typu prostředí Windows Runtime. Volatelné (nebo veřejné) rozhraní typu je zpřístupněno navenek, aby ho bylo možné používat. Tato projektovaná část typu kontrastuje se skutečnou interní implementací typu, která není samozřejmě volatelná a není veřejná. Je to jenom promítaná část, kterou definujeme v IDL.

Po vytvoření zdrojového .idl kódu IDL (v souboru) pak můžete IDL zkompilovat do strojově čitelných souborů metadat (označovaných také jako Windows Metadata). Tyto soubory metadat mají příponu .winmda tady jsou některé z jejich použití.

  • A .winmd můž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 Windows Metadata, která patří 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.
  • .winmd může popisovat typy prostředí Windows Runtime v jedné části aplikace, aby je mohla využívat jiná část téže aplikace. Například typ prostředí Windows Runtime, který využívá stránka XAML ve stejné aplikaci.
  • Aby bylo možné snadněji využívat typy prostředí Windows Runtime (integrované nebo třetí strany), systém sestavení C++/WinRT používá .winmd soubory k 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 .winmd soubor a pak ho použije k vygenerování obálky pro projekci a zástupné procedury, na kterých se má vaše implementace založit (o těchto zástupcích se dozvíme dále v tomto tématu).

Konkrétní verze IDL, kterou používáme v C++/WinRT, je Microsoft Interface Definition Language 3.0. Ve zbývající části tohoto tématu se podrobněji podíváme na typ mainpage jazyka C#. Rozhodneme, které její části mají být v projekci typu C++/WinRT MainPage (tj. v jeho volatelném neboli veřejném rozhraní) a které mohou být jen součástí jeho implementace. Toto rozlišení je důležité, protože když přistoupíme k psaní našeho IDL (čemuž se budeme věnovat v následující části), budeme v něm definovat pouze volatelné části.

Soubory zdrojového kódu jazyka C#, které společně implementují typ MainPage , jsou: MainPage.xaml (které brzy portujeme zkopírováním), MainPage.xaml.csa SampleConfiguration.cs.

Ve verzi C++/WinRT zabereme typ MainPage do souborů zdrojového kódu podobným způsobem. Vezmeme logiku MainPage.xaml.cs a přeložíme ji pro většinu případů na MainPage.h a MainPage.cpp. A pro logiku v SampleConfiguration.cs, přeložíme to na SampleConfiguration.h a SampleConfiguration.cpp.

Třídy v aplikaci Univerzální platforma Windows (UPW) v jazyce C# jsou samozřejmě typy prostředí Windows Runtime. Když ale vytvoříte typ v aplikaci C++/WinRT, můžete zvolit, jestli se jedná o typ prostředí Windows Runtime, nebo běžnou třídu/strukturu/výčet jazyka C++.

Každá stránka XAML v našem projektu musí být typu prostředí Windows Runtime, takže MainPage musí být typ prostředí Windows Runtime. V projektu C++/WinRT už je MainPage prostředí Windows Runtime typ, takže tento aspekt nemusíme měnit. Konkrétně jde o třídu modulu runtime.

  • Další podrobnosti o tom, zda byste měli vytvořit třídu modulu runtime pro daný typ, najdete v tématu Vytváření rozhraní API s C++/WinRT.
  • V C++/WinRT mají interní implementace třídy runtime a její promítané (veřejné) části podobu dvou různých tříd. Ty se označují jako typ implementace a projektovaný typ. Další informace o nich najdete v tématu uvedeném v předchozí odrážce a také v části Využívání rozhraní API pomocí C++/WinRT.
  • Další informace o souvislosti mezi třídami modulu runtime a soubory IDL (.idl) najdete v tématu Ovládací prvky XAML: vazba na vlastnost C++/WinRT, podle kterého můžete také postupovat. Toto téma vás provede procesem vytváření nové třídy modulu runtime. Prvním krokem je přidání nové položky souboru Midl (.idl) do projektu.

Pro MainPage máme ve skutečnosti potřebný MainPage.idl soubor již v projektu C++/WinRT. Je to proto, že to pro nás vytvořila šablona projektu. Později ale v tomto názorném postupu přidáme do projektu další .idl soubory.

Krátce uvidíme seznam přesně toho, co potřebujeme přidat do existujícího MainPage.idl souboru. Předtím si musíme ujasnit, co je potřeba zahrnout do IDL a co ne.

Abychom zjistili, které členy MainPage je potřeba deklarovat v MainPage.idl (aby se staly součástí běhové třídy MainPage) a které mohou být prostě členy implementačního typu MainPage, sestavme seznam členů třídy C# MainPage. Tyto členy najdeme tak, že se podíváte do MainPage.xaml.cs a do SampleConfiguration.cs.

Najdeme celkem dvanáct protected polí a private metod. A najdeme následující public členy.

  • Výchozí konstruktor MainPage().
  • Statická pole Aktuální a FEATURE_NAME.
  • Vlastnosti IsClipboardContentChangedEnabled a Scenarios.
  • Metody BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotifications a NotifyUser.

public Jsou to právě ti členové, které lze deklarovat v MainPage.idl. Pojďme tedy prozkoumat každou z nich a zjistit, zda musí být součástí běhové třídy MainPage, nebo zda stačí, aby byla součástí pouze její implementace.

  • Výchozí konstruktor MainPage(). U stránky XAML Page je běžné deklarovat výchozí konstruktor v jejím rozhraní IDL. Takto může architektura uživatelského rozhraní XAML aktivovat daný typ.
  • Statické pole Current se používá v rámci jednotlivých scénářů stránky XAML 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. U vlastních projektů se v takových případech můžete rozhodnout to udělat. 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 rámci typu MainPage . Opět platí, že když se rozhodneme jej deklarovat v IDL, bude náš kód o něco čistší.
  • Vlastnost IsClipboardContentChangedEnabled se používá pouze ve třídě OtherScenarios . Takže během portování to trochu zjednodušíme a uděláme z toho soukromé pole běhové třídy OtherScenarios. Takže ten nepůjde do IDL.
  • Vlastnost Scenarios je kolekce objektů typu Scenario (typ, který jsme zmínili dříve). O Scenario budeme mluvit v následujícím pododdílu, takže vlastnost Scenarios zatím nechme stranou.
  • Metody BuildClipboardFormatsOutputString, DisplayToast a EnableClipboardContentChangedNotifications jsou pomocné funkce, které se týkají spíše 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 se volá z jednotlivých stránek scénářů XAML u instance MainPage, která je vrácena ze statického pole Current. Vzhledem k tomu, že (jak již bylo uvedeno) Current je instance předpokládané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 použít k vytvoření vazby dat, musí být deklarován také v IDL (bez ohledu na to, jestli používáte {x:Bind} nebo {Binding}). Další informace najdete v tématu Datová vazba.

Postupujeme: sestavujeme seznam položek, které přidat a které nepřidat do souboru MainPage.idl. Stále ale musíme probrat vlastnost Scenarios a typ NotifyType . Tak to uděláme příště.

IDL pro typy Scénáře a NotifyType

Třída Scenario je definována v SampleConfiguration.cs. Rozhodli jsme se, jak přenést danou třídu do C++/WinRT. Standardně bychom z toho pravděpodobně vytvořili běžný C++ struct. Pokud se ale scénář používá napříč 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 scénář se v tomto kontextu používá.

<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 přiřazena k vlastnosti ItemsSource ovládacího prvku ListBox (což je ovládací prvek pro položky). Vzhledem k tomu, že scénářpotřebuje spolupracovat s XAML, musí to být typ prostředí Windows Runtime. Proto je potřeba ho definovat v IDL. Definování typu Scenario v IDL způsobí, že systém sestavení C++/WinRT za vás automaticky vygeneruje definici ve zdrojovém kódu typu Scenario v interním souboru záhlaví (jehož název a umístění nejsou pro tento návod důležité).

A vy si vzpomenete, že MainPage.Scenarios je kolekce objektů Scenario , které jsme právě řekli, že musí být v IDL. Z tohoto důvodu je nutné v IDL deklarovat i samotný MainPage.Scenarios .

NotifyType je enum, deklarovaný v MainPage.xaml.cs jazyka C#. Protože předáme NotifyType metodě, která patří do třídy modulu runtime MainPage, NotifyType také musí být typ prostředí Windows Runtime; a musí být definován v MainPage.idl.

Teď do souboru přidáme MainPage.idl nové typy a nový člen Mainpage , který jsme se rozhodli deklarovat v IDL. Zároveň z IDL odebereme zástupné členy Mainpage, které nám šablona projektu Visual Studio dala.

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 oboru názvů z Clipboard na SDKTemplate. Pokud chcete, stačí nahradit celý obsah MainPage.idl následujícím kódem. Dalším vylepšením je, že měníme název Scenario::ClassType na 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

Další informace o obsahu .idl souboru v projektu C++/WinRT najdete v tématu Microsoft Interface Definition Language 3.0.

Při vlastním portování možná nebudete chtít ani potřebovat změnit název oboru názvů, jak jsme to udělali výše. Děláme to tady jen proto, že výchozí namespace projektu C#, který portujeme, je SDKTemplate; zatímco název projektu i sestavení je Clipboard.

Ale jak budeme v tomto postupu pokračovat v portování, změníme ve zdrojovém kódu každý výskyt názvu oboru názvů Clipboard na SDKTemplate. Ve vlastnostech projektu C++/WinRT je také místo, kde se objevuje název oboru názvů Clipboard, takže teď využijeme příležitosti a změníme ho.

V Visual Studio nastavte pro projekt C++/WinRT vlastnost projektu Společné vlastnosti>C++/WinRT>Kořenový obor názvů na hodnotu SDKTemplate.

Uložte IDL a znovu vygenerujte soubory stub.

Téma ovládací prvky XAML; vazba na vlastnost C++/WinRT představuje pojem soubory stub a ukazuje jejich použití krok za krokem. Také jsme dříve v tomto tématu zmínili stuby, když jsme uváděli, že systém sestavení C++/WinRT převede obsah vašich souborů .idl do metadat systému Windows a že nástroj s názvem cppwinrt.exe pak z těchto metadat generuje stuby, na nichž pak můžete založit svou implementaci.

Pokaždé, když ve svém IDL něco přidáte, odeberete nebo změníte a provedete sestavení, sestavovací systém aktualizuje implementace stubů v těchto souborech stubů. 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 MainPage.idl souboru, takže byste ho teď měli uložit. Projekt se momentálně nepodaří úspěšně sestavit, ale přesto je užitečné sestavení nyní spustit, protože se tím znovu vygenerují stub soubory pro MainPage. Proto teď sestavte projekt a ignorujte všechny chyby sestavení.

Pro tento projekt C++/WinRT se soubory stub generují ve složce \Clipboard\Clipboard\Generated Files\sources. Najdete je tam poté, co částečné sestavení skončí (opět podle očekávání se sestavení úplně nezdaří. Krok, který nás ale zajímá – generování stubů – však proběhne úspěšně). Soubory, které nás zajímají, jsou MainPage.h a MainPage.cpp.

V těchto dvou souborech se zástupnými implementacemi uvidíte nové zástupné implementace členů MainPage, které jsme přidali do IDL (například Current a FEATURE_NAME). Tyto zástupné implementace budete potřebovat zkopírovat do souborů MainPage.h a MainPage.cpp, které už jsou v projektu. Stejně jako jsme to udělali s IDL, odebereme z těchto existujících souborů zástupné členy Mainpage, které nám šablona projektu Visual Studio dala (fiktivní vlastnost s názvem MyProperty a obslužná rutina události s názvem ClickHandler).

Ve skutečnosti je jediným členem současné verze MainPage, který chceme zachovat, konstruktor.

Jakmile zkopírujete nové členy ze souborů stub, odstraníte členy, které nechcete, a aktualizujete obor názvů, měly by soubory MainPage.h a MainPage.cpp ve vašem projektu vypadat jako výpisy kódu uvedené níže. Všimněte si, že existují dva typy MainPage . Jeden v jmenném prostoru implementation a druhý v jmenném prostoru factory_implementation. Jedinou změnou, kterou jsme provedli u factory_implementation, je přidání SDKTemplate do oboru názvů.

// 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 pomocí String a když nám nástroj cppwinrt.exe vygeneruje kód C++/WinRT, použije typ winrt::hstring. Kdykoli v kódu C# narazíme na řetězec, převedeme jej na winrt::hstring. Další informace najdete v tématu Zpracování řetězců v C++/WinRT.

Vysvětlení const& parametrů v podpisech metody najdete v tématu Předávání parametrů.

Aktualizujte všechny zbývající deklarace a odkazy na obor názvů a zkompilujte sestavení

Před sestavením projektu C++/WinRT vyhledejte všechny deklarace oboru názvů Clipboard (a odkazy na něj) a změňte je na SDKTemplate.

  • MainPage.xaml a App.xaml. Obor názvů se objevuje v hodnotách atributů x:Class a xmlns:local.
  • App.idl.
  • App.h.
  • App.cpp. Existují dvě direktivy using namespace (vyhledejte podřetězec using namespace Clipboard) a dvě kvalifikace typu MainPage (vyhledejte Clipboard::MainPage). Ty se potřebují změnit.

Vzhledem k tomu, že jsme z MainPage odebrali obslužnou rutinu události, přejděte také do MainPage.xaml a odstraňte ze značek prvek Button.

Uložte všechny soubory. Vyčistěte řešení (sestavit>čisté řešení) a pak ho sestavte. Po dokončení všech dosud provedených změn, přesně tak, jak je napsané, se očekává, že sestavení bude úspěšné.

Implementujte členy MainPage, které jsme deklarovali v IDL

Konstruktor, Aktuální a 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 MainPage.xaml znovu používat v celé jeho podobě (jeho zkopírováním). Prozatím (níže) dočasně přidáme do projektu C++/WinRT element TextBlock s odpovídajícím názvem MainPage.xaml .

FEATURE_NAME je statické pole MainPage (pole jazyka C# const je v podstatě statické ve svém chování), definované v SampleConfiguration.cs. V jazyce C++/WinRT to místo (statického) pole vyjádříme jako C++/WinRT výraz (statické) vlastnosti jen pro čtení. Způsob vyjádření getteru vlastnosti C++/WinRT je funkce, která vrací hodnotu vlastnosti a nepřijímá žádné parametry (přístup). Takže statické pole FEATURE_NAME v jazyce C# se stane statickou přístupovou funkcí FEATURE_NAME v jazyce C++/WinRT (v tomto případě vracející řetězcový literál).

Pokud bychom portovali vlastnost jen pro čtení v jazyce C#, udělali bychom to samé. U zapisovatelné vlastnosti v jazyce C# se v C++/WinRT setter vlastnosti vyjadřuje jako funkce void, která jako parametr přebírá hodnotu vlastnosti (měnič). V obou případech platí, že pokud je pole nebo vlastnost v jazyce C# statická, pak je statická také přístupová metoda a/nebo metoda pro zápis v C++/WinRT.

Current je statické pole (nikoli konstanta) 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í, aktuální 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ím statickém poli s názvem current, definujeme nebo inicializujeme aktuálníMainPage.cpp hodnotu (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) s názvem SampleConfiguration.cpp.

Upravte MainPage.xaml, MainPage.h, MainPage.cpp a SampleConfiguration.cpp tak, aby odpovídaly položkám v seznamu níže.

<!-- 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 funkce z MainPage.cppMainPage::Current() a MainPage::FEATURE_NAME(), protože teď tyto metody definujeme 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 slouží k implementaci funkcí, které jste zpřístupnili ve svém projektovaném typu. Vzhledem k tomu, že deklarace MainPage::current (in MainPage.h) se zobrazí v oboru názvů implementace (winrt::SDKTemplate::implementation), nekvalifikovaná MainPage by odkazovala na typ implementace. Proto použijeme kvalifikaci SDKTemplate::, aby bylo jasné, že MainPage::current má být instancí promítaného typu winrt::SDKTemplate::MainPage.

V konstruktoru je několik bodů souvisejících s MainPage::current = *this;, které si zaslouží vysvětlení.

  • Pokud použijete this ukazatel uvnitř člena typu implementace, this ukazatel je samozřejmě ukazatel na typ implementace.
  • Chcete-li ukazatel this převést na odpovídající projektovaný typ, dereferencujte ho. 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 Vytváření instancí a vracení typů implementace a rozhraní.

Také v konstruktoru je SampleTitle().Text(FEATURE_NAME());. Tato SampleTitle() část je volání jednoduché funkce přístupového objektu s názvem SampleTitle, která vrací TextBlock , který jsme přidali do XAML. Kdykoli prvek XAML x:Name pojmenujete, kompilátor XAML pro vás vygeneruje přístupový prvek, který je pojmenovaný podle tohoto prvku. Část .Text(...) volá mutátor Text objektu TextBlock, který vrátil akcesor SampleTitle. A FEATURE_NAME() volá naši statickou přístupovou funkci MainPage::FEATURE_NAME, aby vrátila řetězcový literál. Celkem tento řádek kódu nastavuje vlastnost Text u prvku TextBlock s názvem SampleTitle.

Všimněte si, že protože jsou řetězce ve prostředí Windows Runtime širokoznakové, při portování řetězcového literálu k němu přidáváme předponu pro širokoznakové kódování L. Proto změníme (například) "řetězcový literál" na L"řetězcový literál". Viz také literály širokých řetězců.

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 naší předchozí analýzy víme, že tato kolekce objektů typu Scenario se zobrazuje v prvku ListBox. V jazyce C++/WinRT existují omezení pro 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:

V případě IInspectable, pokud prvky samy o sobě nejsou třídami modulu Runtime, musí být takového druhu, aby je bylo možné zabalit na IInspectable a z něj rozbalit. To znamená, že to musí být typy prostředí Windows Runtime (viz Boxing a rozbalování hodnot do IInspectable).

V této případové studii jsme z Scenario neudělali třídu runtime. To je ale stále rozumná možnost. A budou i případy ve vaší vlastní práci na portování, kdy bude runtime třída jednoznačně správnou volbou. Pokud například potřebujete, aby byl typ elementu pozorovatelný (viz ovládací prvky XAML, vazba na vlastnost C++/WinRT), nebo pokud prvek potřebuje mít metody z jakéhokoli jiného důvodu a je to více než jen sada datových členů.

Vzhledem k tomu, že v tomto návodu nebudeme pro typ scénáře používat třídu modulu runtime, musíme se zamyslet nad boxováním. Pokud bychom vytvořili scénář v běžném jazyce C++ struct, pak bychom ho nemohli zavést do rámečku. Ale deklarovali jsme Scenario v IDL jako struct, a proto ho můžeme zabalit.

Zbývá nám buď předem zabalit Scenario, nebo počkat, dokud je nebudeme chtít přiřadit do ItemsSource, a zabalit je až v ten okamžik. Tady jsou některé aspekty těchto dvou možností.

  • Boxování předem. U této možnosti je naším datovým členem kolekce objektů IInspectable připravená k přiřazení do uživatelského rozhraní. Při inicializaci zabalíme objekty Scenario do tohoto datového členu. Potřebujeme jenom jednu kopii této kolekce, ale pokaždé, když potřebujeme číst její pole, musíme prvek rozbalit.
  • Balení právě včas. U této možnosti naše datová položka představuje kolekci Scenario. Když přijde čas přiřadit je k uživatelskému rozhraní, zabalíme objekty Scenario z datového členu do nové kolekce typu IInspectable. Pole prvků v datovém členu můžeme číst bez rozbalení, ale potřebujeme dvě kopie kolekce.

Jak můžete vidět, u takto malé kolekce se výhody a nevýhody víceméně vyrovnají. Takže v této případové studii zvolíme variantu just-in-time.

Člen scénáře je pole MainPage definované a inicializované v SampleConfiguration.cs. A Scenarios je vlastnost třídy MainPage jen pro čtení, definovaná v MainPage.xaml.cs (a implementovaná tak, že jednoduše vrací pole scenarios). 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 scenariosInner a scenarios, v tomto pořadí. Deklarujeme scenariosInner v MainPage.h. A protože má statickou dobu uložení, budeme ho definovat/inicializovat v souboru .cpp (SampleConfiguration.cpp, v tomto případě).

Upravte MainPage.h a SampleConfiguration.cpp tak, aby odpovídaly níže uvedeným seznamů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.cppMainPage::scenarios(), protože teď tuto metodu definujeme v souboru hlaviček.

Jak vidíte, v SampleConfiguration.cpp inicializujeme statický datový člen scenariosInner 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 rozhraní IObservableVector. Vzhledem k tomu, že v této ukázce není kolekce pozorovatelná (nemusí být, protože po inicializaci nepřidává 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 ovládacích prvcích XAML, vazbách ke kolekci C++/WinRT a kolekcích pomocí C++/WinRT.

Inicializační kód, který jste právě přidali, odkazuje na typy, které v projektu ještě nejsou (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++>Blank Page (C++/WinRT) (ujistěte se, že jde o šablonu položky Blank Page (C++/WinRT), a ne o Blank Page). 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 CopyImageXAML , CopyFiles, HistoryAndRoaminga OtherScenarios.

Teď budete moct znovu sestavit, pokud chcete.

NotifyUser

V projektu jazyka C# najdete implementaci metody MainPage.NotifyUser v MainPage.xaml.cssouboru . MainPage.NotifyUser má závislost na MainPage.UpdateStatus a tato metoda zase obsahuje závislosti na elementech XAML, které jsme ještě nepřevedli. Prozatím tedy budeme v projektu C++/WinRT zasuňovat metodu UpdateStatus a později ji portujeme.

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 odesílá aktualizace uživatelského rozhraní do hlavního vlákna. Ve WinUI 3 se používá Microsoft. UI. Dispatching.DispatcherQueue místo starších CoreDispatcher. V C++/WinRT platí, že kdykoli chcete použít typ z oblasti názvů Windows nebo Microsoft, musíte zahrnout odpovídající hlavičkový soubor oblasti názvů C++/WinRT (další informace o tom najdete v článku Začínáme s C++/WinRT). V tomto případě, jak uvidíte v následujícím výpisu kódu, hlavička je winrt/Microsoft.UI.Dispatching.ha my ji zahrneme do pch.h.

UpdateStatus je privátní. Proto vytvoříme privátní metodu u našeho typu implementace MainPage . UpdateStatus nemá být volána ve třídě runtime, takže ji nebudeme deklarovat v IDL.

Po přenesení MainPage.NotifyUser a vytvoření zástupné implementace pro MainPage.UpdateStatus máme v projektu C++/WinRT toto. Po tomto výpisu kódu se podíváme na některé podrobnosti.

// 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();
}
...

V jazyce C# můžete pomocí tečkové notace přistupovat k vnořeným vlastnostem. Typ MainPage jazyka C# má tedy přístup k vlastní vlastnosti Dispatcher se syntaxí Dispatcher. A jazyk C# může k této hodnotě dále přistupovat pomocí tečkové notace se syntaxí, 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.

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

Pokud verze NotifyUser volá Dispatcher.RunAsync, ekvivalent WinUI 3 používá DispatcherQueue.TryEnqueue. Verze C++/WinRT implementuje delegáta zpětného volání jako funkci lambda. V jazyce C++/WinRT zachytíme dva parametry, které budeme používat, a také ukazatel this (protože budeme volat členskou funkci). Další informace o implementaci delegátů jako lambda a příkladů kódu najdete v tématu Zpracování událostí pomocí delegátů v C++/WinRT.

Implementace zbývajících členů MainPage

Pojďme vytvořit úplný seznam členů MainPage (implementovaných v MainPage.xaml.cs a SampleConfiguration.cs), abychom viděli, které z nich jsme už přenesli a které ještě zbývá přenést.

Člen Access Status
Konstruktor MainPage public Převedeno
Aktuální vlastnost public Přenesen
FEATURE_NAME vlastnost public Přenesen
IsClipboardContentChangedEnabled – vlastnost public Nezahájeno
Vlastnost scénářů public Převedeno
Metoda BuildClipboardFormatsOutputString public Nezahájeno
Metoda DisplayToast public Nezahájeno
Metoda EnableClipboardContentChangedNotifications public Nezahájeno
Metoda NotifyUser public Portováno
Metoda OnNavigatedTo protected Nezahájeno
isApplicationWindowActive pole private Nezahájeno
pole needToPrintClipboardFormat private Nezahájeno
Pole scénářů private Portováno
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 Nahrazeno zástupnou implementací

V několika následujících pododdílech si tedy promluvíme o členech, které ještě nebyly portovány.

Note

Čas od času 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. Projekt se tak bude nadále sestavovat po každém pododdílu. Alternativou je vyřešit odkazy zkopírováním celého obsahu MainPage.xaml projektu C# do projektu C++/WinRT. Ale pokud to uděláme, potrvá dlouho, než se budeme moct na chvíli zastavit a znovu provést build (a tím případně zakrýt překlepy nebo jiné chyby, kterých se cestou dopustíme).

Jakmile dokončíme přenos imperativního kódu pro třídu MainPage , zkopírujeme obsah souboru XAML a budeme mít jistotu, že projekt bude i nadále sestavovat.

IsClipboardContentChangedEnabled

Jedná se o vlastnost jazyka C# typu get/set, jejíž výchozí hodnota je false. Je členem MainPage a je definován v SampleConfiguration.cs.

Pro C++/WinRT budeme potřebovat přístupovou funkci, nastavovací funkci a podkladový datový člen v podobě pole. Vzhledem k tomu, že IsClipboardContentChangedEnabled představuje stav jednoho ze scénářů v ukázce, a nikoli stav samotné hlavní stránky , vytvoříme nové členy na novém typu nástroje s názvem SampleState. A my ho implementujeme v našem SampleConfiguration.cpp souboru zdrojového kódu a vytvoříme členy static (protože potřebujeme pouze jednu instanci v celé aplikaci; a abychom k nim měli přístup, aniž bychom potřebovali instanci třídy).

Chcete-li doplnit náš SampleConfiguration.cpp v projektu C++/WinRT, přidejte novou položku Visual C++>Code> hlavičkového souboru Header File (.h) s názvem SampleConfiguration.h. Upravte SampleConfiguration.h a SampleConfiguration.cpp tak, aby odpovídaly níže uvedeným seznamů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 .cpp soubor je pro to vhodným místem (SampleConfiguration.cpp v tomto případě).

BuildClipboardFormatsOutputString

Tato metoda je veřejným členem MainPage a 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 C++/WinRT vytvoříme BuildClipboardFormatsOutputString veřejnou statickou metodu SampleState. Můžeme z toho udělat static, protože nepřistupuje k žádným členům instance.

Abychom mohli v C++/WinRT použít typy Clipboard a DataPackageView, budeme muset zahrnout hlavičkový soubor pro obor názvů Windows v C++/WinRT winrt/Windows.ApplicationModel.DataTransfer.h.

V jazyce C# je vlastnost DataPackageView.AvailableFormats typu IReadOnlyList, takže můžeme použít její vlastnost Count. V jazyce C++/WinRT vrací přístupová funkce DataPackageView::AvailableFormats objekt IVectorView, který má přístupovou funkci Size, kterou můžeme volat.

K portování použití typu C# System.Text.StringBuilder použijeme standardní typ C++ std::wostringstream. Tento typ je výstupní proud pro široké znakové řetězce (a abychom jej mohli použít, budeme muset vložit hlavičkový soubor sstream). Namísto použití metody Append, jako když pracujete s StringBuilder, použijete operátor vložení (<<) s výstupním proudem, například wostringstream. Další informace najdete v článcích programování s iostream a Formátování řetězců C++/WinRT.

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. var přenesete do auto v C++/WinRT. V jazyce C++/WinRT však existují případy, kdy (aby se zabránilo kopiím), chcete odkaz na odvozený (nebo vyvolaný) typ a vy vyjadřujete odkaz lvalue na vyvolaný typ s auto&. Existují také případy, kdy chcete speciální druh odkazu, který se správně naváže bez ohledu na to, zda je inicializován pomocí lvalue nebo rvalue. A to vyjádříte pomocí auto&&. To je podoba, kterou vidíte použitou ve smyčce for v níže uvedeném portovaném kódu. Úvod k lvalue a rvalue naleznete v tématu Kategorie hodnot a odkazy na ně.

Upravte pch.h, SampleConfiguration.h a SampleConfiguration.cpp tak, aby odpovídaly níže uvedeným položká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() };
}

Note

Syntaxe na řádku kódu DataPackageView clipboardContent{ Clipboard::GetContent() }; používá funkci moderního standardního jazyka C++ označovanou jako jednotná inicializace s charakteristickým použitím složených závorek místo znaménka = . 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í), můžete nahradit výše uvedenou syntaxi 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.

Zobrazit oznámení

DisplayToast je veřejná statická metoda třídy C# MainPage 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. Jedna nová věc, kterou je třeba zmínit, je, že doslovný řetězcový literál jazyka C# (@) převedete na standardní řetězcový nezpracovaný literál jazyka C++ (LR).

Pokud také odkazujete na typy ToastNotification a XmlDocument v jazyce C++/WinRT, můžete je buď kvalifikovat podle názvu oboru názvů, nebo můžete upravit SampleConfiguration.cpp a přidat using namespace direktivy, jako je následující příklad.

using namespace Windows::UI::Notifications;

Při odkazech na typ XmlDocument máte stejnou volbu a vždy, když odkazujete na jakýkoli jiný typ prostředí Windows Runtime.

Kromě těchto položek se při provádění následujících kroků řiďte stejnými pokyny jako dříve.

  • Deklarujte metodu v SampleConfiguration.ha definujte ji v SampleConfiguration.cpp.
  • Upravte pch.h, aby obsahoval všechny nezbytné hlavičkové soubory oboru názvů Windows pro C++/WinRT.
  • Vytvářejte objekty C++/WinRT na zásobníku, ne na haldě.
  • Nahraďte volání vlastností get accessors syntaxí volání funkce (()).

Velmi častou příčinou chyb kompilátoru/linkeru je, že zapomenete zahrnout soubory záhlaví oboru názvů Windows pro C++/WinRT, které potřebujete. Další informace o jedné možné chybě najdete v tématu C3779: Proč mi kompilátor dává chybu "consume_Something: funkce, která vrací "auto" nelze použít dříve, než je definována?.

Pokud chcete postupovat podle tohoto návodu a sami převést DisplayToast, můžete své výsledky porovnat s kódem ve verzi C++/WinRT v archivu ZIP se zdrojovým kódem ukázky Clipboard sample, který jste si stáhli.

Povolit oznámení o změně obsahu schránky

EnableClipboardContentChangedNotifications je veřejná statická metoda třídy C# MainPage a je definována 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žíváte syntaxi operátorů += a -= k registraci a zrušení registrace delegátů obsluhy 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. Chcete-li se zaregistrovat, předáte svého delegáta registrační funkci a na oplátku obdrží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 můžete vidět v následující ukázce kódu) syntaxe volání funkce je jednoduchá.

Podobné tokeny se ve skutečnosti používají na pozadí v jazyce C#. Ale jazyk činí tento detail implicitním. C++/WinRT to výslovně uvádí.

Typ objektu se zobrazí v podpisech obslužné rutiny události jazyka C#. V jazyce C# je objektaliasem pro typ .NET System.Object. Ekvivalentem v jazyce C++/WinRT je winrt::Windows::Foundation::IInspectable. V obslužných rutinách událostí C++/WinRT se tedy zobrazí IInspectable .

Upravte SampleConfiguration.h a SampleConfiguration.cpp tak, aby odpovídaly níže uvedeným seznamů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, 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){}

Samotné delegáty pro zpracování událostí (OnClipboardChanged a OnWindowActivated) zatím ponechte jako prázdné metody. Už jsou na našem seznamu položek určených k přenesení, takže se jim budeme věnovat v pozdějších pododdílech.

OnNavigatedTo

OnNavigatedTo je chráněná metoda třídy C# MainPage a je definována v MainPage.xaml.cs. Zde je spolu 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 zde je k uživatelskému rozhraní přiřazena naše kolekce objektů scénáře . Kód jazyka C# vytvoří seznam System.Collections.Generic.List objektů Scenario a přiřadí jej vlastnosti ItemsSource prvku ListBox (což je ovládací prvek pro položky). A v jazyce C# použijeme interpolaci řetězců k sestavení názvu pro každý objekt scénáře (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 element ListBox stub, 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(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);
    }
}
...

Znovu voláme funkci winrt::single_threaded_observable_vector , tentokrát ale vytvoříme kolekci IInspectable. To bylo součástí rozhodnutí, které jsme přijali, boxovat naše objekty Scenario právě v okamžiku potřeby.

A místo použití interpolace řetězců v jazyce C# zde používáme kombinaci funkce to_hstring a operátoru zřetězeníwinrt::hstring.

isApplicationWindowActive

V jazyce C# isApplicationWindowActive je jednoduché soukromé bool pole patřící do třídy MainPage a je definováno v SampleConfiguration.cs. Výchozí hodnota je false. V jazyce C++/WinRT ho nastavíme jako soukromé statické pole SampleState (z důvodů, proč jsme už popsali) v SampleConfiguration.h souborech a SampleConfiguration.cpp souborech se stejným výchozím nastavením.

Už jsme viděli, jak deklarovat, definovat a inicializovat statické pole. Pro připomenutí se vraťte k tomu, co jsme udělali s polem isClipboardContentChangedEnabled, a udělejte totéž s polem isApplicationWindowActive.

needToPrintClipboardFormat

Stejný vzor jako isApplicationWindowActive (viz nadpis bezprostředně před tímto vzorem).

Button_Click

Button_Click je privátní metoda (zpracování událostí) třídy C# MainPage a je definována v MainPage.xaml.cs. Zde je, spolu s prvkem XAML SplitView, na který odkazuje, a s prvkem 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 public obslužná rutina události (jak vidíte, deklarujete ji předprivate:deklaracemi). 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), pak 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, 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

V jazyce C# je DisplayChangedFormats soukromou metodou patřící do třídy MainPage a je definována v SampleConfiguration.cs.

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

V C++/WinRT z něj v souborech SampleConfiguration.h a SampleConfiguration.cpp uděláme privátní statické pole třídy SampleState (protože nepřistupuje k žádným členům instance). Kód v jazyce C# pro tuto metodu nepoužívá System.Text.StringBuilder; ale provádí dost formátování řetězců, takže ve verzi C++/WinRT je to další vhodné místo k použití std::wostringstream.

Místo statické vlastnosti System.Environment.NewLine , která se používá v kódu jazyka C#, vložíme do výstupního datového proudu standardní znak C++ std::endl (znak nového řádku).

// 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. A převede svůj stream na winrt::hstring a vrátí jej. Zavoláme funkci hstring::c_str , která vrátí hstring zpět na řetězec ve stylu jazyka C, a pak tuto funkci vložíme do našeho streamu. Bylo by efektivnější vytvořit jen jeden std::wostringstream a předávat ho dál (resp. odkaz na něj), aby do něj metody mohly přímo vkládat řetězce.

Přesně to děláme ve verzi C++/WinRT zdrojového kódu Clipboard sample (v souboru ZIP, který jste si 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 metody SampleState::D isplayChangedFormats a SampleState::BuildClipboardFormatsOutputString jsou refaktorovány pro volání této nové metody. Funkčně odpovídá výpisům kódu v tomto tématu, ale je efektivnější.

Footer_Click je asynchronní obslužná rutina událostí patřící do třídy C# MainPage a je definována 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 rozepsal z jednoho řádku do čtyř, aby bylo snazší vidět, co dělá, a v důsledku toho i to, jak bychom ho měli portovat.

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);
}

Ačkoli je metoda z technického hlediska asynchronní, po await už nic nedělá, takže nepotřebuje await (ani klíčové slovo async). Pravděpodobně je používá, aby se zabránilo zprávě IntelliSense v 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 asynchronních objektech najdete v tématu Souběžnost a asynchronní operace s C++/WinRT.

Teď si promluvme o tom, co metoda dělá. Protože se jedná o obslužnou rutinu události Click prvku HyperlinkButton, objekt s názvem sender je ve skutečnosti prvkem typu 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 object, můžeme ji v jazyce C# převést na řetězec pomocí Object.ToString. Z výsledného řetězce vytvoříme objekt URI . A nakonec (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, 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);
}

Jako vždy vytvoříme obslužnou rutinu události public. Používáme funkci as u objektu sender, abychom ho přetypovali na HyperlinkButton. V jazyce C++/WinRT je vlastnost TagIInspectable (ekvivalent objektu). Ale na IInspectable není žádný tostring. Místo toho musíme rozbalit IInspectable na skalární hodnotu (v tomto případě řetězec). Další informace o boxování a rozbalování najdete v tématu Boxing a unboxing values to IInspectable.

Poslední dva řádky opakují vzorce portování, které jsme už viděli dříve, a v podstatě odpovídají verzi v jazyce C#.

HandleClipboardChanged

Přenesení této metody nepřináší nic nového. Můžete porovnat verze C# a C++/WinRT v archivu ZIP se zdrojovým kódem Clipboard sample, který jste stáhli.

OnClipboardChanged a OnWindowActivated

Zatím máme pro tyto dvě obslužné rutiny událostí pouze prázdné kostry. Ale jejich portování je jednoduché a nezpůsobí nic nového, co by se mělo diskutovat.

ScenarioControl_SelectionChanged

Toto je další privátní obslužná rutina události patřící do třídy C# MainPage a definována v MainPage.xaml.cs. V jazyce C++/WinRT ho zveřejníme a implementujeme v MainPage.h a MainPage.cpp.

Pro tuto metodu budeme potřebovat MainPage::navigace, což je privátní logické pole inicializované na false. A budete potřebovat rámeček s MainPage.xamlnázvem ScenarioFrame. Kromě těchto podrobností ale portování této metody odhalí žádné nové techniky.

Pokud místo ručního portování kopírujete kód z verze C++/WinRT v ZIP archivu zdrojového kódu ukázky Clipboard sample, který jste si stáhli, pak tam uvidíte použití MainPage::NavigateTo. Prozatím stačí refaktorovat obsah NavigateTo na ScenarioControl_SelectionChanged.

UpdateStatus

Zatím máme pro MainPage.UpdateStatus pouze stub. Přenesení implementace opět z velké části pokrývá již známé téma. Jedním z nových bodů na vědomí je, že zatímco v C# můžeme porovnat řetězec s String.Empty, V C++/WinRT místo toho voláme winrt::hstring::empty function. Další věc je, že nullptr je standardní ekvivalent v jazyce C++ k null z jazyka C#.

Zbývající část portu můžete provést pomocí technik, které jsme už probrali. Tady je seznam věcí, které budete muset udělat, než bude možné portovanou verzi této metody zkompilovat.

  • Do MainPage.xaml přidejte prvek Border s názvem StatusBorder.
  • Do MainPage.xaml přidejte TextBlock s názvem StatusBlock.
  • Do MainPage.xaml přidejte element StackPanel s názvem StatusPanel.
  • Do pch.h, přidat #include "winrt/Microsoft.UI.Xaml.Media.h".
  • Do pch.h, přidat #include "winrt/Microsoft.UI.Xaml.Automation.Peers.h".
  • Chcete-li MainPage.cpp přidat using namespace winrt::Microsoft::UI::Xaml::Media;.
  • Chcete-li MainPage.cpp přidat using namespace winrt::Microsoft::UI::Xaml::Automation::Peers;.

Zkopírujte XAML a styly potřebné k dokončení portu MainPage.

V případě XAML je ideálním případem použití stejných značek XAML v jazyce C# a projektu C++/WinRT. A ukázka schránky je jedním z těchto případů.

Ukázka schránky obsahuje ve svém souboru Styles.xaml slovník stylů XAML ResourceDictionary, který se používá pro tlačítka, nabídky a další prvky napříč uživatelským rozhraním 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. Teď 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 \Clipboard_sample\SharedContent\xaml složky, vyberte a zkopírujte App.xaml a MainPage.xamlvložte tyto dva soubory do \Clipboard\Clipboard složky v projektu C++/WinRT a po zobrazení výzvy zvolte nahrazení souborů.

V projektu C++/WinRT v Visual Studio klikněte na Zobrazit všechny soubory a zapněte ho. Nyní přidejte novou složku, okamžitě pod uzlem 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.xaml a vložte ji do složky \Clipboard\Clipboard\Styles, kterou jste právě vytvořili. Zpět v Průzkumník řešení v projektu C++/WinRT klikněte pravým tlačítkem na Styles složku >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 Stylesji . Přejděte do složky \Clipboard_sample\SharedContent\xaml, vyberte položku Styles.xaml, zkopírujte ji a vložte ji do složky \Clipboard\Clipboard\Styles v projektu C++/WinRT. Klikněte pravým tlačítkem myši na Styles složku (v Průzkumník řešení v projektu C++/WinRT) >Přidejte>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.

Teď jsme dokončili portování MainPage a pokud jste postupovali podle kroků, projekt C++/WinRT se teď sestaví a spustí.

Sloučit vaše .idl soubory

Kromě standardního MainPage.xaml výchozího bodu uživatelského 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ěkolika následujících hlavních částech se podíváme na to, jak převést code-behind. 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 modulu runtime do souborů MIDL (.idl). Dále sloučíme obsah CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idl a OtherScenarios.idl tak, že toto IDL přesuneme do jediného souboru s názvem Project.idl (a potom odstraníme původní soubory).

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 do projektu C++/WinRT přidejte 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 : 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();
    }
}

Jak vidíte, je to jen kopie obsahu jednotlivých souborů .idl, to vše v rámci jednoho oboru názvů a s odstraněným MyProperty z každé běhové třídy.

V okně Průzkumník řešení v aplikaci Visual Studio označte všechny původní soubory IDL (CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idl a OtherScenarios.idl) a pomocí Upravit>Odebrat je odeberte (v dialogovém okně zvolte Odstranit).

Nakonec — a aby bylo odebrání MyProperty dokončeno — v souborech .h a .cpp pro každý z těchto pěti typů stránek XAML odstraňte deklarace a definice přístupových funkcí int32_t MyProperty() a mutátorových funkcí void MyProperty(int32_t).

Mimochodem, vždy je vhodné, aby název souborů XAML odpovídal názvu třídy, kterou představují. Pokud máte x:Class="MyNamespace.MyPage" například v souboru značek XAML, měl by mít tento soubor název MyPage.xaml. I když to není technický požadavek, pokud nebudete pro stejný artefakt používat různé názvy, váš projekt bude srozumitelnější, lépe udržovatelný a bude se s ním snáze pracovat.

CopyFiles

V projektu C# je typ stránky XAML CopyFiles implementován v souborech zdrojového kódu CopyFiles.xaml a CopyFiles.xaml.cs. Pojďme se postupně podívat na jednotlivé č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 obslužná rutina události a z klíčového async slova v jeho podpisu můžeme zjistit, že metoda provádí asynchronní práci. V jazyce C++/WinRT implementujeme asynchronní metodu jako korutinu. Úvod ke souběžnosti v C++/WinRT společně s popisem korutiny najdete v tématu Souběžnost a asynchronní operace s C++/WinRT.

Je běžné chtít po dokončení korutiny naplánovat další zpracování a v takových případech by korutina vracela určitý typ asynchronního objektu, na který lze čekat a který může volitelně hlásit průběh. Takové úvahy se ale na obslužnou funkci události obvykle nevztahují. Pokud tedy 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 najdete v tématu Fire and Forget.

Ačkoli podstata korutiny typu fire-and-forget spočívá v tom, že vás nezajímá, kdy skončí, práce stále pokračuje na pozadí (nebo je pozastavena a čeká na obnovení běhu). Z implementace v jazyce C# je vidět, že CopyButton_Click závisí na ukazateli this (přistupuje k datovému členu instance rootPage). Proto si musíme být jisti, že ukazatel this (ukazatel na objekt CopyFiles) bude existovat 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 by byla stránka CopyFiles zničena (přechodem mimo ni), zatímco CopyButton_Click je stále v letu na vlákně na pozadí, nebude bezpečný pro přístup rootPage. Aby korutina fungovala správně, musí získat silnou referenci na ukazatel this a uchovat tuto referenci po celou dobu trvání korutiny. Další informace najdete v tématu Silné a slabé odkazy v jazyce C++/WinRT.

Pokud se podíváte do ukázky ve verzi C++/WinRT, v CopyFiles::CopyButton_Click uvidíte, že je to provedeno jednoduchou deklarací na 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 FileOpenPicker objektu a dva řádky později přistupujeme k vlastnosti FileTypeFilter objektu. Návratový typ této vlastnosti implementuje rozhraní IVector pro řetězce. A na tomto IVector voláme metodu IVector<T>.ReplaceAll(T[]). Zajímavé je, jakou hodnotu předáváme této metodě, která 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ů jazyka 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 seznamech standardních inicializátorů.

Klíčové slovo C# await portujeme do co_await 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>, na kterou odkazuje storageItems, na IEnumerable<IStorageItem>, který očekává DataPackage.SetStorageItems. Ale v C++/WinRT musíme explicitně převést z IVectorView<StorageFile> na IIterable<IStorageItem>. A tak tu máme další příklad fungování funkce as.

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

Pokud používáme null klíčové slovo v jazyce C# (například Clipboard.SetContentWithOptions(dataPackage, null)), používáme nullptr v jazyce C++/WinRT (například Clipboard::SetContentWithOptions(dataPackage, nullptr)).

PasteButton_Click

Toto je další obslužná rutina pro událost ve formě korutiny spuštěné bez čekání na dokončení. 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 portu 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 objekt C# je null nebo není if (storageItems != null). V C++/WinRT se můžeme spolehnout na konverzní operátor na bool, který interně provádí test vůči nullptr.

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());

Takové vytvoření objektu std::wstring_view z winrt::hstring ilustruje alternativu k volání funkce hstring::c_str (která převede winrt::hstring na řetězec ve stylu jazyka C). Tato alternativa funguje díky operátoru převodu hstringna std::wstring_view.

Zvažte tento fragment jazyka C#.

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

Při převodu klíčového slova C# as do C++/WinRT jsme zatím viděli funkci as použitou několikrát. Tato funkce vyvolá výjimku, pokud převod typu selže. Pokud ale chceme, aby se 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 ze CopyFiles.xaml složky původního stažení ukázkového zdrojového shared kódu a vložit ho do CopyFiles.xaml souboru v projektu C++/WinRT (nahrazení existujícího obsahu tohoto souboru v projektu C++/WinRT).

Nakonec upravte CopyFiles.h a .cpp odstraňte fiktivní funkci ClickHandler , protože právě přepíšeme odpovídající kód XAML.

Dokončili jsme portování CopyFiles a pokud jste postupovali podle kroků, projekt C++/WinRT se teď sestaví a spustí a scénář CopyFiles bude funkční.

CopyImage

Pokud chcete portovat typ stránky CopyImage XAML, postupujte stejně jako u CopyFiles. Při převádění CopyImage se setkáte s použitím příkazu using v jazyce C#, který zajišťuje, že objekty implementující rozhraní IDisposable budou správně uvolněny.

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 IClosable s 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 především pro výhody jazyků, které nemají deterministické finalizace. C++/WinRT má deterministické finalizace, a proto často nemusíme volat IClosable::Close při psaní C++/WinRT. Ale někdy je lepší s tím skončit, a tohle je jeden z těch případů. Zde je identifikátor imageStream obalem s počítáním odkazů nad podkladovým objektem prostředí Windows Runtime (v tomto případě jde o objekt, který implementuje IRandomAccessStreamWithContentType). I když můžeme zjistit, ž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 bude volat Close. Je to proto, že jsme předali imageStream jiným rozhraním API a ta mohou stále zvyšovat počet referencí podkladového objektu prostředí Windows Runtime. Takže je to případ, kdy je vhodné explicitně volat Zavřít . Další informace najdete v tématu Potřebuji volat IClosable::Close u tříd modulu runtime, které používám?.

Dále zvažte výraz (uint)(imageDecoder.OrientedPixelWidth * 0.5)jazyka C#, který najdete v obslužné rutině události OnDeferredImageRequestedHandler . Tento výraz vynásobí uint číslem double, čímž vznikne 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 CopyImage.OnDeferredImageRequestedHandler v jazyce C# obsahuje klauzuli finally, ale ne klauzuli catch. Ve verzi C++/WinRT jsme přešli o něco dál a implementovali catch jsme klauzuli, 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 CopyFiles je posledním krokem v portu výběr celého obsahu CopyImage.xamla jeho vložení do stejného souboru v projektu C++/WinRT.

Kopírovat text

Můžete portovat CopyText.xaml a CopyText.xaml.cs používat techniky, které jsme už probrali.

Historie aroaming

Při portování typu stránky XAML HistoryAndRoaming se objevuje několik bodů, na které je třeba upozornit.

Nejprve se podívejte na zdrojový kód jazyka C# a sledujte tok řízení z OnNavigatedTo prostřednictvím obslužné rutiny události OnHistoryEnabledChanged a nakonec k asynchronní funkci CheckHistoryAndRoaming (který není očekáván, takže se v podstatě aktivuje a zapomene). Vzhledem k tomu, že CheckHistoryAndRoaming je asynchronní, budeme muset být opatrní v jazyce C++/WinRT o životnosti this ukazatele. Výsledek uvidíte, pokud se podíváte na implementaci v souboru zdrojového HistoryAndRoaming.cpp kódu. Nejprve, když připojíme delegáty k událostem Clipboard::HistoryEnabledChanged a Clipboard::RoamingEnabledChanged, uchováváme 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_weak místo závislosti na ukazateli this . To znamená, že samotný delegát, který nakonec volá asynchronní kód, neudrží stránku HistoryAndRoaming aktivní, pokud z ní přejdeme jinam.

A za druhé, když se konečně dostaneme k naší fire-and-forget korutině CheckHistoryAndRoaming, první, co uděláme, je, že si vezmeme silnou referenci na this, abychom zaručili, že stránka HistoryAndRoaming bude existovat alespoň do chvíle, kdy se korutina definitivně dokončí. Další informace o obou aspektech, které jsme právě popsali, najdete v tématu Silné a slabé odkazy v jazyce C++/WinRT.

Při přenosu CheckHistoryAndRoaming najdeme další bod zájmu. 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é jako první volá obslužnou rutinu události, je hlavní vlákno uživatelského rozhraní. Asynchronní metoda ale obvykle může běžet a/nebo pokračovat na libovolném vlákně. V jazyce C# je řešením odeslat práci do vlákna uživatelského rozhraní. V jazyce C++/WinRT můžeme použít funkci winrt::resume_foreground společně s DispatcherQueue ukazatele this k pozastavení korutiny a jejímu okamžitému pokračování v hlavním vlákně uživatelského rozhraní.

Relevantní výraz je co_await winrt::resume_foreground(DispatcherQueue());. Kratší verze vznikne 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

Můžete portovat OtherScenarios.xaml a OtherScenarios.xaml.cs používat techniky, které jsme už probrali.

Conclusion

Doufáme, že vám tento průvodce poskytl dostatek informací a postupů pro portování, takže nyní můžete přistoupit k portování svých aplikací v C# do C++/WinRT. Pro připomenutí se můžete i nadále vracet k verzím zdrojového kódu before (C#) a after (C++/WinRT) v ukázce Clipboard sample a porovnat je vedle sebe, abyste viděli, jak si odpovídají.