Memindahkan sampel Clipboard ke C++/WinRT dari C#—studi kasus

Topik ini menyajikan studi kasus tentang porting salah satu sampel aplikasi Universal Windows Platform (UWP) dari C# ke C++/WinRT. Anda dapat memperoleh latihan dan pengalaman dalam melakukan porting dengan mengikuti panduan langkah demi langkah dan melakukan porting pada sampel sendiri seiring prosesnya.

Note

Kode sumber yang di-port adalah aplikasi UWP C#. Kode C++/WinRT tujuan dalam artikel ini ditulis untuk WinUI 3 (SDK Aplikasi Windows). Di mana pun sumber UWP menggunakan API yang berbeda di WinUI 3 (misalnya, Windows.UI.Core.CoreDispatcher vs. Microsoft.UI.Dispatching.DispatcherQueue), artikel ini secara eksplisit menunjukkan WinUI 3 yang benar yang setara dalam output C++/WinRT. Anda dapat menggunakan pola kode dari kolom C++/WinRT langsung di aplikasi WinUI 3 Anda.

Untuk katalog komprehensif detail teknis yang terlibat dalam porting ke C++/WinRT dari C#, lihat topik pendamping Pindahkan ke C++/WinRT dari C#.

Preface singkat tentang file kode sumber C# dan C++

Dalam proyek C#, file kode sumber Anda terutama berupa file .cs. Saat Anda pindah ke C++, Anda akan melihat bahwa ada lebih banyak jenis file kode sumber untuk membiasakan diri. Alasannya adalah untuk melakukan perbedaan antara pengkompilasi, cara kode sumber C++ digunakan kembali, dan gagasan mendeklarasikan dan menentukan jenis dan fungsinya (metodenya).

Deklarasi fungsi hanya menjelaskan tanda tangan fungsi (jenis pengembaliannya, namanya, serta jenis dan nama parameternya). Definisi fungsi mencakup isi fungsi (implementasinya).

Ini sedikit berbeda dalam hal jenis. Anda menentukan jenis dengan memberikan namanya dan dengan (minimal) hanya mendeklarasikan semua fungsi anggotanya (dan anggota lainnya). Itu benar, Anda dapat menentukan jenis bahkan jika Anda tidak menentukan fungsi anggotanya.

  • Berkas kode sumber C++ yang umum adalah .h (titik h) dan berkas .cpp. File .h adalah file header , dan mendefinisikan satu atau beberapa jenis. Meskipun Anda dapat menentukan fungsi anggota di header, biasanya itulah tujuan .cpp file. Jadi untuk jenis C++ hipotetis MyClass, Anda akan menentukan MyClass di MyClass.h, dan Anda akan menentukan fungsi anggotanya di MyClass.cpp. Agar pengembang lain dapat menggunakan kembali kelas Anda, Anda hanya akan membagikan file .h dan kode objek. Anda akan merahasiakan file Anda .cpp , karena implementasinya merupakan kekayaan intelektual Anda.
  • Header yang telah dikompilasi sebelumnya (pch.h). Biasanya ada sekumpulan file header yang Anda sertakan dalam aplikasi Anda, dan Anda tidak sering mengubah file tersebut. Jadi alih-alih memproses konten kumpulan header tersebut setiap kali Anda mengkompilasi, Anda dapat menggabungkan header tersebut ke dalam satu file, mengkompilasinya sekali, lalu menggunakan output langkah prakompilasi tersebut setiap kali Anda membangun. Anda melakukannya melalui file header yang telah dikommpilasikan sebelumnya (biasanya bernama pch.h).
  • .idl file. File-file ini berisi Bahasa Definisi Antarmuka (IDL). Anda dapat menganggap IDL sebagai file header untuk jenis Windows Runtime. Kita akan berbicara lebih lanjut tentang IDL di bagian IDL untuk jenis MainPage.

Mengunduh dan menguji sampel Clipboard

Kunjungi halaman web sampel Clipboard , dan klik Unduh ZIP. Unzip file yang diunduh, dan lihat struktur folder.

  • Versi C# dari kode sumber sampel terkandung dalam folder bernama cs.
  • Versi C++/WinRT dari kode sumber sampel terkandung dalam folder bernama cppwinrt.
  • File lain—digunakan oleh versi C# dan versi C++/WinRT—dapat ditemukan di shared folder dan SharedContent .

Panduan dalam topik ini menunjukkan bagaimana Anda dapat membuat ulang versi C++/WinRT dari sampel Clipboard dengan memindahkannya dari kode sumber C#. Dengan begitu, Anda dapat melihat bagaimana Anda dapat memindahkan proyek C# Anda sendiri ke C++/WinRT.

Untuk merasakan apa yang dilakukan sampel, buka solusi C# (\Clipboard_sample\cs\Clipboard.sln), ubah konfigurasi yang sesuai (mungkin ke x64), bangun, dan jalankan. Antarmuka pengguna (UI) sampel sendiri memandu Anda melalui berbagai fiturnya, langkah demi langkah.

Tip

Folder akar sampel yang Anda unduh mungkin dinamai Clipboard bukan Clipboard_sample. Tetapi kami akan terus merujuk ke folder Clipboard_sample tersebut untuk membedakannya dari versi C++/WinRT yang akan Anda buat di langkah selanjutnya.

Membuat Aplikasi Kosong, bernama Clipboard

Note

Untuk informasi tentang menginstal dan menggunakan C++/WinRT Visual Studio Extension (VSIX) dan paket NuGet (yang bersama-sama menyediakan templat proyek dan dukungan build), lihat dukungan Visual Studio untuk C++/WinRT.

Mulai proses porting dengan membuat proyek C++/WinRT baru di Microsoft Visual Studio. Buat proyek baru menggunakan templat proyek C++ Blank App, Packaged (WinUI 3 in Desktop). Atur namanya ke Clipboard, dan (sehingga struktur folder Anda akan cocok dengan penelusuran) pastikan bahwa Tempatkan solusi dan proyek di direktori yang sama tidak dicentang.

Sebagai titik acuan awal, pastikan proyek baru yang masih kosong ini dapat dibangun dan dijalankan.

File Package.appxmanifest, dan file aset

Jika versi C# dan C++/WinRT dari sampel tidak perlu diinstal berdampingan pada komputer yang sama, maka file sumber manifes paket aplikasi dua proyek (Package.appxmanifest) dapat identik. Dalam hal ini, Anda hanya dapat menyalin Package.appxmanifest dari proyek C# ke proyek C++/WinRT, dan Anda selesai.

Agar dua versi sampel berdampingan, mereka membutuhkan pengidentifikasi yang berbeda. Dalam hal ini, dalam proyek C++/WinRT, buka Package.appxmanifest file di editor XML, dan catat ketiga nilai ini.

  • Di dalam elemen /Package/Identity , perhatikan nilai atribut Name . Ini adalah nama paket. Untuk proyek yang baru dibuat, proyek tersebut akan diberi nilai awal berupa GUID unik.
  • Di dalam elemen /Package/Applications/Application , perhatikan nilai atribut Id . Ini adalah id aplikasi.
  • Di dalam elemen /Package/mp:PhoneIdentity , perhatikan nilai atribut PhoneProductId . Sekali lagi, untuk proyek yang baru dibuat, ini akan disetel ke GUID yang sama dengan yang disetel untuk nama paket.

Kemudian salin Package.appxmanifest dari proyek C# ke proyek C++/WinRT. Terakhir, Anda dapat memulihkan tiga nilai yang Anda catat. Atau Anda dapat mengedit nilai yang disalin untuk membuatnya unik dan/atau sesuai untuk aplikasi dan untuk organisasi Anda (seperti yang biasanya Anda lakukan untuk proyek baru). Misalnya, dalam hal ini alih-alih memulihkan nilai nama paket, kita hanya dapat mengubah nilai yang disalin dari Microsoft. SDKSamples.Clipboard.CS ke Microsoft. SDKSamples.Clipboard.CppWinRT. Dan kita dapat membiarkan ID aplikasi disetel ke App. Selama nama paket atau id aplikasi berbeda, kedua aplikasi akan memiliki ID Model Pengguna Aplikasi (AUMID) yang berbeda. Dan itulah yang diperlukan agar dua aplikasi diinstal berdampingan pada komputer yang sama.

Untuk tujuan panduan ini, masuk akal untuk membuat beberapa perubahan lain di Package.appxmanifest. Ada tiga kemunculan dari string Clipboard C# Sample. Ubah itu menjadi Sampel Clipboard C++/WinRT.

Dalam proyek C++/WinRT, Package.appxmanifest file dan proyek sekarang tidak sinkron sehubungan dengan file aset yang mereka referensikan. Untuk memperbaikinya, pertama-tama hapus aset dari proyek C++/WinRT dengan memilih semua file dalam Assets folder (dalam Penjelajah Solusi di Visual Studio) dan menghapusnya (pilih Hapus dalam dialog).

Proyek C# mereferensikan file aset dari folder bersama. Anda dapat melakukan hal yang sama dalam proyek C++/WinRT, atau Anda dapat menyalin file seperti yang akan kita lakukan dalam panduan ini.

Navigasikan ke \Clipboard_sample\SharedContent\media folder . Pilih tujuh file yang disertakan proyek C# (microsoft-sdk.png, , smalltile-sdk.png, splash-sdk.png, squaretile-sdk.pngstorelogo-sdk.png, tile-sdk.png, dan windows-sdk.png), salin, dan tempelkan ke \Clipboard\Clipboard\Assets folder dalam proyek baru.

Assets Klik kanan folder (dalam Penjelajah Solusi di proyek C++/WinRT) >Tambahkan>item yang Ada... dan navigasi ke \Clipboard\Clipboard\Assets. Di pemilih file, pilih tujuh file dan klik Tambahkan.

Package.appxmanifest sekarang kembali sinkron dengan file aset proyek.

MainPage, termasuk fungsionalitas yang mengonfigurasi sampel

Sampel Clipboard—seperti semua sampel aplikasi Universal Windows Platform (UWP)—terdiri dari kumpulan skenario yang dapat dilalui pengguna satu per satu. Kumpulan skenario dalam sampel tertentu dikonfigurasi dalam kode sumber sampel. Setiap skenario dalam koleksi adalah item data yang menyimpan judul, serta jenis kelas dalam proyek yang mengimplementasikan skenario.

Dalam versi C# sampel, jika Anda melihat dalam SampleConfiguration.cs file kode sumber, Anda akan melihat dua kelas. Sebagian besar logika konfigurasi berada di kelas MainPage , yang merupakan kelas parsial (membentuk kelas lengkap ketika dikombinasikan dengan markup di MainPage.xaml dan kode imperatif di MainPage.xaml.cs). Kelas lain dalam file kode sumber ini adalah Skenario, dengan properti Judul dan ClassType-nya .

Selama beberapa subbagian berikutnya, kita akan melihat cara mem-port MainPage dan Skenario.

IDL untuk tipe MainPage

