Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym temacie przedstawiono analizę przypadku przenoszenia jednej z przykładów aplikacji platformy uniwersalnej systemu Windows (UWP) z C# do C++/WinRT. Możesz zdobyć praktykę i doświadczenie w przenoszeniu, śledząc instrukcje krok po kroku i przenosząc samodzielnie przykład podczas pracy.
Aby uzyskać kompleksowy wykaz szczegółów technicznych związanych z przenoszeniem do języka C++/WinRT z języka C#, zobacz temat towarzyszący Move to C++/WinRT from C#(Przechodzenie do języka C++/WinRT z języka C#).
Krótki wstęp dotyczący plików kodu źródłowego w języku C# i C++
W projekcie C# pliki kodu źródłowego to przede wszystkim pliki .cs. Po przejściu do języka C++zauważysz, że istnieje więcej rodzajów plików kodu źródłowego, do których można się przyzwyczaić. Przyczyną jest różnica między kompilatorami, ponowne użycie kodu źródłowego C++, oraz pojęcia deklarowania i definiowania typu i jego funkcji (metod).
Deklaracja funkcji opisuje tylko sygnaturę funkcji (jej typ zwracany, jej nazwę i jej typy oraz nazwy parametrów). Definicja funkcji zawiera ciało funkcji (jej implementację).
Jest to trochę inne, jeśli chodzi o typy. zdefiniować typ, podając jego nazwę i przez (co najmniej) deklarując wszystkich jego funkcji członkowskich (i innych elementów członkowskich). To prawda, można zdefiniować typ, nawet jeśli nie zdefiniujesz jego funkcji składowych.
- Typowe pliki kodu źródłowego języka C++ to
.h(kropka) i.cpppliki. Plikjest plikiem nagłówka i definiuje co najmniej jeden typ. Chociaż można zdefiniować funkcje członkowskie w nagłówku, zazwyczaj jest to plik .cpp. W przypadku hipotetycznego typu C++ MyClass należy zdefiniować klasę MyClass wMyClass.hpliku i zdefiniować jej funkcje członkowskie w plikuMyClass.cpp. Aby inni deweloperzy mogli ponownie używać klas, należy udostępnić tylko.hpliki i kod obiektu. Zachowasz pliki.cppw tajemnicy, ponieważ implementacja stanowi twoją własność intelektualną. - Prekompilowany nagłówek (
pch.h). Zazwyczaj w aplikacji znajduje się zestaw plików nagłówkowych, które są uwzględniane w aplikacji, a te pliki nie są często zmieniane. Dlatego zamiast przetwarzać zawartość tego zestawu nagłówków za każdym razem, gdy kompilujesz, można agregować te nagłówki w jednym pliku, skompilować je raz, a następnie użyć danych wyjściowych tego kroku prekompilacji za każdym razem, gdy tworzysz. Można to zrobić za pomocą wstępnie skompilowanego pliku nagłówka(zazwyczaj o nazwie ). -
.idlpliki. Te pliki zawierają język IDL (Interface Definition Language). IDL można myśleć jak o plikach nagłówkowych dla typów środowiska uruchomieniowego Windows. Więcej informacji na temat języka IDL znajdziesz w sekcjiIDL dla typu .MainPage
Pobieranie i testowanie przykładu Schowka
Odwiedź stronę internetową przykładowej witryny schowka, a następnie kliknij Pobierz plik ZIP. Rozpakuj pobrany plik i przyjrzyj się strukturze folderów.
- Wersja przykładowego kodu źródłowego w języku C# znajduje się w folderze o nazwie
cs. - Wersja C++/WinRT przykładowego kodu źródłowego znajduje się w folderze o nazwie
cppwinrt. - Inne pliki — używane zarówno przez wersję języka C#, jak i wersję C++/WinRT — można znaleźć w
sharedfolderach i .SharedContent
Przewodnik w tym temacie pokazuje, jak odtworzyć wersję przykładu Schowka w C++/WinRT, portując go z kodu źródłowego w języku C#. Dzięki temu możesz zobaczyć, jak można przenosić własne projekty języka C# do języka C++/WinRT.
Aby dowiedzieć się, co robi przykład, otwórz rozwiązanie języka C# (\Clipboard_sample\cs\Clipboard.sln), zmień konfigurację zgodnie z potrzebami (na przykład na x64), skompiluj i uruchom program. Własny interfejs użytkownika (UI) przykładu przeprowadzi Cię przez różne funkcje krok po kroku.
Wskazówka
Folder główny pobranego przykładu może mieć nazwę Clipboard zamiast Clipboard_sample. Jednak nadal będziemy odwoływać się do tego folderu Clipboard_sample , aby odróżnić go od wersji C++/WinRT, którą utworzysz w późniejszym kroku.
Utwórz pustą aplikację (C++/WinRT) o nazwie Schowek
Uwaga / Notatka
Aby uzyskać informacje na temat instalowania i używania rozszerzenia C++/WinRT Visual Studio (VSIX) i pakietu NuGet (które razem zapewniają obsługę szablonu projektu i kompilacji), zobacz obsługa programu Visual Studio dla języka C++/WinRT.
Rozpocznij proces przenoszenia, tworząc nowy projekt C++/WinRT w programie Microsoft Visual Studio. Utwórz nowy projekt przy użyciu szablonu projektu Blank App (C++/WinRT). Uzupełnij nazwę na Schowek, a (aby struktura folderów pasowała do instrukcji) upewnij się, że opcja Umieść rozwiązanie i projekt w tym samym katalogu jest niezaznaczona.
Aby uzyskać punkt odniesienia, upewnij się, że ten nowy, pusty projekt buduje się i uruchamia.
Package.appxmanifest i pliki zasobów
Jeśli wersje języka C# i C++/WinRT przykładu nie muszą być instalowane obok siebie na tym samym komputerze, pliki źródłowe manifestu pakietu aplikacji dwóch projektów (Package.appxmanifest) mogą być identyczne. W takim przypadku możesz po prostu skopiować Package.appxmanifest z projektu C# do projektu C++/WinRT i wszystko gotowe.
Aby dwie wersje przykładu współistnieły, potrzebują różnych identyfikatorów. W takim przypadku w projekcie C++/WinRT otwórz plik Package.appxmanifest w edytorze XML i zanotuj te trzy wartości.
- Wewnątrz elementu /Package/Identity zanotuj wartość atrybutu Name . Jest to nazwa pakietu. W przypadku nowo utworzonego projektu projekt da mu początkową wartość unikatowego identyfikatora GUID.
- Wewnątrz elementu
/Package/Applications/Application zanotuj wartość atrybutu identyfikatora. Jest to identyfikator aplikacji. - Wewnątrz elementu /Package/mp:PhoneIdentity zanotuj wartość atrybutu PhoneProductId . Ponownie w przypadku nowo utworzonego projektu zostanie on ustawiony na ten sam identyfikator GUID, na który ustawiono nazwę pakietu.
Następnie skopiuj Package.appxmanifest z projektu C# do projektu C++/WinRT. Na koniec możesz przywrócić trzy zanotowane wartości. Możesz też edytować skopiowane wartości, aby były unikatowe i/lub odpowiednie dla aplikacji i dla organizacji (zwykle w przypadku nowego projektu). Na przykład w tym przypadku zamiast przywracać wartość nazwy pakietu, możemy po prostu zmienić skopiowaną wartość z Microsoft.SDKSamples.Clipboard.CS na Microsoft.SDKSamples.Clipboard.CppWinRT. Możemy pozostawić identyfikator aplikacji ustawiony na App. Jeśli nazwa pakietu lub identyfikator aplikacji są różne, obie aplikacje będą mieć różne identyfikatory modelu użytkownika aplikacji (AUMID). I to jest niezbędne, aby dwie aplikacje zostały zainstalowane obok siebie na tym samym komputerze.
Na potrzeby tego przewodnika warto wprowadzić kilka innych zmian w Package.appxmanifest. Istnieją trzy wystąpienia ciągu przykładu schowka C#. Zmień to na Schowek C++/WinRT Przykład.
W projekcie Package.appxmanifest C++/WinRT plik i projekt nie są teraz zsynchronizowane w odniesieniu do plików elementów zawartości, do których się odwołują. Aby rozwiązać ten problem, najpierw usuń zasoby z projektu C++/WinRT, wybierając wszystkie pliki w Assets folderze (w Eksploratorze rozwiązań w programie Visual Studio) i usuwając je (wybierz pozycję Usuń w oknie dialogowym).
Projekt C# odwołuje się do plików zasobów z folderu udostępnionego. Możesz to zrobić w projekcie C++/WinRT lub skopiować pliki, co zrobimy w tym przewodniku.
Przejdź do folderu \Clipboard_sample\SharedContent\media. Wybierz siedem plików, które zawiera projekt języka C# (microsoft-sdk.png, smalltile-sdk.png, , splash-sdk.pngsquaretile-sdk.png, storelogo-sdk.png, tile-sdk.pngi windows-sdk.png), skopiuj je i wklej je do \Clipboard\Clipboard\Assets folderu w nowym projekcie.
Kliknij prawym przyciskiem myszy Assets folder (w Eksploratorze rozwiązań w projekcie C++/WinRT) >Dodaj>istniejący element... i przejdź do \Clipboard\Clipboard\Assets. W selektorze plików wybierz siedem plików i kliknij przycisk Dodaj.
Package.appxmanifest jest już zsynchronizowana z plikami zasobów projektu.
MainPage, w tym funkcje, które konfigurują przykład
Przykład schowka — podobnie jak wszystkie przykłady aplikacji platformy uniwersalnej systemu Windows (UWP) — składa się z kolekcji scenariuszy, przez które użytkownik może przechodzić pojedynczo. Kolekcja scenariuszy w danym przykładzie jest skonfigurowana w kodzie źródłowym przykładu. Każdy scenariusz w kolekcji to element danych, który przechowuje tytuł, a także typ klasy w projekcie, który implementuje scenariusz.
Jeśli spojrzysz na plik kodu źródłowego w wersji przykładowej w SampleConfiguration.cs języku C#, zobaczysz dwie klasy. Większość logiki konfiguracji znajduje się w klasie MainPage , która jest klasą częściową (stanowi kompletną klasę w połączeniu z znacznikami w MainPage.xaml pliku i kodem imperatywnego w pliku MainPage.xaml.cs). Inną klasą w tym pliku kodu źródłowego jest Scenariusz z właściwościami Title i ClassType .
W kilku następnych podsekcjach przyjrzymy się, jak przenieść MainPage i scenariusz.
IDL dla typu MainPage
Zacznijmy od tej sekcji, mówiąc krótko o języku IDL (Interface Definition Language) i o tym, jak pomaga nam to podczas programowania za pomocą języka C++/WinRT. IDL to rodzaj kodu źródłowego opisującego wywoływaną powierzchnię typu środowiska uruchomieniowego systemu Windows. Wywoływana (lub publiczna) powierzchnia typu jest udostępniana na zewnątrz, aby można było go używać. Ta zaprojektowana część typu kontrastuje z rzeczywistą wewnętrzną implementacją typu, która oczywiście nie jest wywoływalna i nie jest publiczna. Jest to tylko projektowana część, którą definiujemy w języku IDL.
Po utworzeniu kodu źródłowego .idl IDL (w pliku) można skompilować kod IDL do plików metadanych z możliwością odczytu maszynowego (nazywanych również metadanymi systemu Windows). Te pliki metadanych mają rozszerzenie .winmd, a oto niektóre z ich zastosowań.
- Element
.winmdmoże opisywać typy środowiska uruchomieniowego systemu Windows w składniku. W przypadku odwołowania się do składnika środowiska uruchomieniowego systemu Windows (WRC) z projektu aplikacji projekt aplikacji odczytuje metadane systemu Windows należące do WRC (metadane mogą znajdować się w osobnym pliku lub mogą być pakowane do tego samego pliku co sam plik WRC), aby można było korzystać z typów WRC z poziomu aplikacji. - Element
.winmdmoże opisywać typy środowiska uruchomieniowego systemu Windows w jednej części aplikacji, aby mogły być używane przez inną część tej samej aplikacji. Na przykład typ środowiska uruchomieniowego systemu Windows używany przez stronę XAML w tej samej aplikacji. - Aby ułatwić korzystanie z typów Windows Runtime (zarówno wbudowanych, jak i pochodzących od innych firm), system kompilacji C++/WinRT używa plików
.winmddo generowania typów otoki, które reprezentują projektowane części tych typów Windows Runtime. - Aby ułatwić implementację własnych typów środowiska uruchomieniowego systemu Windows, system kompilacji C++/WinRT zamienia plik IDL w
.winmdplik, a następnie używa go do generowania otoek dla projekcji, a także wycinków, na których można oprzeć implementację (omówimy więcej informacji na temat tych wycinków w dalszej części tego tematu).
Określona wersja języka IDL używanego z językiem C++/WinRT to Microsoft Interface Definition Language 3.0. W pozostałej części tego tematu przeanalizujemy MainPage w języku C# szczegółowo. Zdecydujemy, które części muszą znajdować się w projekcji C++/WinRT MainPage typem (czyli w wywołaniu lub publicznym, powierzchni) i które mogą być tylko częścią jego implementacji. To rozróżnienie jest ważne, ponieważ gdy dojdziemy do utworzenia naszego kodu IDL (co zrobimy w sekcji po tej sekcji), zdefiniujemy tam tylko elementy, które można wywołać.
Pliki kodu źródłowego języka C#, które razem implementują typ MainPage, to: MainPage.xaml (który wkrótce przekonwertujemy przez skopiowanie), MainPage.xaml.csoraz SampleConfiguration.cs.
W wersji C++/WinRT w podobny sposób podzielimy MainPage na pliki kodu źródłowego. Użyjemy logiki z MainPage.xaml.cs i w większości zastosujemy ją w MainPage.h i MainPage.cpp. W przypadku logiki w systemie SampleConfiguration.csprzetłumaczymy to na SampleConfiguration.h i SampleConfiguration.cpp.
Klasy w aplikacji platformy uniwersalnej systemu Windows w języku C# są oczywiście typami środowiska uruchomieniowego systemu Windows. Jednak podczas tworzenia typu w aplikacji C++/WinRT można wybrać, czy ten typ jest typem środowiska uruchomieniowego systemu Windows, czy zwykłą klasą/strukturą/wyliczeniem języka C++.
Każda strona XAML w naszym projekcie musi być typem środowiska uruchomieniowego systemu Windows, więc strona MainPage musi być typem środowiska uruchomieniowego systemu Windows. W projekcie C++/WinRT strona MainPage jest już typem środowiska uruchomieniowego systemu Windows, więc nie musimy zmieniać tego aspektu. W szczególności jest to klasa środowiska uruchomieniowego.
- Aby uzyskać więcej informacji na temat tego, czy należy utworzyć klasę środowiska uruchomieniowego dla danego typu, zobacz temat Author APIs with C++/WinRT.
- W języku C++/WinRT wewnętrzna implementacja klasy środowiska uruchomieniowego oraz rzutowane (publiczne) części istnieją w postaci dwóch różnych klas. Są one znane jako typ implementacji oraz przewidywany typ . Więcej informacji o nich można znaleźć w temacie wymienionym w poprzednim punkcie punktowym, a także w temacie Korzystanie z interfejsów API za pomocą języka C++/WinRT.
- Aby uzyskać więcej informacji o połączeniu między klasami środowiska uruchomieniowego a plikami IDL (pliki
.idl), możesz przeczytać temat dotyczący kontrolek XAML i śledzić sposób wiązania z właściwością C++/WinRT. W tym temacie opisano proces tworzenia nowej klasy środowiska uruchomieniowego. Pierwszym krokiem w tym procesie jest dodanie nowego elementu pliku Midl (.idl) do projektu.
W przypadku strony głównej w rzeczywistości mamy już wymagany plik MainPage.idl w projekcie C++/WinRT. Dzieje się tak, ponieważ szablon projektu utworzył go dla nas. Jednak w dalszej części tego przewodnika będziemy dodawać do projektu kolejne pliki .idl.
Wkrótce zobaczymy listę dokładnie tego, czego musimy dodać do istniejącego pliku MainPage.idl. Zanim to zrobimy, musimy przemyśleć, co powinno, a co nie powinno znaleźć się w IDL.
Aby określić, które elementy członkowskie programu MainPage należy zadeklarować w MainPage.idl pliku (tak, aby stały się częścią klasy środowiska uruchomieniowego MainPage ) i które mogą być po prostu elementami członkowskimi typu implementacji MainPage , utwórzmy listę elementów członkowskich klasy MainPage języka C#. Znajdujemy tych członków, patrząc w MainPage.xaml.cs i w SampleConfiguration.cs.
Znajdujemy łącznie dwanaście protected pól i private metod. A my znajdujemy następujących członków public.
- Domyślny konstruktor
MainPage(). - Pola statyczne Bieżący i FEATURE_NAME.
- Właściwości IsClipboardContentChangedEnabled i Scenarios.
- Metody BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotifications, i NotifyUser.
To są ci członkowie public, którzy są kandydatami do zgłoszenia w MainPage.idl. Przyjrzyjmy się każdemu z nich i sprawdźmy, czy muszą one być częścią klasy środowiska uruchomieniowego MainPage , czy też muszą być tylko częścią jego implementacji.
- Domyślny konstruktor
MainPage(). W przypadku strony XAML, normalne jest zadeklarowanie domyślnego konstruktora w języku IDL. Dzięki temu platforma interfejsu użytkownika XAML może aktywować typ. - Pole statyczne Current jest używane z poziomu indywidualnych stron XAML scenariuszy, aby uzyskać dostęp do instancji aplikacji MainPage. Ponieważ funkcja Current nie jest używana do współdziałania ze strukturą XAML (ani nie jest używana w jednostkach kompilacji), możemy zarezerwować ją wyłącznie jako element członkowski typu implementacji. Dzięki własnym projektom w takich przypadkach możesz to zrobić. Ale ponieważ pole jest wystąpieniem projektowanego typu, wydaje się wskazane, aby zadeklarować je w języku IDL. Dlatego zrobimy to tutaj (dzięki temu kod jest także nieco czystszy).
- Jest to podobny przypadek dla pola statycznego FEATURE_NAME, do którego uzyskuje się dostęp w typie MainPage. Ponownie, wybierając zadeklarowanie go w języku IDL sprawia, że nasz kod jest nieco czystszy.
- Właściwość IsClipboardContentChangedEnabled jest używana tylko w klasie OtherScenarios . Dlatego podczas portowania uprościmy trochę rzeczy i uczynimy je prywatnym polem w klasie OtherScenarios środowiska uruchomieniowego. Więc ten nie pójdzie w IDL.
- Właściwość Scenarios jest kolekcją obiektów typu Scenario (typu, o którym wspomniano wcześniej). Omówimy Scenariusz w następnej podsekcji, więc pozostawmy właściwość Scenarios do tego czasu.
- Metody BuildClipboardFormatsOutputString, DisplayToasti EnableClipboardContentChangedNotifications to funkcje narzędziowe, które mają więcej wspólnego z ogólnym stanem przykładu niż ze stroną główną. Dlatego podczas portu refaktoryzujemy te trzy metody na nowy typ narzędzia o nazwie SampleState (który nie musi być typem środowiska uruchomieniowego systemu Windows). Z tego powodu te trzy metody nie trafią do IDL.
- Metoda NotifyUser jest wywoływana wewnątrz poszczególnych stron XAML scenariusza na instancji MainPage, która jest zwracana ze statycznego pola Current. Ponieważ (jak już wspomniano) Current jest instancją projektowanego typu, musimy zadeklarować NotifyUser w IDL. NotifyUser przyjmuje parametr typu NotifyType. Omówimy to w następnej podsekcji.
Każdy członek, który chcesz powiązać z danymi, musi być również zadeklarowany w języku IDL (niezależnie od tego, czy używasz {x:Bind}, czy {Binding}). Aby uzyskać więcej informacji, zobacz powiązanie danych.
Robimy postępy: opracowujemy listę członków, których należy dodać, a których nie dodawać do pliku MainPage.idl. Musimy jednak omówić właściwość scenariuszy oraz typ NotifyType. Zróbmy to dalej.
Protokół IDL dla typów Scenario i NotifyType
Klasa Scenario jest zdefiniowana w pliku SampleConfiguration.cs. Mamy decyzję o sposobie przenoszenia tej klasy do języka C++/WinRT. Domyślnie prawdopodobnie będzie to zwykły C++ struct. Jeśli jednak scenariusz jest używany w plikach binarnych lub do współdziałania ze strukturą XAML, należy zadeklarować go w języku IDL jako typ środowiska uruchomieniowego systemu Windows.
Analizując kod źródłowy języka C#, stwierdzamy, że w tym kontekście jest używany Scenariusz .
<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;
Kolekcja obiektów scenariusza
Jak pamiętasz, MainPage.Scenarios jest kolekcją obiektów Scenario, które właśnie powiedzieliśmy, że muszą znajdować się w języku IDL. Z tego powodu MainPage.Scenarios również należy zadeklarować w języku IDL.
NotifyType jest enum zadeklarowanym w MainPage.xaml.csjęzyka C#. Ponieważ przekazujemy element NotifyType do metody należącej do klasy środowiska uruchomieniowego MainPage , funkcja NotifyType również musi być typem środowiska uruchomieniowego systemu Windows; należy go zdefiniować w pliku MainPage.idl.
Teraz dodajmy do pliku MainPage.idl nowe typy i nowego członka Mainpage, którego postanowiliśmy zadeklarować w IDL. W tym samym czasie usuniemy z IDL członków zastępczych Mainpage, które szablon projektu Visual Studio nam udostępnił.
Dlatego w projekcie C++/WinRT otwórz MainPage.idlplik i zmodyfikuj go tak, aby wyglądała jak poniższa lista. Należy pamiętać, że jedną z edycji jest zmiana nazwy przestrzeni nazw z Schowka na SDKTemplate. Jeśli chcesz, możesz po prostu zastąpić całą zawartość MainPage.idl poniższym kodem. Kolejną poprawką jest zmiana nazwy 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);
};
}
Uwaga / Notatka
Aby uzyskać więcej informacji na temat zawartości .idl pliku w projekcie C++/WinRT, zobacz Microsoft Interface Definition Language 3.0.
W przypadku pracy z własnym przenoszeniem możesz nie chcieć lub nie musieć zmieniać nazwy przestrzeni nazw, tak jak my to zrobiliśmy powyżej. Robimy tak, ponieważ domyślna przestrzeń nazw projektu języka C#, który przenosimy, to SDKTemplate, natomiast nazwa projektu i zestawu to Schowek.
Jednak w miarę kontynuowania pracy z portem w tym przewodniku będziemy zmieniać każde wystąpienie nazwy przestrzeni nazw Clipboard w kodzie źródłowym na SDKTemplate. Istnieje również miejsce we właściwościach projektu C++/WinRT, w którym pojawia się nazwa przestrzeni nazw Schowek, więc skorzystamy z okazji, aby to teraz zmienić.
W programie Visual Studio dla projektu C++/WinRT ustaw właściwość projektu Common Properties>C++/WinRT>Root Namespace na wartość SDKTemplate.
Zapisz plik IDL i ponownie wygeneruj pliki wycinkowe
Temat kontrolek XAML, powiązanie z właściwością C++/WinRT wprowadza pojęcie plików atrap i pokazuje przewodnik po nich w działaniu. Wspomnieliśmy również o wycinkach we wcześniejszej części tego tematu, gdy wspomnieliśmy, że system kompilacji C++/WinRT zamienia zawartość .idl plików w metadane systemu Windows, a następnie z tych metadanych narzędzie o nazwie cppwinrt.exe generuje wycinki, na których można oprzeć implementację.
Za każdym razem, gdy dodasz, usuniesz lub zmienisz coś w swoim IDL i skompilujesz, system kompilacji aktualizuje implementacje stubów w tych plikach stubów. Dlatego za każdym razem, gdy zmieniasz IDL i kompilujesz, zalecamy wyświetlenie tych plików wycinków, skopiowanie zmienionych podpisów i wklejanie ich do projektu. Przedstawimy bardziej szczegółowe informacje i przykłady dokładnie tego, jak to zrobić za chwilę. Ale zaletą tego jest zapewnienie bezbłędnego sposobu nieustannego poznawania, jaki powinien być kształt typu implementacji i jaka powinna być sygnatura jego metod.
W tym momencie w instrukcji skończyliśmy edytować plik MainPage.idl na razie, więc należy go teraz zapisać. Projekt nie zostanie ukończony w tym momencie, ale wykonanie kompilacji teraz to przydatna rzecz, ponieważ ponownie generuje pliki stubów dla MainPage. Dlatego skompiluj projekt teraz i zignoruj wszelkie błędy kompilacji.
W przypadku tego projektu C++/WinRT pliki wycinkowe są generowane w folderze \Clipboard\Clipboard\Generated Files\sources . Znajdziesz je tam po zakończeniu częściowej kompilacji (ponownie, zgodnie z oczekiwaniami, kompilacja nie powiedzie się całkowicie. Ale krok, który nas interesuje — generowanie wycinków — się powiodło). Pliki, które nas interesują, to MainPage.h i MainPage.cpp.
W tych dwóch plikach szablonowych zobaczysz nowe implementacje szablonowe elementów MainPage, które dodaliśmy do IDL (na przykładCurrent i FEATURE_NAME). Te implementacje wycinków należy skopiować do MainPage.h i MainPage.cpp plików, które znajdują się już w projekcie. Jednocześnie, podobnie jak w przypadku języka IDL, usuniemy z tych istniejących plików symbole zastępcze członków Mainpage, które zapewnił nam szablon projektu Visual Studio (fikcyjną właściwość o nazwie MyPropertyi program obsługi zdarzeń o nazwie ClickHandler).
W rzeczywistości jedynym członkiem bieżącej wersji MainPage, który chcemy zachować, jest konstruktor.
Gdy już skopiujesz nowych członków z plików wycinków, usuniesz członków, których nie chcemy, i zaktualizujesz przestrzeń nazw, pliki MainPage.h i MainPage.cpp w Twoim projekcie powinny wyglądać jak poniższe listy kodu. Zwróć uwagę, że istnieją dwa typy mainPage . Jedna w przestrzeni nazw implementacji , a druga w przestrzeni nazw factory_implementation . Jedyną zmianą, którą wprowadziliśmy w factory_implementation, jest dodanie SDKTemplate do przestrzeni nazwowej.
// 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();
}
}
W przypadku ciągów język C# używa elementu System.String. Zobacz przykładową metodę MainPage.NotifyUser . W naszym języku IDL deklarujemy ciąg za pomocą String, a gdy narzędzie cppwinrt.exe generuje dla nas kod C++/WinRT, używa typu winrt::hstring. Za każdym razem, gdy napotkamy ciąg w kodzie języka C#, przełożymy go na winrt::hstring. Aby uzyskać więcej informacji, zobacz obsługa ciągów w języku C++/WinRT.
Aby zapoznać się z wyjaśnieniem parametrów const& w sygnaturach metody, zobacz Przekazywanie parametrów.
Aktualizowanie wszystkich pozostałych deklaracji/odwołań przestrzeni nazw i kompilacji
Przed utworzeniem projektu C++/WinRT znajdź wszelkie deklaracje przestrzeni nazw Clipboard (i odwołania do niej), i zmień je na SDKTemplate.
-
MainPage.xamliApp.xaml. Przestrzeń nazw pojawia się w wartościach atrybutówx:Classixmlns:local. -
App.idl. -
App.h. -
App.cpp. Istnieją dwie dyrektywyusing namespace(wyszukaj podciągusing namespace Clipboard) i dwie kwalifikacje typu MainPage (wyszukajClipboard::MainPage). Ci potrzebują zmiany.
Ponieważ usunęliśmy program obsługi zdarzeń z MainPage, przejdź również do MainPage.xaml i usuń ze znaczników element Button.
Zapisz wszystkie pliki. Wyczyść rozwiązanie (Build>Clean Solution), a potem je zbuduj. Po zastosowaniu wszystkich zmian do tej pory, dokładnie jak zapisano, kompilacja powinna zakończyć się powodzeniem.
Implemetuj członków zadeklarowanych w MainPage w IDL
Konstruktor Aktualnyi FEATURE_NAME
Oto odpowiedni kod (z projektu C#), który musimy przeportować.
<!-- 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";
...
}
...
Wkrótce będziemy ponownie używać MainPage.xaml w całości (przez skopiowanie go). Na razie (poniżej) tymczasowo dodamy element TextBlock o odpowiedniej nazwie do MainPage.xaml projektu C++/WinRT.
FEATURE_NAME jest polem statycznym MainPage (pole const w C# jest zasadniczo statyczne w swoim zachowaniu), zdefiniowane w SampleConfiguration.cs. W przypadku języka C++/WinRT, zamiast statycznego pola, zrobimy to jako wyrażenie C++/WinRT odpowiadające statycznej właściwości tylko do odczytu. Sposób wyrażania getterów właściwości w C++/WinRT to funkcja, która zwraca wartość właściwości i nie przyjmuje żadnych parametrów (akcesor). Zatem pole statyczne języka C# FEATURE_NAME staje się statycznym akcesorem funkcji w C++/WinRT FEATURE_NAME (w tym przypadku zwracającym literał ciągu).
Nawiasem mówiąc, zrobilibyśmy to samo, gdybyśmy przenosili właściwość tylko do odczytu w języku C#. W przypadku właściwości zapisywalnej języka C# metoda wyrażania zestawu właściwości w języku C++/WinRT jest funkcją void , która przyjmuje wartość właściwości jako parametr (mutator). W obu przypadkach, jeśli pole lub właściwość w języku C# jest statyczna, to samo dotyczy akcesorów i/lub mutatorów C++/WinRT.
Current to statyczne (nie stałe) pole MainPage. Ponownie utworzymy ją (wyrażenie C++/WinRT) jako właściwość tylko do odczytu i ponownie ustawimy ją jako statyczną. Gdzie FEATURE_NAME jest stała, Current nie jest. Dlatego w języku C++/WinRT będziemy potrzebować pola zapasowego, a nasze akcesorium zwróci to. Dlatego w projekcie C++/WinRT zadeklarujemy w MainPage.h prywatne pole statyczne o nazwie current, zdefiniujemy/zainicjujemy current w MainPage.cpp (ponieważ ma statyczny czas trwania przechowywania), a uzyskamy do niego dostęp za pośrednictwem statycznej funkcji publicznej o nazwie Current.
Sam konstruktor wykonuje kilka przypisań, które są proste do przeniesienia.
W projekcie C++/WinRT dodaj nowy element Visual C++>Code>C++ File (.cpp) o nazwie SampleConfiguration.cpp.
Edytuj MainPage.xaml, MainPage.h, MainPage.cppi SampleConfiguration.cpp, aby dopasować je do poniższych list.
<!-- 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";
}
Pamiętaj również, aby usunąć istniejące ciała funkcji z MainPage.cpp dla MainPage::Current() i MainPage::FEATURE_NAME(), ponieważ definiujemy te metody w innym miejscu.
Jak widać, mainPage::current jest zadeklarowany jako typ SDKTemplate::MainPage, który jest przewidywanym typem. Nie jest to typ SDKTemplate::implementation::MainPage, który jest typem implementacji. Przewidywany typ to typ przeznaczony do wykorzystania w projekcie na potrzeby współdziałania XAML lub między plikami binarnymi. To typ implementacji jest używany do implementowania funkcjonalności, które zostały ujawnione dla rzutowanego typu. Ponieważ deklaracja MainPage::current (w MainPage.h) pojawia się w przestrzeni nazw implementacji (winrt::SDKTemplate::implementation), nieuznana MainPage odwoływałaby się do typu implementacji. Aby było jasne, że chcemy, aby MainPage::current było instancją przewidywanego typu winrt::SDKTemplate::MainPage, stosujemy kwalifikator SDKTemplate::.
W konstruktorze istnieją pewne kwestie związane z MainPage::current = *this;, które zasługują na wyjaśnienie.
- Jeśli używasz wskaźnika
thiswewnątrz składnika typu implementacji, wskaźnikthisjest oczywiście wskaźnikiem do typu implementacji. - Aby przekonwertować wskaźnik
thisna odpowiedni typ projekcyjny, wyłuszcz go. Jeśli typ implementacji został wygenerowany na podstawie języka IDL (jak mamy tutaj), typ implementacji ma operator konwersji, który konwertuje na jego przewidywany typ. Dlatego przypisanie tutaj działa.
Aby uzyskać więcej informacji o tych szczegółach, zobacz Tworzenie wystąpień i zwracanie typów implementacji oraz interfejsów.
Również w konstruktorze jest SampleTitle().Text(FEATURE_NAME());. Część SampleTitle() jest wywołaniem prostej funkcji dostępu nazywanej SampleTitle, która zwraca TextBlock, który dodaliśmy do XAML. Za każdym razem, gdy x:Name element XAML, kompilator XAML generuje akcesor o nazwie odpowiadającej elementowi. Część .Text(...) wywołuje funkcję mutatora Text w obiekcie TextBlock, który metoda SampleTitle zwraca. A FEATURE_NAME() wywołuje naszą statyczną funkcję dostępu MainPage::FEATURE_NAME, aby zwrócić literał ciągu. W sumie ten wiersz kodu ustawia właściwość Text w elemencie TextBlock o nazwie SampleTitle.
Należy pamiętać, że ponieważ ciągi znaków są wielobajtowe w środowisku uruchomieniowym systemu Windows, aby przenieść literał ciągu znaków, poprzedzamy go prefiksem kodowania wide-char L. Dlatego zamieniamy (na przykład) "literał ciągu" na L"literał ciągu". Zobacz także literały szerokiego ciągu .
Scenariusze
Oto odpowiedni kod w języku C#, który musimy przenieść.
// 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 wcześniejszego badania wiemy, że ta kolekcja obiektów Scenario jest wyświetlana w ListBox. W języku C++/WinRT istnieją ograniczenia dotyczące rodzaju kolekcji, które można przypisać do właściwości ItemsSource kontrolki elementów. Kolekcja musi być wektorem lub obserwowalnym wektorem, a jego elementy muszą być jednym z następujących elementów:
- Klasy środowiska uruchomieniowego lub
- IInspectable.
W przypadku IInspectable, jeśli elementy nie są klasami środowiska uruchomieniowego, muszą być one takiego rodzaju, aby można je było opakować i rozpakować do i z IInspectable. Oznacza to, że muszą być typami środowiska uruchomieniowego systemu Windows (zobacz wartości Boxing i unboxing doIInspectable ).
W przypadku tego studium przypadku nie utworzyliśmy scenariusza jako klasy wykonywalnej. Jest to jednak rozsądna opcja. I będą przypadki we własnej pracy przenoszenia, gdzie klasa środowiska uruchomieniowego na pewno będzie sposobem na przejście. Jeśli na przykład musisz ustawić typ elementu jako obserwowalny (zobacz kontrolki XAML i powiązanie z właściwością C++/WinRT), lub jeśli element potrzebuje mieć metody z innego powodu i jest czymś więcej niż tylko zestawem pól danych.
Ponieważ w tym przewodniku nie struct, nie moglibyśmy go opakować. Zadeklarowaliśmy jednak Scenariusz jako struct w języku IDL, dlatego możemy pole.
Pozostaje nam wybór: opakować scenariusz z wyprzedzeniem lub poczekać, aż będziemy przypisywać do źródła elementów ItemsSourcei opakować je na zasadzie just-in-time. Poniżej przedstawiono kilka zagadnień dotyczących tych dwóch opcji.
- Boks przed czasem. W przypadku tej opcji nasz element danych jest kolekcją IInspectable gotową do przypisania do interfejsu użytkownika. Podczas inicjowania opakowujemy obiekty Scenario do tego elementu członkowskiego danych. Potrzebujemy tylko jednej kopii tej kolekcji, ale musimy rozpakować element za każdym razem, gdy musieliśmy odczytać jego pola.
- Boks w samą porę. W przypadku tej opcji nasze dane to kolekcja scenariuszy . Kiedy nadejdzie czas, aby przypisać do interfejsu użytkownika, umieszczamy obiekty scenariusza z elementu członkowskiego danych w nowej kolekcji IInspectable. Możemy odczytywać pola elementów w członku danych bez rozpakowywania, ale potrzebujemy dwóch kopii kolekcji.
Jak widać, dla małej kolekcji takiej jak ta, zalety i wady równoważą się nawzajem. Dlatego w przypadku tego studium przypadku wybierzemy opcję just-in-time.
Scenariusze są polem MainPage, zdefiniowanym i zainicjowanym w SampleConfiguration.cs. A Scenarios jest właściwością tylko do odczytu MainPage, zdefiniowaną w MainPage.xaml.cs (i zaimplementowaną tak, aby zwracała bezpośrednio pole scenariuszy). Zrobimy coś podobnego w projekcie C++/WinRT, ale utworzymy dwa statyczne elementy członkowskie (ponieważ potrzebujemy tylko jednego wystąpienia w aplikacji i abyśmy mogli uzyskać do nich dostęp bez stworzenia wystąpienia klasy). Nazwiemy je odpowiednio scenariuszami wewnętrznymi i scenariuszami . Zadeklarujemy scenariusze Inner w MainPage.h. Ponieważ ma on statyczny czas przechowywania, zdefiniujemy/zainicjujemy go w pliku .cpp (w tym przypadkuSampleConfiguration.cpp).
Edytuj MainPage.h i SampleConfiguration.cpp, aby dopasować je do poniższej listy.
// 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>() },
});
Pamiętaj również, aby usunąć istniejącą treść funkcji z MainPage.cpp elementu MainPage::scenarios(), ponieważ teraz definiujemy tę metodę w pliku nagłówka.
Jak widać, w SampleConfiguration.cppinicjujemy statyczny członek danych scenariosInner wywołując funkcję pomocniczą C++/WinRT o nazwie winrt::single_threaded_observable_vector. Ta funkcja tworzy dla nas nowy obiekt kolekcji Środowiska uruchomieniowego systemu Windows i zwraca go jako interfejs IObservableVector . Ponieważ w tym przykładzie kolekcja nie jest zauważalna (nie musi być, ponieważ nie dodaje ani nie usuwa elementów po zainicjowaniu), możemy zamiast tego zdecydować się na wywołanie winrt::single_threaded_vector. Ta funkcja zwraca kolekcję jako interfejs IVector .
Aby uzyskać więcej informacji o kolekcjach i powiązaniu z nimi, zobacz kontrolki elementów XAML; wiązanie z kolekcją C++/WinRTi kolekcjami za pomocą języka C++/WinRT.
Właśnie dodany kod inicjowania odnosi się do typów, które nie są jeszcze w projekcie (na przykład winrt::SDKTemplate::CopyText. Aby rozwiązać ten problem, dodajmy do projektu pięć nowych pustych stron XAML.
Dodawanie pięciu nowych pustych stron XAML
Dodaj nowy element Visual C++>Pusta Strona (C++/WinRT) do projektu (upewnij się, że jest to szablon elementu Pusta Strona (C++/WinRT), a nie Pusta Strona). Nadaj mu nazwę CopyText. Strona XAML jest zdefiniowana w przestrzeni nazw SDKTemplate, co jest zgodne z naszymi oczekiwaniami.
Powtórz powyższy proces jeszcze cztery razy i nadaj nazwę stronom XAML CopyImage, CopyFiles, HistoryAndRoaming i OtherScenarios.
Teraz będzie można utworzyć ponownie, jeśli chcesz.
NotifyUser
W projekcie języka C# znajdziesz implementację metody MainPage.NotifyUser w pliku MainPage.xaml.cs.
MainPage.NotifyUser ma zależność od MainPage.UpdateStatus, a ta metoda z kolei ma zależności od elementów XAML, które jeszcze nie zostały przeniesione. Na razie w projekcie C++/WinRT utworzymy szkielet metody UpdateStatus, a później to zaimplementujemy.
Oto odpowiedni kod w języku C#, który musimy przenieść.
// 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 używa wyliczenia Windows.UI.Core.CoreDispatcherPriority enum. W języku C++/WinRT zawsze, gdy chcesz użyć typu z przestrzeni nazw systemu Windows, musisz dołączyć odpowiedni plik nagłówka przestrzeni nazw C++/WinRT systemu Windows (aby uzyskać więcej informacji na ten temat, zobacz Wprowadzenie do języka C++/WinRT). W takim przypadku, jak zobaczysz na poniższej liście kodu, nagłówek to winrt/Windows.UI.Core.h, a my będziemy go uwzględniać w elemencie pch.h.
UpdateStatus jest prywatny. Uczynimy z tego prywatną metodę w naszej implementacji typu MainPage. Funkcja UpdateStatus nie ma być wywoływana w klasie środowiska uruchomieniowego, więc nie zadeklarujemy jej w języku IDL.
Po przeporcie MainPage.NotifyUseri wyczyszczanie MainPage.UpdateStatus, jest to to, co mamy w projekcie C++/WinRT. Po tej liście kodu przeanalizujemy niektóre szczegóły.
// 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();
}
...
W języku C#można użyć notacji kropkowej, aby kropkę do właściwości zagnieżdżonych. Dlatego typ Dispatcher.HasThreadAccess. W C++/WinRT właściwości są implementowane jako funkcje akcesorów, więc składnia różni się tylko tym, że dla każdego wywołania funkcji dodaje się nawiasy.
| C# | C++/WinRT |
|---|---|
Dispatcher.HasThreadAccess |
Dispatcher().HasThreadAccess() |
Gdy wersja języka C# NotifyUser wywołuje CoreDispatcher.RunAsync, implementuje delegat asynchronicznego wywołania zwrotnego jako funkcję lambda. Wersja C++/WinRT działa tak samo, ale składnia jest nieco inna. W C++/WinRT przechwytujemy dwa parametry, które będziemy używać, oraz wskaźnik this (ponieważ będziemy wywoływać funkcję składową). Więcej informacji na temat implementowania delegatów jako lambda oraz przykładów kodu znajduje się w temacie Obsługa zdarzeń za pomocą delegatów w C++/WinRT. Dodatkowo, możemy zignorować część var task = w tym konkretnym przypadku. Nie czekamy na zwrócony obiekt asynchroniczny, więc nie trzeba go przechowywać.
Zaimplementuj pozostałych członków MainPage
Utwórzmy pełną listę członków MainPage (zaimplementowanych w MainPage.xaml.cs i SampleConfiguration.cs), aby zobaczyć, które zostały do tej pory przeniesione i które z nich jeszcze trzeba zrobić.
| Członek | Dostęp | Stan |
|---|---|---|
| konstruktor MainPage | public |
Przeniesiony |
| właściwość Current | public |
Przeniesiony |
| właściwość FEATURE_NAME | public |
Przeniesiony |
| właściwość IsClipboardContentChangedEnabled | public |
Nie uruchomiono |
| właściwość Scenarios | public |
Przeniesiony |
| Metoda BuildClipboardFormatsOutputString | public |
Nie uruchomiono |
| Metoda DisplayToast | public |
Nie uruchomiono |
| EnableClipboardContentChangedNotifications, metoda | public |
Nie uruchomiono |
| metoda NotifyUser | public |
Przeniesiony |
| OnNavigatedTo, metoda | protected |
Nie uruchomiono |
| to pole AktywnościOknaAplikacji | private |
Nie uruchomiono |
| pole NeedToPrintClipboardFormat | private |
Nie uruchomiono |
| scenariusze pole | private |
Przeniesiony |
| Button_Click metoda | private |
Nie uruchomiono |
| Metoda DisplayChangedFormats | private |
Nie uruchomiono |
| Footer_Click, metoda | private |
Nie uruchomiono |
| metoda HandleClipboardChanged | private |
Nie uruchomiono |
| OnClipboardChanged, metoda | private |
Nie uruchomiono |
| OnWindowActivated, metoda | private |
Nie uruchomiono |
| "ScenarioControl_SelectionChanged" - metoda | private |
Nie uruchomiono |
| UpdateStatus , metoda | private |
Zgaszony |
Omówimy jeszcze nieportowanych członków w kilku kolejnych podsekcjach.
Uwaga / Notatka
Od czasu do czasu natkniemy się na odwołania w kodzie źródłowym do elementów interfejsu użytkownika w oznaczeniach XAML (w MainPage.xaml). Gdy dojdziemy do tych odwołań, będziemy je tymczasowo omijać, dodając do XAML proste elementy zastępcze. W ten sposób projekt będzie się rozwijał po każdej podsekcji. Alternatywą jest rozwiązanie odwołań przez skopiowanie całej zawartościMainPage.xaml z projektu C# do projektu C++/WinRT. Ale jeśli to zrobimy, to minie dużo czasu, zanim uda nam się dotrzeć do pit stopu i rozpocząć budowę na nowo (co potencjalnie utrudni wykrycie jakichkolwiek literówek lub innych błędów, które robimy po drodze).
Po zakończeniu przenoszenia kodu imperatywnego dla klasy MainPagenastępnie skopiujemy zawartość pliku XAML i będziemy mieć pewność, że projekt będzie nadal kompilować.
IsClipboardContentChangedEnabled
Jest to właściwość get-set języka C#, która domyślnie ma wartość false. Jest elementem członkowskim MainPage i jest zdefiniowany w pliku SampleConfiguration.cs.
W przypadku języka C++/WinRT potrzebujemy funkcji dostępu, funkcji mutatora i elementu członkowskiego danych pomocniczych jako pola. Ponieważ IsClipboardContentChangedEnabled reprezentuje stan jednego ze scenariuszy w przykładzie, a nie stan samej MainPage, utworzymy nowych członków na nowym typie narzędziowym nazwanym SampleState. Zaimplementujemy to w naszym pliku kodu źródłowego SampleConfiguration.cpp i utworzymy członków static (ponieważ potrzebujemy tylko jednego wystąpienia w całej aplikacji, abyśmy mogli uzyskać do nich dostęp bez konieczności instancji klasy).
Aby towarzyszyć naszym SampleConfiguration.cpp w projekcie C++/WinRT, dodaj nowy element Visual C++>Code>Plik Nagłówkowy (.h) o nazwie SampleConfiguration.h. Edytuj SampleConfiguration.h i SampleConfiguration.cpp, aby dopasować je do poniższej listy.
// 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;
}
}
Ponownie, pole z magazynem static (na przykład SampleState::isClipboardContentChangedEnabled) musi być zdefiniowane co najmniej raz w aplikacji, a plik .cpp jest odpowiednim miejscem na to (SampleConfiguration.cpp w tym przypadku).
BuildClipboardFormatsOutputString
Ta metoda jest publiczną metodą klasy MainPagei jest zdefiniowana w 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();
}
...
W języku C++/WinRT uczynimy BuildClipboardFormatsOutputString publiczną, statyczną metodą SampleState. Możliwe jest, aby ustawić to static, ponieważ nie uzyskuje dostępu do żadnych członków instancji.
Aby użyć typów Clipboard i DataPackageView w języku C++/WinRT, należy dołączyć plik nagłówkowy przestrzeni nazw Windows dla C++/WinRT winrt/Windows.ApplicationModel.DataTransfer.h.
W języku C# właściwość DataPackageView.AvailableFormats jest typu IReadOnlyList, co pozwala nam uzyskać dostęp do właściwości Count. W języku C++/WinRT funkcja dostępu DataPackageView::AvailableFormats zwraca funkcję dostępu IVectorView, która ma funkcję dostępu Size, którą możemy wywołać.
Aby użyć języka C# sstream ). Zamiast używać metody
Kod języka C# konstruuje element StringBuilder ze new słowem kluczowym. W języku C#, obiekty są domyślnie typami referencyjnymi, zadeklarowanymi na stercie z new. W nowoczesnym standardzie C++obiekty są domyślnie typami wartości zadeklarowanych na stosie (bez użycia metody new). Dlatego przenosimy StringBuilder output = new StringBuilder(); do języka C++/WinRT jako po prostu std::wostringstream output;.
Słowo kluczowe języka C# var prosi kompilatora o wywnioskowanie typu. Przenosisz var do auto w C++/WinRT. Jednak w języku C++/WinRT istnieją przypadki, w których (aby uniknąć kopii) potrzebujesz odwołanie do typu wnioskowanego (lub dedukowanego) i wyrażasz odwołanie lvalue do typu wnioskowanego za pomocą auto&. Istnieją również przypadki, w których chcesz uzyskać specjalny rodzaj odnośnika, który jest poprawnie powiązany, niezależnie od tego, czy jest inicjowany jako lvalue, czy jako rvalue. I wyrażasz to za pomocą auto&&. Jest to forma używana w pętli for w poniższym przeportowanym kodzie. Aby zapoznać się z wprowadzeniem do lvalues i rvalues, zobacz Kategorie wartości i odwołania do nich.
Edytuj pch.h, SampleConfiguration.hi SampleConfiguration.cpp, aby dopasować je zgodnie z poniższymi listami.
// 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() };
}
Uwaga / Notatka
Składnia w wierszu kodu DataPackageView clipboardContent{ Clipboard::GetContent() }; używa funkcji nowoczesnego standardowego języka C++ o nazwie jednolite inicjowanie, z jej charakterystycznym zastosowaniem nawiasów klamrowych zamiast znaku =. Ta składnia jasno wskazuje, że ma miejsce inicjalizacja, a nie przypisanie. Jeśli wolisz rodzaj składni, która wygląda jak przypisanie (ale w rzeczywistości nią nie jest), możesz zastąpić składnię powyżej równoważną DataPackageView clipboardContent = Clipboard::GetContent();. Dobrym pomysłem jest zapoznanie się z obydwoma sposobami wyrażania inicjalizacji, ponieważ prawdopodobnie często je spotkasz w kodzie, z którym się zetkniesz.
DisplayToast
DisplayToast to publiczna statyczna metoda klasy MainPage języka C#, która jest zdefiniowana w pliku SampleConfiguration.cs. W języku C++/WinRT utworzymy publiczną statyczną metodę SampleState.
Już napotkaliśmy większość szczegółów i technik, które są istotne dla portowania tej metody. Jednym z nowych elementów jest portowanie literału ciągu języka C# (@) do standardowego literału ciągu pierwotnego języka C++ (LR).
Ponadto w przypadku odwołania się do typów ToastNotification i XmlDocument w języku C++/WinRT można je zakwalifikować według nazwy przestrzeni nazw lub edytować SampleConfiguration.cpp i dodać dyrektywy using namespace, takie jak poniższy przykład.
using namespace Windows::UI::Notifications;
Podczas odwołowania się do typu XmlDocument i przy każdym odwołwaniu się do dowolnego innego typu środowiska uruchomieniowego systemu Windows.
Oprócz tych elementów po prostu postępuj zgodnie z tymi samymi wskazówkami, które stosowałeś wcześniej, aby wykonać następujące kroki.
- Zadeklaruj metodę w pliku
SampleConfiguration.hi zdefiniuj ją w plikuSampleConfiguration.cpp. - Edytuj
pch.h, aby uwzględnić wszystkie niezbędne pliki nagłówkowe przestrzeni nazw Windows C++/WinRT. - Konstruuj obiekty C++/WinRT na stosie zamiast na stercie.
- Zastąp wywołania właściwości get accessors składnią wywołania funkcji (
()).
Bardzo typową przyczyną błędów kompilatora/konsolidatora jest zapominanie o dołączeniu potrzebnych plików nagłówków przestrzeni nazw systemu Windows C++/WinRT. Aby uzyskać więcej informacji na temat jednego możliwego błędu, zobacz C3779: Dlaczego kompilator daje mi błąd "consume_Something: funkcja zwracająca "auto" nie może być używana przed jego zdefiniowaniem?.
Jeśli chcesz wykonać czynności opisane w przewodniku i zaimplementować DisplayToast samodzielnie, możesz porównać wyniki z wersją C++/WinRT w pliku ZIP zawierającym kod źródłowy przykładowego programu Schowek , który pobrałeś.
EnableClipboardContentChangedNotifications
EnableClipboardContentChangedNotifications to publiczna metoda statyczna klasy MainPage i jest zdefiniowana w 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) { ... }
...
W języku C++/WinRT utworzymy publiczną statyczną metodę SampleState.
W języku C# używasz składni operatora += i -= do rejestrowania i odwoływania delegatów obsługi zdarzeń. W języku C++/WinRT masz kilka opcji składniowych do rejestrowania/odwoływanie delegata zgodnie z opisem w temacie Obsługa zdarzeń przy użyciu delegatów w języku C++/WinRT. Jednak ogólna forma polega na tym, że zwykle rejestrujesz i odwołujesz za pomocą pary funkcji nazwanych dla zdarzenia. Aby się zarejestrować, przekazujesz pełnomocnika do funkcji rejestracji i pobierasz token odwołania w zamian (winrt::event_token). Aby odwołać, należy przekazać ten token do funkcji odwołania. W takim przypadku hander jest statyczny i (jak widać na poniższej liście kodu) składnia wywołania funkcji jest prosta.
Podobne tokeny są rzeczywiście używane w tle w języku C#. Ale język sprawia, że szczegóły są niejawne. C++/WinRT czyni to jawne.
Typ obiektu pojawia się w podpisach programów obsługi zdarzeń w języku C#. W języku C# obiekt jest aliasem typu .NET System.Object . Odpowiednikiem w języku C++/WinRT jest winrt::Windows::Foundation::IInspectable. W programach obsługi zdarzeń C++/WinRT zobaczysz IInspectable.
Edytuj SampleConfiguration.h i SampleConfiguration.cpp, aby dopasować je do poniższej listy.
// 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){}
Pozostaw delegaty obsługi zdarzeń (OnClipboardChanged i OnWindowActivated) jako stuby na razie. Znajdują się one już na naszej liście członków do portu, więc przejdziemy do nich w kolejnych podsekcjach.
OnNavigatedTo
OnNavigatedTo jest chronioną metodą klasy MainPage języka C# i jest zdefiniowana w pliku MainPage.xaml.cs. Oto to, wraz z odwołującym się do niego XAML ListBox.
<!-- 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;
}
}
Jest to ważna i interesująca metoda, ponieważ zbiór obiektów Scenario jest przypisany tutaj do interfejsu użytkownika. Kod języka C# tworzy System.Collections.Generic.List obiektów Scenario i przypisuje je do właściwości ItemsSourceListBox (która jest kontrolką elementów). W języku C#używamy interpolacji ciągów do utworzenia tytułu dla każdego obiektu Scenario (zwróć uwagę na użycie znaku specjalnego $).
W języku C++/WinRT zrobimy OnNavigatedTo metodą publiczną MainPage. Dodamy element stub ListBox do kodu XAML, aby kompilacja powiodła się. Po liście kodu przeanalizujemy niektóre szczegóły.
<!-- 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);
}
}
...
Ponownie wywołujemy funkcję winrt::single_threaded_observable_vector, ale tym razem ma ona służyć do utworzenia kolekcji IInspectable. To była część decyzji, którą podjęliśmy, aby przeprowadzić pakowanie naszych obiektów scenariuszy i na zasadzie just-in-time.
Ponadto zamiast używania interpol acji ciągów w języku C# w tym miejscu używamy kombinacji funkcji to_hstring i operatora łączenia winrt::hstring.
isApplicationWindowActive
W języku C#, isApplicationWindowActive to proste pole prywatne bool należące do klasy MainPage i zdefiniowane w pliku SampleConfiguration.cs. Wartość domyślna to false. W języku C++/WinRT ustawimy je jako prywatne statyczne pole SampleState (z powodów, które już opisaliśmy) w plikach SampleConfiguration.h i SampleConfiguration.cpp, zachowując tę samą wartość domyślną.
Widzieliśmy już, jak zadeklarować, zdefiniować i zainicjować pole statyczne. Dla przypomnienia, spójrz wstecz na to, co zrobiliśmy z polem isClipboardContentChangedEnabled i zrób to samo z isApplicationWindowActive.
needToPrintClipboardFormat
Ten sam wzorzec co isApplicationWindowActive (patrz nagłówek bezpośrednio poprzedzający ten).
Kliknięcie_Przycisku
Button_Click jest prywatną metodą obsługi zdarzeń klasy MainPage języka C# i jest zdefiniowana w MainPage.xaml.cs. W tym miejscu wraz z SplitView XAML, do którego się odwołuje, oraz ToggleButton, który go rejestruje.
<!-- MainPage.xaml -->
...
<SplitView x:Name="Splitter" ... />
...
<ToggleButton Click="Button_Click" .../>
...
private void Button_Click(object sender, RoutedEventArgs e)
{
Splitter.IsPaneOpen = !Splitter.IsPaneOpen;
}
I oto odpowiednik, przeniesiony do C++/WinRT. Należy pamiętać, że w wersji C++/WinRT procedura obsługi zdarzeń jest public (jak widać, należy zadeklarować ją przed deklaracjamiprivate:). Jest to spowodowane tym, że procedura obsługi zdarzeń zarejestrowana w znacznikach XAML, taka jak ta, musi być public w języku C++/WinRT, aby znacznikI XAML uzyskiwały do niego dostęp. Z drugiej strony, jeśli zarejestrujesz program obsługi zdarzeń w kodzie imperatywnym (tak jak w MainPage::EnableClipboardContentChangedNotifications wcześniej), program obsługi zdarzeń nie musi być 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());
}
WyświetlZmianyFormatów
W języku C# DisplayChangedFormats jest prywatną metodą należącą do klasy MainPage i zdefiniowaną w elemencie SampleConfiguration.cs.
private void DisplayChangedFormats()
{
string output = "Clipboard content has changed!" + Environment.NewLine;
output += BuildClipboardFormatsOutputString();
NotifyUser(output, NotifyType.StatusMessage);
}
W języku C++/WinRT utworzymy prywatne pole statyczne SampleState (nie uzyskuje dostępu do żadnych elementów instancji) w plikach SampleConfiguration.h i SampleConfiguration.cpp. Kod w C# dla tej metody nie używa System.Text.StringBuilder; jednak wykonuje wystarczająco dużo formatowania ciągów, że dla wersji C++/WinRT jest to również dobre miejsce do użycia std::wostringstream.
Zamiast statycznej właściwości System.Environment.NewLine , która jest używana w kodzie języka C#, wstawimy standardowy znak C++ std::endl (znak nowego wiersza) do strumienia wyjściowego.
// 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);
}
W projekcie powyższej wersji języka C++/WinRT występuje niewielka nieefektywność. Najpierw utworzymy std::wostringstream. Jednak również wywołujemy metodę BuildClipboardFormatsOutputString (którą przenieśliśmy wcześniej). Ta metoda tworzy własną std::wostringstream. I zamienia strumień w winrt::hstring i zwraca to. Wywołujemy funkcję
To właśnie robimy w wersji przykładowego kodu źródłowego schowka C++/WinRT (w pobranym pliku ZIP). W tym kodzie źródłowym istnieje nowa prywatna metoda statyczna o nazwie SampleState::AddClipboardFormatsOutputString, która przyjmuje odwołanie do strumienia wyjściowego i działa na nim. Następnie metody SampleState::DisplayChangedFormats i SampleState::BuildClipboardFormatsOutputString są refaktoryzowane, aby wywołać tę nową metodę. Jest ona funkcjonalnie równoważna listom kodu w tym temacie, ale jest bardziej wydajna.
Footer_Click
Footer_Click jest asynchroniczną procedurą obsługi zdarzeń należącą do klasy C# MainPage i jest zdefiniowana w pliku MainPage.xaml.cs. Poniższy kod jest funkcjonalnie odpowiednikiem metody w pobranym kodzie źródłowym. Ale tutaj rozbiłem to z jednej linii na cztery, aby ułatwić zobaczenie, co ono robi, i w konsekwencji, jak powinniśmy to przenosić.
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);
}
Chociaż technicznie metoda jest asynchroniczna, nie wykonuje żadnych czynności po metodzie await, więc nie potrzebuje słowa kluczowego await (ani słowa kluczowego async ). Prawdopodobnie używa ich w celu uniknięcia komunikatu IntelliSense w programie Visual Studio.
Równoważna metoda C++/WinRT będzie również asynchroniczna (ponieważ wywołuje Launcher.LaunchUriAsync). Nie musi jednak co_awaitani zwracać obiektu asynchronicznego. Aby uzyskać informacje o asynchronicznych obiektach co_await, zobacz Współbieżność i operacje asynchroniczne w C++/WinRT.
Teraz porozmawiajmy o tym, co robi metoda. Ponieważ jest to procedura obsługi zdarzeń dla zdarzenia Click obiektu HyperlinkButton, obiekt nazwany sender jest w rzeczywistości HyperlinkButton. Dlatego konwersja typu jest bezpieczna (alternatywnie można wyrazić tę konwersję jako sender as HyperlinkButton). Następnie pobierzemy wartość właściwości Tag (jeśli spojrzysz na znacznik XAML w projekcie języka C#, zobaczysz, że jest on ustawiony na ciąg reprezentujący internetowy adres URL). Mimo że właściwość FrameworkElement.Tag (HyperlinkButton jest FrameworkElement) jest typu object, w języku C# możemy to spisać za pomocą Object.ToString. Z wynikowego ciągu skonstruujemy obiekt Uri. Na koniec za pomocą Shell uruchamiamy przeglądarkę i przechodzimy do adresu URL.
Oto metoda przeniesiona do języka C++/WinRT (ponownie rozszerzona w celu uzyskania jasności), po czym następuje opis szczegółów.
// 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);
}
Tak jak zwykle, tworzymy obsługę zdarzeń public. Używamy
Ostatnie dwa wiersze powtarzają wzorce przenoszenia, które widzieliśmy wcześniej, i są one prawie echo wersji języka C#.
ObsłużZmianęSchowka
Nie ma nic nowego w procesie przenoszenia tej metody. Można porównać wersje C# i C++/WinRT w pliku ZIP, zawierającym pobrany kod źródłowy przykładu schowka .
OnClipboardChanged i OnWindowActivated
Do tej pory mamy tylko puste szablony dla tych dwóch procedur obsługi zdarzeń. Jednak przenoszenie ich jest proste i nie podnosi nic nowego do omówienia.
ScenarioControl_SelectionChanged
Jest to kolejna prywatna procedura obsługi zdarzeń należąca do klasy MainPage języka C# i zdefiniowana w pliku MainPage.xaml.cs. W C++/WinRT uczynimy go publicznym i zaimplementujemy w MainPage.h i MainPage.cpp.
W przypadku tej metody będziemy potrzebować MainPage::nawigując, które jest prywatnym polem typu Boolean zainicjowanym na false. W
Jeśli zamiast ręcznie przenosić, kopiujesz kod z wersji C++/WinRT z pliku ZIP z przykładowym kodem źródłowym schowka , który pobrałeś, to zobaczysz tam używane MainPage::NavigateTo. Na razie wystarczy przekształcić zawartość NavigateTo w ScenarioControl_SelectionChanged.
AktualizujStatus
Do tej pory mamy tylko szkic dla MainPage.UpdateStatus. Przenoszenie jego implementacji na nowo w dużej mierze dotyczy znanych już zagadnień. Jedną z nowych kwestii jest to, że podczas gdy w języku C# możemy porównać ciąg do String.Empty, w języku C++/WinRT zamiast tego wywołujemy funkcję winrt::hstring::empty. Kolejnym powodem jest to, że nullptr jest standardowym odpowiednikiem języka C++ dla nullw języku C#.
Resztę portu można wykonać za pomocą technik, które już omówiliśmy. Oto lista rzeczy, które należy wykonać, zanim przeniesiona wersja tej metody zostanie skompilowana.
- Aby dodać
, obramowanie o nazwie StatusBorder . - Aby
MainPage.xaml, dodaj TextBlock o nazwie StatusBlock. - Aby
MainPage.xaml, dodaj StackPanel o nazwie StatusPanel. - Aby
pch.h, dodaj#include "winrt/Windows.UI.Xaml.Media.h". - Aby
pch.h, dodaj#include "winrt/Windows.UI.Xaml.Automation.Peers.h". - Do
MainPage.cppdodajusing namespace winrt::Windows::UI::Xaml::Media;. - Do
MainPage.cppdodajusing namespace winrt::Windows::UI::Xaml::Automation::Peers;.
Skopiuj kod XAML i style niezbędne do zakończenia przenoszenia MainPage
W przypadku języka XAML idealnym przypadkiem jest to, że można użyć tego samego znaczników XAML w języku C# i projekcie C++/WinRT. Przykład z Schowkiem jest jednym z takich przypadków.
W pliku Styles.xaml jest scalona z elementem App.xaml. A potem jest standardowy MainPage.xaml jako punkt startowy dla interfejsu użytkownika, który widzieliśmy już krótko. Teraz możemy ponownie użyć tych trzech .xaml plików bez zmian w wersji C++/WinRT projektu.
Podobnie jak w przypadku plików elementów zawartości, możesz odwoływać się do tych samych udostępnionych plików XAML z wielu wersji aplikacji. W tym przewodniku po prostu ze względu na prostotę skopiujemy pliki do projektu C++/WinRT i dodamy je w ten sposób.
Przejdź do \Clipboard_sample\SharedContent\xaml folderu, wybierz i skopiuj App.xaml i MainPage.xaml, a następnie wklej te dwa pliki do \Clipboard\Clipboard folderu w projekcie C++/WinRT, wybierając opcję zamiany plików po wyświetleniu monitu.
W projekcie C++/WinRT w programie Visual Studio kliknij pozycję Pokaż wszystkie pliki , aby go włączyć. Teraz dodaj nowy folder bezpośrednio w węźle projektu i nadaj mu Stylesnazwę . W Eksploratorze \Clipboard_sample\SharedContent\xaml plików przejdź do folderu, wybierz i skopiuj Styles.xaml, a następnie wklej go do właśnie utworzonego \Clipboard\Clipboard\Styles folderu. Wróć do Eksploratora rozwiązań w projekcie C++/WinRT, kliknij prawym przyciskiem myszy Styles folder >Dodaj>istniejący element... i przejdź do \Clipboard\Clipboard\Styles. W selektorze plików wybierz pozycję Styles i kliknij pozycję Dodaj.
Dodaj nowy folder do projektu C++/WinRT, bezpośrednio pod węzłem projektu, i nazwij go Styles. Przejdź do \Clipboard_sample\SharedContent\xaml folderu, wybierz i skopiuj Styles.xaml, a następnie wklej go do \Clipboard\Clipboard\Styles folderu w projekcie C++/WinRT. Kliknij prawym przyciskiem myszy Styles folder (w Eksploratorze rozwiązań w projekcie C++/WinRT) >Dodaj>istniejący element... i przejdź do \Clipboard\Clipboard\Styles. W selektorze plików wybierz pozycję Styles i kliknij pozycję Dodaj.
Aby wyłączyć tę opcję, kliknij ponownie Pokaż wszystkie pliki.
Zakończyliśmy przenoszenie MainPage, a jeśli śledziłeś kroki, twój projekt C++/WinRT teraz się zbuduje i uruchomi.
Skonsoliduj swoje .idl pliki
Oprócz standardowego MainPage.xaml jako punktem początkowym dla interfejsu użytkownika, przykład Schowka ma pięć innych stron XAML, które są specyficzne dla scenariusza, wraz z odpowiednimi plikami zaplecza kodu. Będziemy ponownie używać rzeczywistego kodu znacznikowego XAML wszystkich tych stron, niezmienionego, w wersji projektu C++/WinRT. Przyjrzymy się również temu, jak przenosić kod w kilku kolejnych głównych sekcjach. Ale przed tym porozmawiajmy o IDL.
Istnieje wartość konsolidowania kodu IDL dla klas środowiska uruchomieniowego w jednym pliku IDL. Aby dowiedzieć się więcej o tej wartości, zobacz rozbijanie klas środowiska uruchomieniowego na pliki MIDL (.idl). W następnej kolejności skonsolidujemy zawartość CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idl i OtherScenarios.idl poprzez przeniesienie IDL do pojedynczego pliku o nazwie Project.idl (a następnie usunięcie oryginalnych plików).
Chociaż to robimy, usuńmy również automatycznie wygenerowaną fikcyjną właściwość (Int32 MyProperty;i jej implementację) z każdego z tych pięciu typów stron XAML.
Najpierw dodaj nowy element Midl File (.idl) do projektu C++/WinRT. Nadaj mu nazwę Project.idl. Zastąp całą zawartość Project.idl następującym kodem.
// 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 widać, jest to tylko kopia zawartości poszczególnych plików .idl, wszystkie znajdujące się w jednej przestrzeni nazw, a MyProperty zostało usunięte z każdej klasy uruchomieniowej.
W Eksploratorze rozwiązań w programie Visual Studio zaznacz wiele oryginalnych plików IDL (CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idli OtherScenarios.idl) oraz Edytuj>Usuń (wybierz Usuń w oknie dialogowym).
Na koniec — i aby ukończyć usuwanie MyProperty— w plikach .h i .cpp dla każdego z tych samych pięciu typów stron XAML usuń deklaracje i definicje akcesora int32_t MyProperty() i mutatora void MyProperty(int32_t).
Nawiasem mówiąc, zawsze dobrym pomysłem jest, aby nazwa plików XAML była zgodna z nazwą klasy, którą reprezentują. Na przykład, jeśli masz plik x:Class="MyNamespace.MyPage" w znacznikach XAML, to ten plik powinien nosić nazwę MyPage.xaml. Chociaż nie jest to wymaganie techniczne, nie trzeba żonglować różnymi nazwami tego samego artefaktu, dzięki temu projekt będzie bardziej zrozumiały i konserwowalny i łatwiejszy w pracy.
KopiujPliki
W projekcie języka C# typ strony CopyFiles XAML jest implementowany w plikach kodu źródłowego CopyFiles.xaml i CopyFiles.xaml.cs . Przyjrzyjmy się po kolei każdemu członkowi CopyFiles.
rootPage
Jest to pole prywatne.
// CopyFiles.xaml.cs
...
public sealed partial class CopyFiles : Page
{
MainPage rootPage = MainPage.Current;
...
}
...
W języku C++/WinRT możemy zdefiniować i zainicjować go w ten sposób.
// CopyFiles.h
...
struct CopyFiles : CopyFilesT<CopyFiles>
{
...
private:
SDKTemplate::MainPage rootPage{ MainPage::Current() };
};
...
Ponownie (podobnie jak w przypadku elementu MainPage::current), element CopyFiles::rootPage jest zadeklarowany jako typ SDKTemplate::MainPage, który jest przewidywanym typem, a nie typem implementacji.
CopyFiles (konstruktor)
W projekcie C++/WinRT typ
CopyButton_Click
Metoda CopyButton_Click języka C# jest procedurą obsługi zdarzeń, a z obecności słowa kluczowego async w jej podpisie możemy wywnioskować, że metoda wykonuje pracę asynchronicznie. W języku C++/WinRT implementujemy metodę asynchroniczną jako kohroutynę. Aby zapoznać się z wprowadzeniem do współbieżności w języku C++/WinRT, wraz z opisem tego, czym jest coroutine, zobacz współbieżność i asynchroniczne operacje w języku C++/WinRT.
Często chce się zaplanować dalszą pracę po zakończeniu korutyny, a w takich przypadkach korutyna zwróci jakiś typ obiektu asynchronicznego, który można oczekiwać, i opcjonalnie zgłasza postęp. Jednak te zagadnienia zwykle nie mają zastosowania do programu obsługi zdarzeń. Dlatego gdy masz program obsługi zdarzeń, który wykonuje operacje asynchroniczne, możesz zaimplementować to jako kohroutynę zwracającą winrt::fire_and_forget. Aby uzyskać więcej informacji, zobacz Fire i zapomnij.
Chociaż ideą korutyny typu "odpal i zapomnij" jest to, że nie obchodzi Cię, kiedy się zakończy, praca nadal trwa (lub jest zawieszona, czekając na wznowienie) w tle. W implementacji języka C# widać, że CopyButton_Click zależy od wskaźnika this (ma dostęp do elementu danych instancji rootPage). Dlatego musimy mieć pewność, że wskaźnik this (wskaźnik do obiektu CopyFiles) powinna żyć dłużej niż koreutyna CopyButton_Click. W sytuacji takiej jak w przypadku tej przykładowej aplikacji, w której użytkownik przechodzi między stronami interfejsu użytkownika, nie możemy bezpośrednio kontrolować okresu istnienia tych stron. Jeśli strona this i zachować to odwołanie przez czas trwania kohroutyny. Aby uzyskać więcej informacji, zobacz Silne i słabe odwołania w języku C++/WinRT.
Jeśli spojrzysz na wersję przykładu języka C++/WinRT, na CopyFiles::CopyButton_Click, zobaczysz, że jest to zrobione przy użyciu prostej deklaracji na stosie.
fire_and_forget CopyFiles::CopyButton_Click(IInspectable const&, RoutedEventArgs const&)
{
auto lifetime{ get_strong() };
...
}
Przyjrzyjmy się innym aspektom kodu przeniesionego, które są godne uwagi.
W kodzie tworzymy instancję obiektu FileOpenPicker, a dwa wiersze dalej uzyskujemy dostęp do właściwości obiektu FileTypeFilter. Zwracany typ tej właściwości implementuje IVector ciągów znaków. I na tym IVectornazywamy IVector<T>i wywołujemy metodę ReplaceAll(T[]). Interesującym aspektem jest wartość, którą przekazujemy tej metodzie, gdzie oczekiwano tablicy. Oto wiersz kodu.
filePicker.FileTypeFilter().ReplaceAll({ L"*" });
Wartość przekazywana ({ L"*" }) to standardowa lista inicjatorów języka C++. Zawiera on pojedynczy obiekt, w tym przypadku, ale lista inicjatorów może zawierać dowolną liczbę obiektów rozdzielonych przecinkami. Elementy języka C++/WinRT, które umożliwiają wygodne przekazywanie listy inicjalizacyjnej do metody, jak ta, zostały wyjaśnione w standardowych listach inicjalizacyjnych.
Przenosimy słowo kluczowe C# await do co_await języka C++/WinRT. Oto przykład z kodu.
auto storageItems{ co_await filePicker.PickMultipleFilesAsync() };
Następnie rozważmy ten wiersz kodu języka C#.
dataPackage.SetStorageItems(storageItems);
Język C# może niejawnie przekonwertować IReadOnlyList<StorageFile>, który jest reprezentowany przez storageItems, na IEnumerable<IStorageItem> oczekiwane przez metodę DataPackage.SetStorageItems. Jednak w języku C++/WinRT musimy jawnie przekonwertować z IVectorView<StorageFile> na IIterable<IStorageItem>. A więc mamy kolejny przykład działania funkcji jako.
dataPackage.SetStorageItems(storageItems.as<IVectorView<IStorageItem>>());
Gdzie używamy słowa kluczowego null w języku C# (na przykład Clipboard.SetContentWithOptions(dataPackage, null)), używamy nullptr w języku C++/WinRT (na przykład Clipboard::SetContentWithOptions(dataPackage, nullptr)).
WklejPrzycisk_Klik
Jest to kolejny obsługiwacz zdarzeń w postaci korutyny działającej na zasadzie "fire-and-forget". Przyjrzyjmy się aspektom kodu przeniesionego, które są godne uwagi.
W przykładzie napisanym w języku C# przechwytujemy wyjątki za pomocą catch (Exception ex). W przeniesionym kodzie C++/WinRT znajdziesz wyrażenie catch (winrt::hresult_error const& ex). Aby uzyskać więcej informacji o winrt::hresult_error i sposobie pracy z nim, zobacz Obsługa błędów w języku C++/WinRT.
Przykładem testowania, czy obiekt w C# jest null czy nie, jest if (storageItems != null). W języku C++/WinRT możemy polegać na operatorze konwersji na bool, który wewnętrznie przeprowadza test przeciwko nullptr.
Oto nieco uproszczona wersja fragmentu kodu z przeniesionej wersji kodu C++/WinRT przykładu.
std::wostringstream output;
output << std::wstring_view(ApplicationData::Current().LocalFolder().Path());
Konstruowanie std::wstring_view z winrt::hstring w ten sposób ilustruje alternatywę wywołania funkcji hstring::c_str (aby przekształcić winrt::hstring w ciąg w stylu C). Ta alternatywa działa dzięki operatorowi konwersji std::wstring_view.
Rozważmy ten fragment języka C#.
var file = storageItem as StorageFile;
if (file != null)
...
Aby przeportować słowo kluczowe nullptr w przypadku niepowodzenia (abyśmy mogli obsłużyć ten warunek w kodzie), wtedy zamiast tego użyjemy funkcji try_as.
auto file{ storageItem.try_as<StorageFile>() };
if (file)
...
Skopiuj kod XAML niezbędny do zakończenia przenoszenia plików CopyFiles
Teraz możesz wybrać całą zawartość CopyFiles.xaml pliku z shared folderu pobranego oryginalnego przykładowego kodu źródłowego i wkleić go do CopyFiles.xaml pliku w projekcie C++/WinRT (zastępując istniejącą zawartość tego pliku w projekcie C++/WinRT).
Na koniec zmodyfikuj CopyFiles.h i .cpp i usuń tymczasową funkcję ClickHandler, ponieważ właśnie nadpisaliśmy odpowiedni znacznik XAML.
Zakończyliśmy przenoszenie plików CopyFiles, a jeśli stosowałeś się do opisanych kroków, twój projekt C++/WinRT będzie się teraz kompilować i uruchamiać, a scenariusz CopyFiles będzie działać.
SkopiujObraz
Aby przenieść typ strony CopyImage XAML, wykonaj ten sam proces co w przypadku CopyFiles. Podczas przenoszenia CopyImagemożna napotkać użycie języka C# przy użyciu instrukcji, co gwarantuje, że obiekty implementujące interfejs IDisposable są prawidłowo usuwane.
if (imageReceived != null)
{
using (var imageStream = await imageReceived.OpenReadAsync())
{
... // Pass imageStream to other APIs, and do other work.
}
}
Równoważny interfejs w języku C++/WinRT to IClosable, posiadający pojedynczą metodę Close. Oto odpowiednik C++/WinRT dla powyższego kodu C#.
if (imageReceived)
{
auto imageStream{ co_await imageReceived.OpenReadAsync() };
... // Pass imageStream to other APIs, and do other work.
imageStream.Close();
}
Obiekty C++/WinRT implementują funkcję IClosable przede wszystkim z korzyścią dla języków, które nie mają deterministycznej finalizacji. Język C++/WinRT ma deterministyczną finalizację, dlatego często nie musimy wywoływać funkcji IClosable::Close podczas pisania kodu C++/WinRT. Ale są momenty, kiedy dobrze tak to nazwać, i to jest właśnie jeden z takich momentów. W tym miejscu identyfikator imageStream jest otoczką ze zliczaniem referencji dla bazowego obiektu środowiska uruchomieniowego systemu Windows (w tym przypadku obiektu implementującego IRandomAccessStreamWithContentType). Chociaż możemy określić, że finalizator imageStream (jego destruktora) będzie uruchamiany na końcu otaczającego zakresu (nawiasy klamrowe), nie możemy być pewni, że finalizator wywoła Zamknij. Dzieje się tak, ponieważ przekazaliśmy imageStream do innych interfejsów API i nadal mogą one mieć wpływ na liczbę odwołań bazowego obiektu środowiska uruchomieniowego systemu Windows. Jest to więc przypadek, w którym dobrym pomysłem jest jawne wywołanie Close. Aby uzyskać więcej informacji, zobacz Czy muszę wywołać funkcję IClosable::Close w klasach środowiska uruchomieniowego, z których korzystam?.
Następnie rozważ wyrażenie C# (uint)(imageDecoder.OrientedPixelWidth * 0.5), które znajdziesz w procedurze obsługi zdarzenia OnDeferredImageRequestedHandler. To wyrażenie mnoży uint przez double, co daje double. Następnie rzutuje to na uint. W C++/WinRT możemy użyć podobnego do C rzutowania ((uint32_t)(imageDecoder.OrientedPixelWidth() * 0.5)), ale lepiej jest dokładnie wyjaśnić, jakiego rodzaju rzutowania chcemy użyć, a w tym przypadku zrobimy to za pomocą static_cast<uint32_t>(imageDecoder.OrientedPixelWidth() * 0.5).
Wersja języka C# elementu CopyImage.OnDeferredImageRequestedHandler ma klauzulę finally , ale nie klauzulę catch . Poszliśmy nieco dalej w wersji C++/WinRT i zaimplementowaliśmy klauzulę catch , abyśmy mogli zgłosić, czy opóźnione renderowanie zakończyło się pomyślnie.
Przenoszenie pozostałej części tej strony XAML nie daje niczego nowego do omówienia. Pamiętaj, aby usunąć fikcyjną funkcję ClickHandler . Podobnie jak w przypadku funkcji CopyFiles ostatnim krokiem w porcie jest wybranie całej zawartości CopyImage.xamlpliku i wklejenie go do tego samego pliku w projekcie C++/WinRT.
CopyText
Możesz przenosić CopyText.xaml i CopyText.xaml.cs przy użyciu technik, które już omówiliśmy.
HistoriaIRoaming
Podczas przenoszenia typu strony HistoryAndRoaming XAML pojawiają się pewne interesujące kwestie.
Najpierw przyjrzyj się kodowi źródłowemu C# i podążaj za przepływem kontroli od OnNavigatedTo przez obsługę zdarzenia OnHistoryEnabledChanged, aż do funkcji asynchronicznej CheckHistoryAndRoaming (która nie jest oczekiwana, więc zasadniczo działa w trybie „fire and forget”). Ponieważ CheckHistoryAndRoaming jest asynchroniczna, musimy zwrócić uwagę na cykl życia wskaźnika this przy używaniu C++/WinRT. Wynik można zobaczyć, jeśli przyjrzysz się implementacji w pliku kodu źródłowego HistoryAndRoaming.cpp . Po pierwsze, gdy dołączamy delegatów do zdarzeń Schowek::HistoryEnabledChanged i Schowek::RoamingEnabledChanged, bierzemy tylko słabe odwołanie do obiektu strony HistoryAndRoaming. Robimy to, tworząc delegata, który zależy od wartości zwróconej przez winrt::get_weak, zamiast polegać na wskaźniku this. Oznacza to, że sam delegat, który ostatecznie wywołuje kod asynchroniczny, nie utrzymuje aktywności strony HistoryAndRoaming, jeśli od niej odejdziemy.
A po drugie, kiedy w końcu dotrzemy do naszej autonomicznej procedury CheckHistoryAndRoaming coroutine, pierwszą rzeczą, jaką robimy, jest utrzymanie silnego odniesienia do this, aby zagwarantować, że strona HistoriaAndRoaming pozostanie aktywna przynajmniej do czasu, gdy procedura ostatecznie się zakończy. Aby uzyskać więcej informacji na temat obu opisanych aspektów, zobacz Silne i słabe odwołania w języku C++/WinRT.
Znajdujemy kolejny istotny punkt podczas przenoszenia CheckHistoryAndRoaming. Zawiera kod aktualizowania interfejsu użytkownika; dlatego musimy mieć pewność, że robimy to w głównym wątku interfejsu użytkownika. Wątek, który na początku wywołuje procedurę obsługi zdarzeń, jest głównym wątkiem interfejsu użytkownika. Jednak zazwyczaj metoda asynchroniczna może wykonywać się i/lub wznawiać na dowolnym wątku. W języku C# rozwiązaniem jest wywołanie metody CoreDispatcher.RunAsync i zaktualizowanie interfejsu użytkownika z poziomu funkcji lambda. W języku C++/WinRT możemy użyć funkcji winrt::resume_foreground
Odpowiednie wyrażenie to co_await winrt::resume_foreground(Dispatcher());. Alternatywnie, chociaż z mniejszą jasnością, możemy wyrazić to po prostu jako co_await Dispatcher();. Krótsza wersja jest osiągana dzięki uprzejmości operatora konwersji dostarczonego przez język C++/WinRT.
Przenoszenie pozostałej części tej strony XAML nie daje niczego nowego do omówienia. Pamiętaj, aby usunąć fikcyjną funkcję ClickHandler oraz pamiętaj, aby skopiować oznaczenia XAML.
otherScenarios
Możesz przenosić OtherScenarios.xaml i OtherScenarios.xaml.cs przy użyciu technik, które już omówiliśmy.
Podsumowanie
Mam nadzieję, że ten przewodnik uzbroił Cię w wystarczające informacje i techniki dotyczące przenoszenia, dzięki którym możesz teraz przenieść własne aplikacje języka C# do języka C++/WinRT. Dla przypomnienia, można nadal odwoływać się do wersji kodu źródłowego przed (C#) i po (C++/WinRT) w przykładzie schowka i porównywać je obok siebie, aby zobaczyć korespondencję.