Mari kita mulai bagian ini dengan berbicara secara singkat tentang Bahasa Definisi Antarmuka (IDL), dan bagaimana hal itu membantu kita ketika kita memprogram dengan C++/WinRT. IDL adalah semacam kode sumber yang menjelaskan permukaan yang dapat dipanggil dari jenis Windows Runtime. Permukaan jenis yang dapat dipanggil (atau publik) diproyeksikan ke seluruh dunia, sehingga jenisnya dapat dikonsumsi. Bagian yang ditampilkan dari tipe tersebut berbeda dengan implementasi internal sebenarnya dari tipe itu, yang tentu saja tidak bisa dipanggil dan tidak bersifat publik. Hanya bagian yang diproyeksikan yang kita tentukan dalam IDL.

Setelah menulis kode sumber IDL (dalam .idl file), Anda kemudian dapat mengkompilasi IDL ke dalam file metadata yang dapat dibaca mesin (juga dikenal sebagai metadata Windows). File metadata tersebut memiliki ekstensi .winmd, dan berikut adalah beberapa kegunaannya.

  • .winmd Dapat menjelaskan jenis Windows Runtime dalam komponen. Saat Anda mereferensikan komponen Windows Runtime (WRC) dari proyek aplikasi, proyek aplikasi membaca Metadata Windows milik WRC (metadata tersebut mungkin berada dalam file terpisah, atau mungkin dimasukan ke dalam file yang sama dengan WRC itu sendiri) sehingga Anda dapat menggunakan jenis WRC dari dalam aplikasi.
  • Sebuah .winmd dapat mendeskripsikan tipe Windows Runtime dalam satu bagian aplikasi Anda sehingga dapat digunakan oleh bagian lain dari aplikasi yang sama. Misalnya, tipe Windows Runtime yang dipakai oleh halaman XAML di aplikasi yang sama.
  • Untuk memudahkan Anda menggunakan jenis Windows Runtime (bawaan atau pihak ketiga), sistem build C++/WinRT menggunakan .winmd file untuk menghasilkan jenis pembungkus untuk mewakili bagian yang diproyeksikan dari jenis Windows Runtime tersebut.
  • Untuk mempermudah Anda menerapkan jenis Windows Runtime Anda sendiri, sistem build C++/WinRT mengubah IDL Anda menjadi .winmd file, lalu menggunakannya untuk menghasilkan pembungkus untuk proyeksi Anda, serta stub untuk mendasarkan implementasi Anda (kita akan berbicara lebih banyak tentang bongkahan ini nanti dalam topik ini).

Versi spesifik IDL yang kami gunakan dengan C++/WinRT adalah Microsoft Bahasa Definisi Antarmuka 3.0. Di sisa bagian topik ini, kita akan memeriksa jenis C# MainPage dalam beberapa detail. Kita akan memutuskan bagian mana yang perlu berada dalam proyeksi tipe C++/WinRT MainPage (yaitu, dalam antarmuka yang dapat dipanggil, atau publik), dan bagian mana yang cukup menjadi bagian dari implementasinya. Perbedaan itu penting karena ketika kita mulai menyusun IDL (yang akan kita lakukan di bagian berikutnya), kita hanya akan mendefinisikan bagian-bagian yang dapat dipanggil di dalamnya.

File kode sumber C# yang bersama-sama mengimplementasikan jenis MainPage adalah: MainPage.xaml (yang akan segera kita port, dengan menyalinnya), MainPage.xaml.cs, dan SampleConfiguration.cs.

Dalam versi C++/WinRT, kita akan memperhitungkan jenis MainPage kita ke dalam file kode sumber dengan cara yang sama. Kami akan mengambil logika yang ada di MainPage.xaml.cs dan menerjemahkannya sebagian besar ke MainPage.h dan MainPage.cpp. Dan untuk logika di SampleConfiguration.cs, kita akan menerjemahkannya ke SampleConfiguration.h dan SampleConfiguration.cpp.

Kelas dalam aplikasi C# Universal Windows Platform (UWP) tentu saja merupakan tipe Windows Runtime. Tetapi ketika Anda menulis jenis dalam aplikasi C++/WinRT, Anda dapat memilih apakah jenis tersebut adalah jenis Windows Runtime, atau kelas/struktur/enumerasi C++ biasa.

Setiap halaman XAML dalam proyek kami harus menjadi jenis Windows Runtime, jadi MainPage harus menjadi jenis Windows Runtime. Dalam proyek C++/WinRT, MainPage sudah merupakan jenis Windows Runtime, jadi kita tidak perlu mengubah aspek itu. Secara khusus, ini adalah kelas runtime.

  • Untuk detail selengkapnya tentang apakah Anda harus menulis kelas runtime untuk jenis tertentu atau tidak, lihat topik API Penulis dengan C++/WinRT.
  • Di C++/WinRT, implementasi internal kelas runtime, dan bagian yang diproyeksikan (publik), ada dalam bentuk dua kelas yang berbeda. Ini dikenal sebagai jenis implementasi dan jenis yang diproyeksikan. Anda dapat mempelajari lebih lanjut tentang mereka dalam topik yang disebutkan di poin sebelumnya, dan juga di Menggunakan API dengan C++/WinRT.
  • Untuk informasi selengkapnya tentang hubungan antara kelas runtime dan IDL (file .idl), Anda dapat membaca dan mengikuti topik Kontrol XAML; mengikat ke properti C++/WinRT. Topik itu menjelaskan proses penulisan kelas runtime baru, langkah pertama adalah menambahkan item File Midl (.idl) baru ke proyek.

Untuk MainPage, kami sebenarnya sudah memiliki file yang diperlukan MainPage.idl dalam proyek C++/WinRT. Itu karena templat proyek membuatnya untuk kita. Namun nanti dalam panduan ini kita akan menambahkan file .idl tambahan ke dalam proyek.

Kita akan segera melihat daftar persis IDL yang perlu kita tambahkan ke file MainPage.idl yang ada. Sebelum itu, kita perlu mempertimbangkan apa yang perlu, dan apa yang tidak perlu, dimasukkan ke dalam IDL.

Untuk menentukan anggota MainPage mana yang perlu kami deklarasikan MainPage.idl (sehingga mereka menjadi bagian dari kelas runtime MainPage ), dan yang hanya dapat menjadi anggota jenis implementasi MainPage , mari kita buat daftar anggota kelas C# MainPage . Kami menemukan anggota tersebut dengan mencari di MainPage.xaml.cs dan di SampleConfiguration.cs.

Kami menemukan total dua belas protected bidang dan private metode. Dan kami menemukan anggota berikut public .

  • Konstruktor bawaan MainPage().
  • Bidang statis Saat ini dan FEATURE_NAME.
  • properti IsClipboardContentChangedEnabled dan Scenarios.
  • Metode BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotifications, dan NotifyUser.

Anggota public itulah yang menjadi kandidat untuk dideklarasikan dalam MainPage.idl. Jadi mari kita periksa masing-masing dan lihat apakah mereka perlu menjadi bagian dari kelas runtime MainPage , atau apakah mereka hanya perlu menjadi bagian dari implementasinya.

  • Konstruktor default MainPage(). Untuk Page XAML, merupakan hal yang umum untuk mendeklarasikan konstruktor default dalam IDL. Dengan begitu, kerangka kerja UI XAML dapat mengaktifkan jenisnya.
  • Field statis Current digunakan di dalam halaman XAML skenario individual untuk mengakses instance MainPage milik aplikasi. Karena Current tidak digunakan untuk berinteroperasi dengan kerangka kerja XAML (dan juga tidak digunakan antar unit kompilasi), kita dapat mencadangkannya agar hanya menjadi anggota tipe implementasi. Dengan proyek Anda sendiri dalam kasus seperti ini, Anda dapat memilih untuk melakukannya. Namun, karena field tersebut merupakan instance dari tipe hasil proyeksi, terasa logis untuk mendeklarasikannya dalam IDL. Jadi itulah yang akan kita lakukan di sini (dan melakukannya juga membuat kode sedikit lebih bersih).
  • Ini adalah kasus serupa untuk bidang FEATURE_NAME statis, yang diakses dalam jenis MainPage . Sekali lagi, memilih untuk mendeklarasikannya di IDL membuat kode kita sedikit lebih bersih.
  • Properti IsClipboardContentChangedEnabled hanya digunakan di kelas OtherScenarios . Jadi selama port, kita akan menyederhanakan hal-hal sedikit, dan menjadikannya bidang privat dari kelas runtime OtherScenarios . Jadi yang satu tidak akan masuk ke IDL.
  • Skenario properti adalah kumpulan objek jenis Skenario (jenis yang kami sebutkan sebelumnya). Kita akan berbicara tentang Skenario di sub-bagian berikutnya, jadi mari kita tinggalkan properti Skenario sampai saat itu juga.
  • Metode BuildClipboardFormatsOutputString, DisplayToast, dan EnableClipboardContentChangedNotifications adalah fungsi utilitas yang terasa lebih berkaitan dengan status umum sampel daripada tentang halaman utama. Jadi selama port, kita akan merefaktor ketiga metode ini ke jenis utilitas baru bernama SampleState (yang tidak perlu menjadi jenis Windows Runtime). Untuk alasan itu, ketiga metode ini tidak akan masuk ke IDL.
  • Metode NotifyUser dipanggil dari masing-masing halaman XAML skenario pada instans MainPage yang dikembalikan dari field statis Current. Karena (seperti yang telah disebutkan) Current adalah instans dari tipe yang diproyeksikan, kita perlu mendeklarasikan NotifyUser di IDL. NotifyUser mengambil parameter jenis NotifyType. Kita akan membicarakannya di sub-bagian berikutnya.

Setiap anggota yang ingin Anda ikat datanya juga perlu dideklarasikan dalam IDL (baik Anda menggunakan {x:Bind} atau {Binding}). Untuk informasi selengkapnya, lihat Pengikatan data.

Kami membuat kemajuan: kami mengembangkan daftar anggota mana yang akan ditambahkan, dan yang tidak akan ditambahkan, ke MainPage.idl file. Tetapi kita masih harus membahas properti Skenario , dan jenis NotifyType . Jadi, mari kita lakukan itu selanjutnya.

IDL untuk tipe Skenario dan NotifyType

Kelas Skenario didefinisikan dalam SampleConfiguration.cs. Kami memiliki keputusan untuk membuat tentang cara memindahkan kelas tersebut ke C++/WinRT. Secara default, kita mungkin akan menjadikannya C++ structbiasa. Tetapi jika Skenario digunakan di berbagai biner, atau untuk berinteroperasi dengan kerangka kerja XAML, maka Skenario harus dideklarasikan dalam IDL sebagai tipe Windows Runtime.

Mempelajari kode sumber C#, kami menemukan bahwa Skenario digunakan dalam konteks ini.

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

Kumpulan objek Skenario sedang ditetapkan ke properti ItemsSource dari ListBox (yang merupakan kontrol item). Karena Skenario perlu berinteroperasi dengan XAML, skenario tersebut harus merupakan tipe Windows Runtime. Jadi perlu didefinisikan dalam IDL. Menentukan jenis Skenario di IDL menyebabkan sistem build C++/WinRT menghasilkan definisi kode sumber Skenario untuk Anda dalam file header di balik layar (nama dan lokasi yang tidak penting untuk panduan ini).

Dan Anda akan ingat bahwa MainPage.Scenarios adalah kumpulan objek Skenario , yang baru saja kami katakan perlu berada di IDL. Untuk itu, MainPage.Scenarios sendiri juga perlu dideklarasikan dalam IDL.

NotifyType adalah sebuah enum yang dideklarasikan dalam MainPage.xaml.cs C#. Karena kami meneruskan NotifyType ke metode milik kelas runtime MainPage, NotifyType juga perlu menjadi jenis Windows Runtime; dan perlu didefinisikan dalam MainPage.idl.

Sekarang mari kita tambahkan ke file MainPage.idl tipe baru dan anggota baru dari Mainpage yang telah kita putuskan untuk dideklarasikan dalam IDL. Pada saat yang sama, kita akan menghapus dari IDL anggota placeholder Mainpage yang ditambahkan oleh templat proyek Visual Studio.

Jadi, dalam proyek C++/WinRT Anda, buka MainPage.idl, dan edit sehingga terlihat seperti daftar di bawah ini. Perhatikan bahwa salah satu pengeditannya adalah mengubah nama namespace layanan dari Clipboard ke SDKTemplate. Jika mau, Anda cukup mengganti seluruh konten MainPage.idl dengan kode berikut. Tweak lain yang perlu diperhatikan adalah kami mengubah nama Skenario::ClassType menjadi Skenario::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

Untuk informasi selengkapnya tentang konten .idl file dalam proyek C++/WinRT, lihat Microsoft Bahasa Definisi Antarmuka 3.0.

Dengan pekerjaan porting Anda sendiri, Anda mungkin tidak ingin atau perlu mengubah nama namespace seperti yang kami lakukan di atas. Kami melakukannya di sini hanya karena namespace default proyek C# yang kami porting adalah SDKTemplate; sementara nama proyek dan rakitannya adalah Clipboard.

Tapi, saat kita melanjutkan dengan port dalam panduan ini, kita akan mengubah setiap kemunculan dalam kode sumber nama namespace Clipboard menjadi SDKTemplate. Ada juga tempat di properti proyek C++/WinRT tempat nama namespace Clipboard muncul, jadi kita akan mengambil kesempatan untuk mengubahnya sekarang.

Dalam Visual Studio, untuk proyek C++/WinRT, atur properti proyek Properti Umum>C++/WinRT>Namespace Akar ke nilai SDKTemplate.

Simpan IDL, dan buat ulang file stub

Topik kontrol XAML; mengikat ke properti C++/WinRT memperkenalkan konsep berkas stub, dan menunjukkan panduan langkah demi langkah tentang penggunaannya. Kami juga menyebutkan stub sebelumnya dalam topik ini ketika kami menyebutkan bahwa sistem build C++/WinRT mengubah konten file Anda .idl menjadi metadata Windows, dan kemudian dari metadata tersebut alat bernama cppwinrt.exe menghasilkan stub tempat Anda dapat mendasarkan implementasi Anda.

Setiap kali Anda menambahkan, menghapus, atau mengubah sesuatu di IDL Anda, dan membangun, sistem build memperbarui implementasi stub dalam file stub tersebut. Jadi setiap kali Anda mengubah IDL dan build, kami sarankan Anda melihat file stub tersebut, menyalin tanda tangan yang diubah, dan menempelkannya ke dalam proyek Anda. Kami akan memberikan lebih spesifik dan contoh tentang cara melakukannya dalam sesaat. Namun, keuntungan dari melakukan ini adalah memberi Anda cara yang bebas dari kesalahan untuk selalu mengetahui seperti apa struktur tipe implementasi Anda seharusnya, dan seperti apa tanda tangan metodenya seharusnya.

Pada titik ini dalam panduan, kami sudah selesai mengedit MainPage.idl file untuk saat ini, jadi Anda harus menyimpannya sekarang. Proyek ini tidak akan dibangun hingga selesai saat ini, tetapi melakukan build sekarang adalah hal yang berguna untuk dilakukan karena meregenerasi file stub untuk MainPage. Jadi bangun proyek sekarang, dan abaikan kesalahan build apa pun.

Untuk proyek C++/WinRT ini, file stub dihasilkan di \Clipboard\Clipboard\Generated Files\sources folder . Anda akan menemukannya di sana setelah build parsial selesai (sekali lagi, seperti yang diharapkan, build tidak akan berhasil sepenuhnya. Tetapi langkah yang kita minati—menghasilkan stub—akan berhasil). File yang kami minati adalah MainPage.h dan MainPage.cpp.

Dalam dua file stub tersebut, Anda akan melihat implementasi stub baru dari anggota MainPage yang kami tambahkan ke IDL (Saat ini dan FEATURE_NAME, misalnya). Anda perlu menyalin implementasi stub tersebut ke file MainPage.h dan MainPage.cpp yang sudah ada di proyek. Pada saat yang sama, seperti yang kami lakukan dengan IDL, kami akan menghapus anggota placeholder dari MainPage yang ditambahkan oleh templat proyek Visual Studio dari file-file yang sudah ada (properti dummy bernama MyProperty, dan penangan peristiwa bernama ClickHandler).

Bahkan, satu-satunya anggota versi MainPage saat ini yang ingin kita simpan adalah konstruktor.

Setelah Anda menyalin anggota baru dari file stub, menghapus anggota yang tidak diinginkan, dan memperbarui namespace, file MainPage.h dan MainPage.cpp dalam proyek Anda akan tampak seperti cuplikan kode di bawah ini. Perhatikan bahwa ada dua jenis MainPage . Satu di namespace implementasi , dan yang kedua di namespace factory_implementation . Satu-satunya perubahan yang telah kita lakukan pada factory_implementation adalah menambahkan SDKTemplate ke namespace layanannya.

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

Untuk string, C# menggunakan System.String. Lihat metode MainPage.NotifyUser misalnya. Dalam IDL kami, kami mendeklarasikan string dengan String, dan ketika cppwinrt.exe alat menghasilkan kode C++/WinRT untuk kami, alat ini menggunakan jenis winrt::hstring . Setiap kali kita menemukan string dalam kode C#, kita akan port itu ke winrt::hstring. Untuk informasi selengkapnya, lihat Penanganan string di C++/WinRT.

Untuk penjelasan tentang parameter const& dalam signature metode, lihat Penerusan parameter.

Perbarui semua deklarasi dan referensi namespace yang tersisa, lalu kompilasi

Sebelum membangun proyek C++/WinRT, temukan deklarasi (dan referensi ke) namespace Clipboard , dan ubah menjadi SDKTemplate.

  • MainPage.xaml dan App.xaml. Namespace muncul dalam nilai atribut x:Class dan xmlns:local.
  • App.idl.
  • App.h.
  • App.cpp. Ada dua direktif using namespace (cari substring using namespace Clipboard), dan dua kualifikasi bertipe MainPage (cari Clipboard::MainPage). Mereka perlu berubah.

Karena kami menghapus penanganan aktivitas dari MainPage, juga masuk ke MainPage.xaml dan hapus elemen Tombol dari markup.

Simpan semua file. Bersihkan solusi (Kompilasi>Bersihkan Solusi), lalu kompilasikan. Setelah mengikuti semua perubahan sejauh ini, tepat seperti yang tertulis, proses build diharapkan berhasil.

Implementasikan anggota MainPage yang telah kita deklarasikan dalam IDL

Konstruktor, Saat Ini, dan FEATURE_NAME

Berikut adalah kode yang relevan (dari proyek C#) yang perlu kita porting.

<!-- 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";
...
}
...

Segera, kita akan menggunakan MainPage.xaml kembali secara keseluruhan (dengan menyalinnya). Untuk saat ini (di bawah), kami akan menambahkan elemen TextBlock untuk sementara, dengan nama yang sesuai, ke dalam MainPage.xaml proyek C++/WinRT.

FEATURE_NAME adalah bidang statis dari MainPage (bidang C# const pada dasarnya statis dalam perilakunya), didefinisikan dalam SampleConfiguration.cs. Untuk C++/WinRT, alih-alih bidang (statis), kita akan menjadikannya ekspresi C++/WinRT dari properti baca-saja (statis). Cara C++/WinRT untuk menyatakan getter properti adalah dengan fungsi yang mengembalikan nilai properti dan tidak menerima parameter (pengakses). Jadi bidang statis C# FEATURE_NAME menjadi fungsi aksesor statis C++/WinRT FEATURE_NAME (dalam hal ini, mengembalikan string literal).

Kebetulan, kami akan melakukan hal yang sama jika kami memindahkan properti baca-saja C#. Untuk properti C# yang dapat ditulis, cara C++/WinRT untuk mengekspresikan setter properti adalah sebagai void fungsi yang mengambil nilai properti sebagai parameter (mutator). Dalam kedua hal tersebut, jika bidang atau properti C# bersifat statis, maka aksesor dan/atau mutator C++/WinRT juga bersifat statis.

Saat ini adalah bidang statis (bukan konstanta) dari MainPage. Sekali lagi, kita akan menjadikannya (dalam bentuk ekspresi C++/WinRT) sebagai properti hanya-baca, dan sekali lagi menjadikannya statis. Jika FEATURE_NAME konstan, Saat ini tidak. Jadi di C++/WinRT kita akan membutuhkan bidang dukungan, dan aksesor kita akan mengembalikannya. Jadi, dalam proyek C++/WinRT, kami akan mendeklarasikan di MainPage.h bidang statis privat bernama saat ini, kami akan menentukan/menginisialisasi saat ini di MainPage.cpp (karena memiliki durasi penyimpanan statis), dan kami akan mengaksesnya melalui fungsi aksesor statis publik bernama Current.

Konstruktor itu sendiri melakukan beberapa tugas, yang mudah untuk port.

Dalam proyek C++/WinRT, tambahkan item Visual C++>Code>C++ File (.cpp) baru dengan nama SampleConfiguration.cpp.

Edit MainPage.xaml, MainPage.h, MainPage.cpp, dan SampleConfiguration.cpp agar sesuai dengan daftar di bawah ini.

<!-- 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";
}

Selain itu, pastikan untuk menghapus badan fungsi yang ada dari MainPage.cpp untuk MainPage::Current() dan MainPage::FEATURE_NAME(), karena kita sekarang mendefinisikan metode tersebut di tempat lain.

Seperti yang Anda lihat, MainPage::current dinyatakan sebagai jenis SDKTemplate::MainPage, yang merupakan jenis yang diproyeksikan. Ini bukan jenis SDKTemplate::implementation::MainPage, yang merupakan jenis implementasi. Jenis hasil proyeksi adalah jenis yang dirancang untuk digunakan baik di dalam proyek untuk interoperabilitas XAML maupun lintas biner. Jenis implementasi adalah jenis yang Anda gunakan untuk menerapkan fungsionalitas yang telah Anda tampilkan pada tipe terproyeksi. Karena deklarasi MainPage::current (dalam MainPage.h) berada di dalam namespace implementasi (winrt::SDKTemplate::implementation), MainPage tanpa kualifikasi akan merujuk ke tipe implementasi. Jadi, kami memenuhi syarat dengan SDKTemplate:: agar lebih jelas bahwa kami ingin MainPage::current menjadi instans dari jenis yang diproyeksikan winrt::SDKTemplate::MainPage.

Dalam konstruktor, ada beberapa hal yang terkait dengan MainPage::current = *this; yang perlu dijelaskan.

  • Saat Anda menggunakan pointer this di dalam anggota dari tipe implementasi, pointer this tentu saja merupakan pointer ke tipe implementasi.
  • Untuk mengonversi penunjuk this menjadi tipe terproyeksi yang sesuai, lakukan dereferensi padanya. Asalkan Anda menghasilkan tipe implementasi dari IDL (seperti pada contoh ini), tipe implementasi tersebut memiliki operator konversi yang mengonversinya menjadi tipe proyeksinya. Itu sebabnya tugas di sini berfungsi.

Untuk informasi selengkapnya mengenai detail tersebut, lihat Membuat instance dan mengembalikan tipe implementasi dan antarmuka.

Di dalam konstruktor juga ada SampleTitle().Text(FEATURE_NAME());. Bagian SampleTitle() adalah pemanggilan fungsi aksesor sederhana bernama SampleTitle, yang mengembalikan TextBlock yang kami tambahkan ke dalam XAML. Setiap kali Anda x:Name elemen XAML, pengompilasi XAML akan menghasilkan aksesor untuk Anda yang diberi nama berdasarkan elemen tersebut. Bagian .Text(...) memanggil fungsi Mutator Teks pada objek TextBlock yang dikembalikan oleh pengakses SampleTitle . Lalu FEATURE_NAME() memanggil fungsi accessor statis kami, MainPage::FEATURE_NAME, untuk mengembalikan literal string. Secara keseluruhan, baris kode tersebut mengatur properti Text pada TextBlock bernama SampleTitle.

Perhatikan bahwa karena string di Windows Runtime menggunakan karakter lebar, untuk mem-porting literal string kita menambahkan prefiks pengodean karakter lebar L. Jadi kita mengubah (misalnya) "string literal" menjadi L"string literal". Lihat juga Literal string luas.

Skenario

Berikut adalah kode C# relevan yang perlu kita porting.

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

Dari penyelidikan kami sebelumnya, kami tahu bahwa kumpulan objek Skenario ini ditampilkan di ListBox. Di C++/WinRT, ada batasan jenis koleksi yang dapat kami tetapkan ke properti ItemsSource kontrol item. Koleksi harus berupa vektor atau vektor yang dapat diamati, dan elemennya harus salah satu dari yang berikut:

Untuk kasus IInspectable, jika elemen-elemen itu sendiri bukan kelas runtime, maka elemen-elemen tersebut harus merupakan jenis yang dapat di-boxing ke IInspectable dan di-unboxing dari IInspectable. Dan itu berarti bahwa mereka harus berupa tipe Windows Runtime (lihat Boxing dan unboxing nilai ke IInspectable).

Untuk studi kasus ini, kami tidak menjadikan Skenario sebagai kelas runtime. Itu masih pilihan yang masuk akal, meskipun. Dan akan ada situasi dalam pekerjaan porting Anda sendiri di mana kelas runtime jelas merupakan pilihan yang tepat. Misalnya, jika Anda perlu membuat jenis elemen dapat diamati (lihat kontrol XAML; mengikat properti C++/WinRT), atau jika elemen perlu memiliki metode karena alasan lain, dan itu lebih dari sekadar sekumpulan anggota data.

Karena dalam panduan ini kita tidak akan menggunakan kelas runtime untuk tipe Skenario, maka kita perlu mempertimbangkan boxing. Jika kita membuat Skenario menjadi C++ biasa struct, maka kita tidak akan bisa membox-nya. Tapi kami mendeklarasikan Skenario sebagai struct di IDL, dan karena itu kami dapat membungkusnya.

Kita dihadapkan pada pilihan untuk membungkus Skenario terlebih dahulu, atau menunggu sampai kita hendak menetapkannya ke ItemsSource lalu membungkusnya secara just-in-time. Berikut adalah beberapa pertimbangan mengenai dua opsi tersebut.

  • Tinju sebelumnya. Untuk opsi ini, member data kami berupa kumpulan IInspectable yang siap ditetapkan ke antarmuka pengguna. Pada inisialisasi, kami mengemas objek Skenario ke dalam anggota data tersebut. Kita hanya perlu satu salinan koleksi itu, tetapi kita harus membuka kotak elemen setiap kali kita perlu membaca bidangnya.
  • Tinju tepat pada waktunya. Untuk opsi ini, anggota data kami adalah kumpulan Skenario. Ketika saatnya tiba untuk menetapkannya ke UI, kami membungkus objek Scenario dari anggota data ke dalam koleksi baru IInspectable. Kita dapat membaca bidang elemen di anggota data tanpa membuka kotak, tetapi kita memerlukan dua salinan koleksi.

Seperti yang Anda lihat, untuk koleksi kecil seperti ini, kelebihan dan kekurangannya kurang lebih seimbang. Jadi, untuk studi kasus ini, kita akan memilih opsi just-in-time.

Anggota skenario adalah bidang dalam MainPage, yang didefinisikan dan diinisialisasi di SampleConfiguration.cs. Dan Skenario adalah properti baca-saja dari MainPage, yang didefinisikan dalam MainPage.xaml.cs (dan diimplementasikan untuk hanya mengembalikan bidang skenario ). Kita akan melakukan sesuatu yang serupa dalam proyek C++/WinRT; tetapi kita akan membuat dua anggota statis (karena kita hanya membutuhkan satu instans di seluruh aplikasi; dan sehingga kita dapat mengaksesnya tanpa memerlukan instans kelas). Dan kita akan menamainya scenariosInner dan scenarios, masing-masing. Kami akan mendeklarasikan skenarioInner di MainPage.h. Dan, karena memiliki durasi penyimpanan statis, kami akan menentukan/menginisialisasinya dalam .cpp file (SampleConfiguration.cpp, dalam hal ini).

Edit MainPage.h dan SampleConfiguration.cpp agar sesuai dengan daftar di bawah ini.

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

Selain itu, pastikan untuk menghapus isi fungsi yang ada dari MainPage.cpp untuk MainPage::scenarios(), karena kita sekarang mendefinisikan metode tersebut dalam file header.

Seperti yang dapat Anda lihat, di SampleConfiguration.cpp, kita menginisialisasi anggota data statis scenariosInner dengan memanggil fungsi pembantu C++/WinRT bernama winrt::single_threaded_observable_vector. Fungsi itu membuat objek koleksi Windows Runtime baru untuk kami, dan mengembalikannya sebagai antarmuka IObservableVector. Karena, dalam sampel ini, koleksi tidak dapat diamati (tidak perlu, karena tidak menambahkan atau menghapus elemen setelah inisialisasi), kita bisa memilih untuk memanggil winrt::single_threaded_vector. Fungsi tersebut mengembalikan koleksi sebagai antarmuka IVector .

Untuk informasi selengkapnya tentang koleksi, dan mengikatnya, lihat kontrol item XAML; mengikat koleksi C++/WinRT, dan Koleksi dengan C++/WinRT.

Kode inisialisasi yang baru saja Anda tambahkan mereferensikan tipe yang belum ada di dalam proyek (misalnya, winrt::SDKTemplate::CopyText. Untuk memperbaikinya, mari kita lanjutkan dan tambahkan lima halaman XAML kosong baru ke proyek.

Tambahkan lima halaman XAML kosong baru

Tambahkan item Visual C++>Blank Page (C++/WinRT) baru ke proyek (pastikan itu adalah templat item Halaman Kosong (C++/WinRT), dan bukan Halaman Kosong satu). Beri nama CopyText. Halaman XAML baru didefinisikan dalam namespace layanan SDKTemplate , yang merupakan apa yang kita inginkan.

Ulangi proses di atas empat kali lagi, dan beri nama halaman CopyImageXAML , , CopyFilesHistoryAndRoaming, dan OtherScenarios.

Anda sekarang dapat membangun lagi, jika mau.

NotifyUser

Dalam proyek C#, Anda akan menemukan implementasi metode MainPage.NotifyUser di MainPage.xaml.cs. MainPage.NotifyUser memiliki dependensi pada MainPage.UpdateStatus, dan metode tersebut pada gilirannya memiliki dependensi pada elemen XAML yang belum kami porting. Jadi untuk sementara kita hanya akan membuat stub untuk metode UpdateStatus dalam proyek C++/WinRT, dan nanti kita akan mem-porting-nya.

Berikut adalah kode C# relevan yang perlu kita porting.

// 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 mengirimkan pembaruan UI ke utas utama. Di WinUI 3, ini menggunakan Microsoft.UI.Dispatching.DispatcherQueue alih-alih CoreDispatcher yang lebih lama. Di C++/WinRT, setiap kali Anda ingin menggunakan jenis dari namespace Windows atau Microsoft, Anda perlu menyertakan file header namespace C++/WinRT yang sesuai (untuk informasi selengkapnya tentang itu, lihat Mulai menggunakan C++/WinRT). Dalam hal ini, seperti yang akan Anda lihat dalam daftar kode di bawah ini, headernya adalah winrt/Microsoft.UI.Dispatching.h, dan kita akan menyertakannya dalam pch.h.

UpdateStatus bersifat pribadi. Jadi kita akan menjadikannya metode privat pada jenis implementasi MainPage kita. UpdateStatus tidak dimaksudkan untuk dipanggil pada kelas runtime, jadi kami tidak akan mendeklarasikannya di IDL.

Setelah mem-porting MainPage.NotifyUser dan membuat stub untuk MainPage.UpdateStatus, inilah yang kita miliki di proyek C++/WinRT. Setelah daftar kode ini, kami akan memeriksa beberapa detailnya.

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

Di C#, Anda dapat menggunakan notasi titik untuk mengakses properti bersarang. Jadi, jenis C# MainPage dapat mengakses properti Dispatcher sendiri dengan sintaks Dispatcher. Dan C# selanjutnya dapat mengakses nilai tersebut dengan notasi seperti Dispatcher.HasThreadAccess. Di C++/WinRT, properti diimplementasikan sebagai fungsi pengakses, sehingga sintaksisnya hanya berbeda karena Anda menambahkan tanda kurung untuk setiap panggilan fungsi.

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

Ketika notifyUser versi C# memanggil Dispatcher.RunAsync, setara WinUI 3 menggunakan DispatcherQueue.TryEnqueue. Versi C++/WinRT mengimplementasikan delegasi panggilan balik sebagai fungsi lambda. Di C++/WinRT, kita menangkap dua parameter yang akan kita gunakan, serta this pointer (karena kita akan memanggil fungsi anggota). Ada info selengkapnya tentang menerapkan delegasi sebagai lambda, dan contoh kode, dalam topik Menangani peristiwa dengan menggunakan delegasi di C++/WinRT.

Menerapkan anggota MainPage yang tersisa

Mari kita buat daftar lengkap anggota MainPage (diimplementasikan di seluruh MainPage.xaml.cs dan SampleConfiguration.cs) sehingga kita dapat melihat mana yang telah kita port sejauh ini, dan mana yang belum dilakukan.

Anggota Access Status
Konstruktor MainPage public Dialihkan
Properti saat ini public Dialihkan
properti FEATURE_NAME public Dialihkan
properti IsClipboardContentChangedEnabled public Belum dimulai
Properti skenario public Dialihportkan
Metode BuildClipboardFormatsOutputString public Belum dimulai
Metode DisplayToast public Belum dimulai
Metode EnableClipboardContentChangedNotifications public Belum dimulai
Metode NotifyUser public Dialihkan
Metode OnNavigatedTo protected Belum dimulai
isApplicationWindowActive field private Belum dimulai
kolom needToPrintClipboardFormat private Belum dimulai
bidang skenario private Dialihkan
metode Button_Click private Belum dimulai
Metode DisplayChangedFormats private Belum dimulai
metode Footer_Click private Belum dimulai
Metode HandleClipboardChanged private Belum dimulai
Metode OnClipboardChanged private Belum dimulai
Metode OnWindowActivated private Belum dimulai
metode ScenarioControl_SelectionChanged private Belum dimulai
Metode UpdateStatus private Tersungkur

Kita akan berbicara tentang anggota yang belum dilaporkan dalam beberapa subbagian berikutnya, kemudian.

Note

Dari waktu ke waktu, kita akan menemukan referensi dalam kode sumber ke elemen UI dalam markup XAML (dalam MainPage.xaml). Saat kita menemui referensi ini, kita akan menyiasatinya untuk sementara dengan menambahkan elemen penampung sederhana ke XAML. Dengan begitu, proyek akan terus dibangun setelah setiap subbagian. Alternatifnya adalah menyelesaikan referensi dengan menyalin seluruh konten dari MainPage.xaml proyek C# ke proyek C++/WinRT sekarang. Tetapi jika kita melakukan itu, akan butuh waktu lama sebelum kita bisa berhenti sejenak dan melakukan build lagi (sehingga berpotensi menyamarkan kesalahan ketik atau kesalahan lain yang kita buat selama proses tersebut).

Setelah selesai melakukan porting kode imperatif untuk kelas MainPage , maka kita akan menyalin konten file XAML dan yakin bahwa proyek akan tetap dibangun.

IsClipboardContentChangedEnabled

Ini adalah properti C# dengan pengakses get dan set yang nilai defaultnya adalah false. Ini adalah anggota MainPage, dan didefinisikan dalam SampleConfiguration.cs.

Untuk C++/WinRT, kita memerlukan fungsi aksesor, fungsi mutator, dan anggota data pendukung berupa field. Karena IsClipboardContentChangedEnabled mewakili status salah satu skenario dalam sampel, daripada status MainPage itu sendiri, kita akan membuat anggota baru pada jenis utilitas baru yang disebut SampleState. Dan kita akan menerapkannya dalam file kode sumber, SampleConfiguration.cpp dan kita akan membuat anggota static (karena kita hanya membutuhkan satu instans di seluruh aplikasi; dan sehingga kita dapat mengaksesnya tanpa memerlukan instans kelas).

Untuk menemani kami SampleConfiguration.cpp dalam proyek C++/WinRT, tambahkan item Visual C++>Code>Header File (.h) baru dengan nama SampleConfiguration.h. Edit SampleConfiguration.h dan SampleConfiguration.cpp agar sesuai dengan daftar di bawah ini.

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

Sekali lagi, field dengan penyimpanan static (seperti SampleState::isClipboardContentChangedEnabled) harus didefinisikan satu kali dalam aplikasi, dan file .cpp adalah tempat yang tepat untuk itu (SampleConfiguration.cpp dalam kasus ini).

BuildClipboardFormatsOutputString

Metode ini adalah anggota publik MainPage, dan didefinisikan dalam 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();
}
...

Di C++/WinRT, kita akan menjadikan BuildClipboardFormatsOutputString sebagai metode statis publik SampleState. Kita dapat membuatnya static karena tidak mengakses anggota instans apa pun.

Untuk menggunakan jenis Clipboard dan DataPackageView di C++/WinRT, kita harus menyertakan file winrt/Windows.ApplicationModel.DataTransfer.hheader namespace C++/WinRT Windows .

Di C#, properti DataPackageView.AvailableFormats adalah IReadOnlyList, sehingga kita dapat mengakses properti Count dari properti tersebut. Di C++/WinRT, fungsi aksesor DataPackageView::AvailableFormats mengembalikan IVectorView, yang memiliki fungsi aksesor Ukuran yang dapat kita panggil.

Untuk mem-port penggunaan jenis C# System.Text.StringBuilder , kita akan menggunakan tipe C++ standar std::wostringstream. Tipe tersebut adalah stream output untuk string lebar (dan untuk menggunakannya, kita perlu menyertakan berkas header sstream). Alih-alih menggunakan metode Tambahkan seperti yang Anda lakukan dengan StringBuilder, Anda menggunakan operator penyisipan (<<) dengan aliran output seperti wostringstream. Untuk informasi selengkapnya, lihat pemrograman iostream, dan Memformat string C++/WinRT.

Kode C# membuat StringBuilder dengan new kata kunci. Di C#, objek adalah jenis referensi secara default, dinyatakan pada heap dengan new. Dalam C++standar modern, objek adalah jenis nilai secara default, dideklarasikan pada tumpukan (tanpa menggunakan new). Jadi kami mem-port StringBuilder output = new StringBuilder(); ke C++/WinRT cukup menjadi std::wostringstream output;.

Kata kunci C# var meminta pengkompilasi untuk menyimpulkan jenis. Anda melakukan port var ke auto di C++/WinRT. Tetapi dalam C++/WinRT, dalam beberapa kasus (untuk menghindari penyalinan) Anda menginginkan referensi ke tipe yang diinferensikan (atau dideduksi), dan Anda menyatakan referensi lvalue ke tipe yang dideduksi dengan auto&. Ada juga kasus di mana Anda menginginkan jenis referensi khusus yang mengikat dengan benar apakah itu diinisialisasi dengan lvalue atau dengan rvalue. Dan anda mengekspresikan itu dengan auto&&. Itulah bentuk yang Anda lihat digunakan dalam loop for pada kode hasil porting di bawah ini. Untuk pengenalan lvalues dan rvalues, lihat Kategori nilai, dan referensinya.

Edit pch.h, SampleConfiguration.h, dan SampleConfiguration.cpp agar sesuai dengan daftar di bawah ini.

// 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

Sintaks pada baris kode DataPackageView clipboardContent{ Clipboard::GetContent() }; menggunakan fitur dalam C++ standar modern yang disebut inisialisasi uniform, yang ditandai dengan penggunaan kurung kurawal alih-alih tanda =. Sintaksis itu memperjelas bahwa inisialisasi, bukan penugasan, sedang berlangsung. Jika Anda lebih suka bentuk sintaksis yang terlihat seperti penugasan (tetapi sebenarnya tidak), maka Anda dapat mengganti sintaks di atas dengan yang setara DataPackageView clipboardContent = Clipboard::GetContent();. Ada baiknya untuk menjadi nyaman dengan kedua cara mengekspresikan inisialisasi, karena Anda cenderung sering melihat keduanya digunakan dalam kode yang Anda temui.

DisplayToast

DisplayToast adalah metode statis publik dari kelas C# MainPage , dan Anda akan menemukannya didefinisikan dalam SampleConfiguration.cs. Di C++/WinRT, kita akan menjadikannya metode statis publik SampleState.

Kami telah menemui sebagian besar detail dan teknik yang diperlukan untuk memigrasikan metode ini. Satu hal baru yang perlu diperhatikan adalah Anda mengonversi literal string verbatim C# (@) menjadi literal string raw C++ standar (LR).

Selain itu, saat mereferensikan jenis ToastNotification dan XmlDocument di C++/WinRT, Anda dapat menuliskannya dengan nama namespace lengkap, atau mengedit SampleConfiguration.cpp dan menambahkan direktif using namespace seperti pada contoh berikut.

using namespace Windows::UI::Notifications;

Anda memiliki pilihan yang sama saat mereferensikan jenis XmlDocument, dan setiap kali Anda mereferensikan jenis Windows Runtime lainnya.

Selain item tersebut, cukup ikuti panduan yang sama dengan yang Sebelumnya Anda lakukan untuk menyelesaikan langkah-langkah berikut.

  • Deklarasikan metode dalam SampleConfiguration.h, dan tentukan dalam SampleConfiguration.cpp.
  • Edit pch.h untuk menyertakan file header namespace C++/WinRT Windows yang diperlukan.
  • Buat objek C++/WinRT di stack, bukan di heap.
  • Ganti pemanggilan ke pengakses get properti dengan sintaks pemanggilan fungsi (()).

Penyebab kesalahan kompilator/linker yang sangat umum adalah lupa menyertakan file header namespace C++/WinRT Windows yang Anda butuhkan. Untuk informasi selengkapnya tentang salah satu kemungkinan galat, lihat C3779: Mengapa kompilator menampilkan galat "consume_Something: fungsi yang mengembalikan 'auto' tidak dapat digunakan sebelum didefinisikan"?.

Jika Anda ingin mengikuti panduan dan port DisplayToast sendiri, maka Anda dapat membandingkan hasil Anda dengan kode dalam versi C++/WinRT di ZIP kode sumber sampel Clipboard yang Anda unduh.

Aktifkan notifikasi perubahan konten papan klip

EnableClipboardContentChangedNotifications adalah metode statis publik dari kelas C# MainPage , dan didefinisikan dalam 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) { ... }
...

Di C++/WinRT, kita akan menjadikannya metode statis publik SampleState.

Di C#, Anda menggunakan sintaks operator += dan -= untuk mendaftarkan dan membatalkan pendaftaran delegasi penanganan kejadian. Di C++/WinRT, Anda memiliki beberapa opsi sintaks untuk mendaftarkan/membatalkan delegate, sebagaimana dijelaskan dalam Menangani peristiwa dengan menggunakan delegate di C++/WinRT. Namun, bentuk umumnya adalah Anda melakukan pendaftaran dan pembatalan melalui pemanggilan sepasang fungsi yang dinamai sesuai peristiwa tersebut. Untuk melakukan pendaftaran, Anda meneruskan delegat Anda ke fungsi pendaftaran, dan sebagai hasilnya Anda mendapatkan token pencabutan (berupa winrt::event_token). Untuk mencabutnya, Anda memberikan token tersebut ke fungsi pencabutan. Dalam hal ini, hander bersifat statis dan (seperti yang Anda lihat dalam daftar kode berikut) sintaks panggilan fungsi mudah.

Token serupa sebenarnya digunakan , di belakang layar, di C#. Tetapi bahasa membuat detail itu implisit. C++/WinRT membuatnya eksplisit.

Jenis objek muncul di tanda tangan penanganan aktivitas C#. Dalam bahasa C#, objek adalah alias untuk jenis .NET System.Object. Yang setara dalam C++/WinRT adalah winrt::Windows::Foundation::IInspectable. Jadi, Anda akan melihat IInspectable di penanganan aktivitas C++/WinRT.

Edit SampleConfiguration.h dan SampleConfiguration.cpp agar sesuai dengan daftar di bawah ini.

// 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){}

Biarkan delegasi penanganan peristiwa itu sendiri (OnClipboardChanged dan OnWindowActivated) sebagai stub untuk saat ini. Mereka sudah ada dalam daftar member kami untuk dipindahkan, jadi kami akan membahasnya di subbagian berikutnya.

OnNavigatedTo

OnNavigatedTo adalah metode yang dilindungi dari kelas C# MainPage , dan didefinisikan dalam MainPage.xaml.cs. Ini dia, bersama dengan XAML ListBox yang dirujuknya.

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

Ini adalah metode penting dan menarik, karena di sinilah kumpulan objek Skenario kami ditetapkan ke UI. Kode C# membuat System.Collections.Generic.List yang berisi objek Scenario, dan menetapkannya ke properti ItemsSource dari ListBox (yang merupakan kontrol yang menampilkan kumpulan item). Dan, di C#, kami menggunakan interpolasi string untuk membangun judul untuk setiap objek Skenario (perhatikan penggunaan $ karakter khusus).

Di C++/WinRT, kita akan menjadikan OnNavigatedTo sebagai metode publik MainPage. Dan kita akan menambahkan elemen ListBox stub ke XAML sehingga build akan berhasil. Setelah daftar kode, kami akan memeriksa beberapa detailnya.

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

Sekali lagi, kami memanggil fungsi winrt::single_threaded_observable_vector, tetapi kali ini untuk membuat sebuah koleksi IInspectable. Itu adalah bagian dari keputusan yang kami buat untuk melakukan tinju objek Skenario kami secara tepat waktu.

Dan, sebagai ganti dari penggunaan interpolasi string C# di sini, kami menggunakan kombinasi fungsi to_hstring dan operator perangkaianwinrt::hstring.

isApplicationWindowActive

Di C#, isApplicationWindowActive adalah bidang privat bool sederhana milik kelas MainPage , dan didefinisikan dalam SampleConfiguration.cs. Pengaturan bawaan adalah false. Dalam C++/WinRT, kita akan menjadikannya field privat statis dari SampleState (untuk alasan yang sudah kami jelaskan) di file SampleConfiguration.h dan SampleConfiguration.cpp, dengan nilai default yang sama.

Kami telah melihat cara mendeklarasikan, menentukan, dan menginisialisasi bidang statis. Untuk penyegaran, lihat kembali apa yang kami lakukan dengan bidang isClipboardContentChangedEnabled , dan lakukan hal yang sama dengan isApplicationWindowActive.

needToPrintClipboardFormat

Pola yang sama dengan isApplicationWindowActive (lihat judul segera sebelum yang satu ini).

Button_Click

Button_Click adalah metode privat (penanganan peristiwa) dari kelas C# MainPage , dan didefinisikan dalam MainPage.xaml.cs. Berikut, beserta XAML SplitView yang dirujuknya, dan ToggleButton yang meregistrasikannya.

<!-- MainPage.xaml -->
...
<SplitView x:Name="Splitter" ... />
...
<ToggleButton Click="Button_Click" .../>
...
private void Button_Click(object sender, RoutedEventArgs e)
{
    Splitter.IsPaneOpen = !Splitter.IsPaneOpen;
}

Dan inilah yang setara, di-port ke C++/WinRT. Perhatikan bahwa dalam versi C++/WinRT, penangan peristiwa adalah public (seperti yang dapat Anda lihat, Anda mendeklarasikannya sebelum deklarasi private:). Ini karena penanganan aktivitas yang terdaftar di markup XAML, seperti ini, harus berada public di C++/WinRT agar markup XAML dapat mengaksesnya. Di sisi lain, jika Anda mendaftarkan penangan peristiwa dalam kode imperatif (seperti yang kami lakukan sebelumnya di MainPage::EnableClipboardContentChangedNotifications), penangan peristiwa tersebut tidak perlu 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

Dalam C#, DisplayChangedFormats adalah metode privat milik kelas MainPage , dan didefinisikan dalam SampleConfiguration.cs.

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

Dalam C++/WinRT, kita akan menjadikannya bidang statis privat dari SampleState (karena tidak mengakses anggota instans apa pun), di file SampleConfiguration.h dan SampleConfiguration.cpp. Kode C# untuk metode ini tidak menggunakan System.Text.StringBuilder; tetapi, metode ini melakukan cukup banyak pemformatan string sehingga untuk versi C++/WinRT, ini juga merupakan tempat yang tepat untuk menggunakan std::wostringstream.

Alih-alih properti System.Environment.NewLine statis, yang digunakan dalam kode C#, kita akan menyisipkan C++ std::endl standar (karakter baris baru) ke dalam aliran output.

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

Ada ketidakefisienan kecil dalam desain versi C++/WinRT di atas. Pertama, kita membuat std::wostringstream. Tetapi kami juga memanggil metode BuildClipboardFormatsOutputString (yang kami port sebelumnya). Metode itu membuat std::wostringstream sendiri. Dan mengubah alirannya menjadi winrt::hstring dan mengembalikannya. Kami memanggil fungsi hstring::c_str untuk mengubah hstring yang dikembalikan kembali menjadi string gaya C, dan kemudian kami memasukkannya ke dalam aliran kami. Akan lebih efisien untuk membuat hanya satu std::wostringstream, lalu meneruskan referensinya ke berbagai bagian yang memerlukannya, sehingga metode dapat langsung menambahkan string ke dalamnya.

Itulah yang kami lakukan dalam versi C++/WinRT dari kode sumber sampel Clipboard (di ZIP yang Anda unduh). Dalam kode sumber tersebut, ada metode statis privat baru bernama SampleState::AddClipboardFormatsOutputString, yang mengambil dan beroperasi pada referensi ke aliran output. Dan kemudian metode SampleState::D isplayChangedFormats dan SampleState::BuildClipboardFormatsOutputString direfaktor untuk memanggil metode baru tersebut. Ini secara fungsional setara dengan daftar kode dalam topik ini, tetapi lebih efisien.

Footer_Click adalah penanganan aktivitas asinkron milik kelas C# MainPage , dan didefinisikan dalam MainPage.xaml.cs. Kode yang tercantum di bawah ini secara fungsional setara dengan metode dalam kode sumber yang Anda unduh. Tapi di sini saya telah menguraikannya dari satu baris menjadi empat baris agar lebih mudah melihat apa yang dilakukan kode ini, dan karena itu bagaimana kita harus mem-portingnya.

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

Sementara, secara teknis, metode ini asinkron, tidak melakukan apa pun setelah await, sehingga tidak memerlukan await (atau async kata kunci). Ini mungkin menggunakannya untuk menghindari pesan IntelliSense di Visual Studio.

Metode C++/WinRT yang setara juga akan asinkron (karena memanggil Launcher.LaunchUriAsync). Tetapi tidak perlu co_await, juga tidak mengembalikan objek asinkron. Untuk informasi tentang co_await dan objek asinkron, lihat Operasi konkurensi dan asinkron dengan C++/WinRT.

Sekarang, mari kita bahas apa yang dilakukan oleh metode tersebut. Karena ini adalah penanganan aktivitas untuk peristiwa KlikHyperlinkButton, objek bernama pengirim sebenarnya adalah HyperlinkButton. Jadi konversi jenis aman (kita bisa saja mengekspresikan konversi ini sebagai sender as HyperlinkButton). Selanjutnya, kami mengambil nilai properti Tag (jika Anda melihat markup XAML di proyek C#, Anda akan melihat bahwa ini diatur ke string yang mewakili url web). Meskipun properti FrameworkElement.Tag (HyperlinkButton adalah FrameworkElement) berjenis objek, di C# kita dapat merangkainya dengan Object.ToString. Dari string yang dihasilkan, kami membuat objek Uri . Dan akhirnya (dengan bantuan Shell) kami meluncurkan browser dan menavigasi ke url.

Berikut adalah metode yang di-port ke C++/WinRT (sekali lagi, diperluas untuk kejelasan), setelah itu adalah deskripsi detailnya.

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

Seperti biasa, kami membuat penanganan publicaktivitas . Kami menggunakan fungsi sebagai pada objek pengirim untuk mengonversinya menjadi HyperlinkButton. Di C++/WinRT, properti Tag adalah IInspectable (setara dengan Object). Tapi tidak ada Tostring di IInspectable. Sebaliknya, kita harus mengekstrak IInspectable menjadi nilai skalar (string, dalam hal ini). Sekali lagi, untuk informasi selengkapnya tentang tinju dan membuka kotak, lihat Nilai Tinju dan buka kotak ke IInspectable.

Dua baris terakhir mengulangi pola porting yang telah kita lihat sebelumnya, dan mereka cukup banyak menggemakan versi C#.

HandleClipboardChanged

Tidak ada hal baru dalam mem-porting metode ini. Anda dapat membandingkan versi C# dan C++/WinRT di ZIP kode sumber sampel Clipboard yang Anda unduh.

OnClipboardChanged dan OnWindowActivated

Sejauh ini kami hanya memiliki stub kosong untuk dua penanganan aktivitas ini. Namun, mem-porting itu cukup mudah, dan tidak memunculkan hal baru untuk dibahas.

ScenarioControl_SelectionChanged

Ini adalah penanganan aktivitas privat lain milik kelas C# MainPage , dan didefinisikan dalam MainPage.xaml.cs. Di C++/WinRT, kita akan membuatnya publik, dan mengimplementasikannya di MainPage.h dan MainPage.cpp.

Untuk metode ini, kita memerlukan MainPage::navigating, yang merupakan bidang Boolean privat, diinisialisasi ke false. Dan Anda memerlukan Frame di MainPage.xaml, yang diberi nama ScenarioFrame. Tetapi, selain detail tersebut, porting metode ini tidak mengungkapkan teknik baru.

Jika, alih-alih melakukan porting secara manual, Anda menyalin kode dari versi C++/WinRT dalam file ZIP kode sumber sampel Clipboard yang Anda unduh, maka Anda akan melihat MainPage::NavigateTo digunakan di sana. Untuk saat ini, cukup refaktor isi NavigateTo ke dalam ScenarioControl_SelectionChanged.

UpdateStatus

Kami hanya memiliki stub sejauh ini untuk MainPage.UpdateStatus. Memindahkan implementasinya, sekali lagi, sebagian besar hanya mengulang hal-hal lama. Satu poin baru yang perlu diperhatikan adalah bahwa sementara di C# kita dapat membandingkan string dengan String.Empty, Di C++/WinRT kita sebagai gantinya memanggil fungsi winrt::hstring::empty . Lainnya adalah bahwa nullptr adalah C++ standar yang setara dengan C#'s null.

Anda dapat melakukan proses porting selebihnya dengan teknik yang sudah kami bahas sebelumnya. Berikut adalah daftar jenis hal yang perlu Anda lakukan sebelum versi port metode ini akan dikompilasi.

  • Ke MainPage.xaml, tambahkan Batas bernama StatusBorder.
  • Ke MainPage.xaml, tambahkan TextBlock bernama StatusBlock.
  • Ke MainPage.xaml, tambahkan StackPanel bernama StatusPanel.
  • Ke pch.h, tambahkan #include "winrt/Microsoft.UI.Xaml.Media.h".
  • Ke pch.h, tambahkan #include "winrt/Microsoft.UI.Xaml.Automation.Peers.h".
  • Untuk MainPage.cpp menambahkan using namespace winrt::Microsoft::UI::Xaml::Media;.
  • Untuk MainPage.cpp menambahkan using namespace winrt::Microsoft::UI::Xaml::Automation::Peers;.

Salin XAML dan gaya yang diperlukan untuk menyelesaikan port MainPage

Untuk XAML, kasus idealnya adalah Anda dapat menggunakan markup XAML yang sama di seluruh C# dan proyek C++/WinRT. Dan sampel Clipboard adalah salah satu kasus tersebut.

Dalam file Styles.xaml-nya, sampel Clipboard memiliki ResourceDictionary XAML yang berisi gaya, yang diterapkan pada tombol, menu, dan elemen UI lainnya di seluruh antarmuka pengguna aplikasi. Halaman Styles.xaml digabungkan ke dalam App.xaml. Dan kemudian ada titik awal standar MainPage.xaml untuk UI, yang telah kita lihat secara singkat. Kita sekarang dapat menggunakan kembali ketiga .xaml file tersebut, tidak berubah, dalam versi C++/WinRT proyek.

Seperti halnya file aset, Anda dapat memilih untuk merujuk ke file XAML bersama yang sama dalam beberapa versi aplikasi Anda. Dalam panduan ini, hanya demi kesederhanaan, kita akan menyalin file ke dalam proyek C++/WinRT dan menambahkannya seperti itu.

Navigasi ke \Clipboard_sample\SharedContent\xaml folder, pilih dan salin App.xaml dan MainPage.xaml, lalu tempelkan kedua file tersebut \Clipboard\Clipboard ke folder di proyek C++/WinRT Anda, memilih untuk mengganti file saat diminta.

Di proyek C++/WinRT di Visual Studio, klik Perlihatkan Semua File untuk mengaktifkannya. Sekarang tambahkan folder baru, segera di bawah simpul proyek, dan beri nama Styles. Di File Explorer, navigasikan \Clipboard_sample\SharedContent\xaml ke folder, pilih dan salin Styles.xaml, dan tempelkan ke \Clipboard\Clipboard\Styles folder yang baru saja Anda buat. Kembali ke Penjelajah Solusi di proyek C++/WinRT, klik kanan folder Styles>Add>Existing item... dan navigasikan ke \Clipboard\Clipboard\Styles. Di pemilih file, pilih Styles dan klik Tambahkan.

Tambahkan folder baru ke proyek C++/WinRT, segera di bawah simpul proyek, dan bernama Styles. Navigasi ke \Clipboard_sample\SharedContent\xaml folder, pilih dan salin Styles.xaml, dan tempelkan ke \Clipboard\Clipboard\Styles folder di proyek C++/WinRT Anda. Styles Klik kanan folder (dalam Penjelajah Solusi di proyek C++/WinRT) >Tambahkan>item yang Ada... dan navigasi ke \Clipboard\Clipboard\Styles. Di pemilih file, pilih Styles dan klik Tambahkan.

Klik Perlihatkan Semua File lagi untuk menonaktifkannya.

Kami sekarang telah selesai memindahkan MainPage, dan jika Anda telah mengikuti langkah-langkahnya, proyek C++/WinRT Anda sekarang akan dibangun dan dijalankan.

Gabungkan file .idl Anda

Selain titik awal standar MainPage.xaml untuk UI, sampel Clipboard memiliki lima halaman XAML khusus skenario lainnya, bersama dengan file code-behind yang sesuai. Kita akan menggunakan kembali markup XAML aktual dari semua halaman ini, tidak berubah, dalam versi C++/WinRT proyek. Dan kita akan melihat cara mem-port kode di belakang di beberapa bagian utama berikutnya. Tapi sebelum itu, mari kita bicara tentang IDL.

Ada nilai dalam mengonsolidasikan IDL untuk kelas runtime Anda ke dalam satu file IDL. Untuk mempelajari nilai tersebut, lihat Memfaktorkan kelas runtime ke dalam file MIDL (.idl). Jadi, selanjutnya kita akan menggabungkan isi dari CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idl, dan OtherScenarios.idl dengan memindahkan IDL tersebut ke dalam satu file bernama Project.idl (kemudian menghapus file-file aslinya).

Saat kami melakukannya, mari kita juga menghapus properti dummy yang dihasilkan secara otomatis (Int32 MyProperty;, dan implementasinya) dari masing-masing dari lima jenis halaman XAML tersebut.

Pertama, tambahkan item Midl File (.idl) baru ke proyek C++/WinRT. Beri nama Project.idl. Ganti seluruh konten Project.idl dengan kode berikut.

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

Seperti yang Anda lihat, itu hanyalah salinan isi berkas individual .idl, semuanya berada dalam satu namespace, dan dengan MyProperty yang dihapus dari setiap kelas runtime.

Di Penjelajah Solusi di Visual Studio, pilih beberapa file IDL asli (CopyFiles.idl, , CopyImage.idl, CopyText.idlHistoryAndRoaming.idl, dan OtherScenarios.idl) dan Edit>Hapus (pilih Hapus dalam dialog).

Terakhir—dan untuk menyelesaikan penghapusan MyProperty—dalam file .h dan .cpp untuk masing-masing dari lima jenis halaman XAML yang sama tersebut, hapus deklarasi dan definisi fungsi aksesori int32_t MyProperty() dan fungsi mutator void MyProperty(int32_t).

Ngomong-ngomong, sebaiknya nama file XAML Anda sesuai dengan nama kelas yang diwakilinya. Misalnya, jika Anda memiliki x:Class="MyNamespace.MyPage" dalam file markup XAML, maka file tersebut harus diberi nama MyPage.xaml. Meskipun ini bukan persyaratan teknis, tidak harus mengubah nama yang berbeda untuk artefak yang sama akan membuat proyek Anda lebih dapat dimengerti dan dipertahankan, dan lebih mudah dikerjakan.

CopyFiles

Dalam proyek C#, jenis halaman CopyFiles XAML diimplementasikan dalam CopyFiles.xaml file kode sumber dan CopyFiles.xaml.cs . Mari kita lihat masing-masing anggota CopyFiles secara bergantian.

rootPage

Ini adalah bidang privat.

// CopyFiles.xaml.cs
...
public sealed partial class CopyFiles : Page
{
    MainPage rootPage = MainPage.Current;
    ...
}
...

Di C++/WinRT, kita dapat menentukan dan menginisialisasinya seperti ini.

// CopyFiles.h
...
struct CopyFiles : CopyFilesT<CopyFiles>
{
    ...
private:
    SDKTemplate::MainPage rootPage{ MainPage::Current() };
};
...

Sekali lagi (sama seperti MainPage::current), CopyFiles::rootPage dinyatakan sebagai jenis SDKTemplate::MainPage, yang merupakan jenis yang diproyeksikan, dan bukan jenis implementasi.

CopyFiles (konstruktor)

Dalam proyek C++/WinRT, jenis CopyFiles sudah memiliki konstruktor yang berisi kode yang kita inginkan (hanya memanggil InitializeComponent).

CopyButton_Click

Metode C# CopyButton_Click adalah penangan peristiwa, dan dari kata kunci async pada signaturenya kita dapat mengetahui bahwa metode tersebut menjalankan operasi asinkron. Di C++/WinRT, kami menerapkan metode asinkron sebagai coroutine. Untuk pengenalan konkurensi di C++/WinRT, bersama dengan deskripsi tentang apa itu coroutine , lihat Operasi konkurensi dan asinkron dengan C++/WinRT.

Umumnya, kita ingin menjadwalkan pekerjaan lanjutan setelah korutin selesai, dan untuk kasus seperti itu korutin akan mengembalikan suatu jenis objek asinkron yang dapat di-await dan secara opsional dapat melaporkan progres. Tetapi pertimbangan tersebut biasanya tidak berlaku untuk penangan peristiwa. Jadi ketika Anda memiliki penanganan aktivitas yang melakukan operasi asinkron, Anda dapat mengimplementasikannya sebagai coroutine yang mengembalikan winrt::fire_and_forget. Untuk informasi selengkapnya, lihat Fire and forget.

Meskipun konsep coroutine fire-and-forget adalah bahwa Anda tidak perlu peduli kapan coroutine tersebut selesai, pekerjaannya tetap berjalan (atau ditangguhkan, menunggu untuk dilanjutkan kembali) di latar belakang. Dari implementasi C#, Anda dapat melihat bahwa CopyButton_Click bergantung pada pointer this (ia mengakses anggota data instans rootPage). Jadi, kita harus memastikan bahwa pointer this(pointer ke objek CopyFiles) tetap ada lebih lama daripada koroutin CopyButton_Click. Dalam situasi seperti aplikasi sampel ini, di mana pengguna menavigasi antara halaman UI, kami tidak dapat langsung mengontrol masa pakai halaman tersebut. Jika halaman CopyFiles ditutup (dengan berpindah dari halaman tersebut) saat CopyButton_Click masih berjalan di utas latar belakang, maka rootPage tidak aman untuk diakses. Agar koroutine ini benar, koroutine tersebut perlu memperoleh referensi kuat ke penunjuk this, dan mempertahankan referensi itu selama masa berlaku koroutine tersebut. Untuk informasi selengkapnya, lihat Referensi yang kuat dan lemah di C++/WinRT.

Jika Anda melihat versi C++/WinRT dari sampel tersebut, pada CopyFiles::CopyButton_Click, Anda akan melihat bahwa hal tersebut dilakukan dengan deklarasi sederhana di stack.

fire_and_forget CopyFiles::CopyButton_Click(IInspectable const&, RoutedEventArgs const&)
{
    auto lifetime{ get_strong() };
    ...
}

Mari kita lihat aspek lain dari kode port yang patut diperhatikan.

Dalam kode, kita membuat instans objek FileOpenPicker , dan dua baris kemudian kita mengakses properti FileTypeFilter objek tersebut. Tipe pengembalian properti tersebut mengimplementasikan IVector yang berisi string. Dan pada IVector tersebut, kita memanggil metode IVector<T>.ReplaceAll(T[]). Aspek yang menarik adalah nilai yang kita teruskan ke metode tersebut, padahal yang diharapkan adalah sebuah array. Berikut adalah baris kode.

filePicker.FileTypeFilter().ReplaceAll({ L"*" });

Nilai yang kita lewati ({ L"*" }) adalah daftar inisialisasi C++ standar. Ini berisi satu objek, dalam hal ini, tetapi daftar inisialisasi dapat berisi sejumlah objek yang dipisahkan koma. Potongan C++/WinRT yang memungkinkan Anda kenyamanan meneruskan daftar inisialisasi ke metode seperti ini dijelaskan dalam daftar inisialisasi Standar.

Kami memindahkan kata kunci C# await ke co_await di C++/WinRT. Berikut adalah contoh dari kode.

auto storageItems{ co_await filePicker.PickMultipleFilesAsync() };

Selanjutnya, pertimbangkan baris kode C# ini.

dataPackage.SetStorageItems(storageItems);

C# dapat secara implisit mengonversi IReadOnlyList<StorageFile> yang diwakili oleh storageItems ke dalam IEnumerable IStorageItem< yang> diharapkan oleh DataPackage.SetStorageItems. Tetapi di C++/WinRT kita perlu secara eksplisit melakukan konversi dari IVectorView<StorageFile> ke IIterable<IStorageItem>. Jadi kita memiliki contoh lain dari fungsi as yang sedang digunakan.

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

Di mana kita menggunakan null kata kunci di C# (misalnya, Clipboard.SetContentWithOptions(dataPackage, null)), kita gunakan nullptr di C++/WinRT (misalnya, Clipboard::SetContentWithOptions(dataPackage, nullptr)).

PasteButton_Click

Ini adalah penanganan aktivitas lain dalam bentuk koroutin api dan lupa. Mari kita lihat aspek-aspek dari kode yang di-porting yang perlu diperhatikan.

Dalam contoh versi C#, kita menangani pengecualian dengan catch (Exception ex). Dalam kode C++/WinRT yang di-port, Anda akan melihat ekspresi catch (winrt::hresult_error const& ex). Untuk informasi selengkapnya tentang winrt::hresult_error dan cara bekerja dengannya, lihat Penanganan kesalahan dengan C++/WinRT.

Contoh pengujian apakah objek C# adalah null atau tidak adalah if (storageItems != null). Dalam C++/WinRT, kita dapat mengandalkan operator konversi ke bool, yang melakukan pemeriksaan terhadap nullptr secara internal.

Berikut adalah versi yang sedikit disederhanakan dari fragmen kode dari sampel versi C++/WinRT yang di-port.

std::wostringstream output;
output << std::wstring_view(ApplicationData::Current().LocalFolder().Path());

Membuat std::wstring_view dari winrt::hstring seperti itu menggambarkan alternatif untuk memanggil fungsi hstring::c_str (untuk mengubah winrt::hstring menjadi string gaya C). Alternatif ini berfungsi berkat operator konversi hstringke std::wstring_view.

Pertimbangkan fragmen C#.

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

Untuk memport kata kunci C# as ke C++/WinRT, sejauh ini kita telah melihat fungsi as digunakan beberapa kali. Fungsi tersebut melemparkan pengecualian jika konversi jenis gagal. Tetapi jika kita ingin konversi kembali nullptr jika gagal (sehingga kita dapat menangani kondisi itu dalam kode), maka kita sebagai gantinya menggunakan fungsi try_as .

auto file{ storageItem.try_as<StorageFile>() };
if (file)
...

Salin XAML yang diperlukan untuk menyelesaikan port copyFiles

Anda sekarang dapat memilih seluruh konten CopyFiles.xaml file dari shared folder unduhan kode sumber sampel asli, dan menempelkannya ke dalam CopyFiles.xaml file di proyek C++/WinRT (menggantikan konten file yang ada di proyek C++/WinRT).

Terakhir, edit CopyFiles.h dan .cpp, lalu hapus fungsi ClickHandler tiruan, karena kita baru saja menimpa markup XAML yang terkait.

Kami sekarang telah selesai memindahkan CopyFiles, dan jika Anda telah mengikuti langkah-langkahnya, proyek C++/WinRT Anda sekarang akan membangun dan menjalankan, dan skenario CopyFiles akan berfungsi.

CopyImage

Untuk memindahkan jenis halaman CopyImage XAML, Anda mengikuti proses yang sama seperti untuk CopyFiles. Saat melakukan porting CopyImage, Anda akan menjumpai penggunaan pernyataan using di C#, yang memastikan bahwa objek yang menerapkan antarmuka IDisposable dilepas dengan benar.

if (imageReceived != null)
{
    using (var imageStream = await imageReceived.OpenReadAsync())
    {
        ... // Pass imageStream to other APIs, and do other work.
    }
}

Antarmuka yang setara di C++/WinRT adalah IClosable, dengan metode tunggal Close. Berikut adalah C++/WinRT yang setara dengan kode C# di atas.

if (imageReceived)
{
    auto imageStream{ co_await imageReceived.OpenReadAsync() };
    ... // Pass imageStream to other APIs, and do other work.
    imageStream.Close();
}

Objek C++/WinRT mengimplementasikan IClosable terutama untuk manfaat bahasa yang tidak memiliki finalisasi deterministik. C++/WinRT memiliki finalisasi deterministik, sehingga kami sering tidak perlu memanggil IClosable::Close saat kami menulis C++/WinRT. Namun, ada saat-saat ketika sebaiknya kita mengakhirinya, dan sekarang adalah salah satunya. Di sini, identifier imageStream adalah pembungkus berpenghitungan referensi untuk objek Windows Runtime yang mendasarinya (dalam hal ini, objek yang mengimplementasikan IRandomAccessStreamWithContentType). Meskipun kita dapat mengetahui bahwa finalizer dari imageStream (destruktornya) akan berjalan pada akhir cakupan yang melingkupinya (kurung kurawal), kita tidak dapat memastikan bahwa finalizer tersebut akan memanggil Close. Itu karena kami mengoper imageStream ke API lain, dan API tersebut mungkin masih menambah jumlah referensi objek Windows Runtime yang mendasarinya. Jadi ini adalah kasus di mana ada baiknya untuk memanggil Close secara eksplisit. Untuk informasi selengkapnya, lihat Apakah saya perlu memanggil IClosable::Close pada kelas runtime yang saya gunakan?.

Selanjutnya, pertimbangkan ekspresi C# (uint)(imageDecoder.OrientedPixelWidth * 0.5), yang akan Anda temukan dalam pengendali peristiwa OnDeferredImageRequestedHandler. Ekspresi tersebut mengalikan uint dengan double, menghasilkan double. Kemudian mengonversinya ke uint. Dalam C++/WinRT, kita dapat menggunakan konversi gaya C yang tampak serupa ((uint32_t)(imageDecoder.OrientedPixelWidth() * 0.5)), namun sebaiknya kita memperjelas jenis konversi apa yang dimaksud, dan dalam kasus ini kita akan melakukannya dengan static_cast<uint32_t>(imageDecoder.OrientedPixelWidth() * 0.5).

CopyImage.OnDeferredImageRequestedHandler versi C# memiliki finally klausa, tetapi bukan catch klausa. Dalam versi C++/WinRT, kami melangkah sedikit lebih jauh dan mengimplementasikan klausa catch agar kami dapat melaporkan apakah rendering tertunda berhasil atau tidak.

Porting sisa halaman XAML ini tidak menghasilkan sesuatu yang baru untuk dibahas. Ingatlah untuk menghapus fungsi ClickHandler dummy. Dan, sama seperti dengan CopyFiles, langkah terakhir di port adalah memilih seluruh konten CopyImage.xaml, dan menempelkannya ke dalam file yang sama di proyek C++/WinRT.

CopyText

Anda dapat melakukan port CopyText.xaml dan CopyText.xaml.cs menggunakan teknik yang telah kami bahas.

HistoryAndRoaming

Ada beberapa hal penting yang muncul saat mem-porting jenis halaman XAML HistoryAndRoaming.

Pertama, lihat kode sumber C#, dan ikuti alur kontrol dari OnNavigatedTo melalui penanganan aktivitas OnHistoryEnabledChanged , dan akhirnya ke fungsi asinkron CheckHistoryAndRoaming (yang tidak ditunggu, jadi pada dasarnya aktif dan lupakan). Karena CheckHistoryAndRoaming bersifat asinkron, kita perlu berhati-hati di C++/WinRT mengenai masa hidup pointer this. Anda dapat melihat hasilnya jika Anda melihat implementasi dalam HistoryAndRoaming.cpp file kode sumber. Pertama, saat kita melampirkan delegate ke event Clipboard::HistoryEnabledChanged dan Clipboard::RoamingEnabledChanged, kita hanya mengambil referensi lemah ke objek halaman HistoryAndRoaming. Kami melakukannya dengan membuat delegat dengan ketergantungan pada nilai yang dikembalikan dari winrt::get_weak, alih-alih pada pointer this. Artinya, delegat itu sendiri, yang pada akhirnya memanggil kode asinkron, tidak membuat halaman HistoryAndRoaming tetap aktif jika kita berpindah dari halaman tersebut.

Dan kedua, ketika kita akhirnya mencapai korutin fire-and-forget CheckHistoryAndRoaming, hal pertama yang kita lakukan adalah mengambil referensi kuat ke this untuk menjamin bahwa halaman HistoryAndRoaming tetap ada setidaknya sampai korutin tersebut akhirnya selesai. Untuk informasi selengkapnya tentang kedua aspek yang baru saja dijelaskan, lihat Referensi yang kuat dan lemah di C++/WinRT.

Kami menemukan tempat menarik lain saat memindahkan CheckHistoryAndRoaming. Ini berisi kode untuk memperbarui UI; jadi kita harus yakin bahwa kita melakukan itu pada utas UI utama. Utas yang pada awalnya memanggil penangan peristiwa adalah utas UI utama. Tetapi biasanya, metode asinkron dapat mengeksekusi dan/atau melanjutkan pada utas arbitrer apa pun. Di C#, solusinya adalah meneruskan tugas ke utas UI. Di C++/WinRT, kita dapat menggunakan fungsi winrt::resume_foreground bersama thisDispatcherQueue milik pointer untuk menangguhkan korutin dan segera melanjutkannya kembali di utas UI utama.

Ekspresi yang relevan adalah co_await winrt::resume_foreground(DispatcherQueue());. Versi yang lebih pendek dicapai berdasarkan operator konversi yang disediakan oleh C++/WinRT.

Porting sisa halaman XAML ini tidak menghasilkan sesuatu yang baru untuk dibahas. Ingatlah untuk menghapus fungsi ClickHandler dummy, dan untuk menyalin melalui markup XAML.

OtherScenarios

Anda dapat melakukan port OtherScenarios.xaml dan OtherScenarios.xaml.cs menggunakan teknik yang telah kami bahas.

Conclusion

Semoga panduan ini telah mempersenjatai Anda dengan info porting dan teknik yang cukup yang sekarang dapat Anda lanjutkan dan port aplikasi C# Anda sendiri ke C++/WinRT. Dengan cara penyegaran, Anda dapat terus merujuk kembali ke versi sebelum (C#) dan setelah (C++/WinRT) dari kode sumber dalam sampel Clipboard, dan membandingkannya secara berdampingan untuk melihat korespondensi.