Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Important
Membangun dengan SDK Aplikasi Windows? Kode artikel ini menggunakan namespace UWP (Windows.UI.Xaml). Jika proyek Anda menargetkan WinUI 3 (SDK Aplikasi Windows), ganti Microsoft.UI.Xaml (dan namespace terkait Microsoft.UI.*) di seluruh kode. Lihat Memetakan API UWP ke SDK Aplikasi Windows untuk panduan pemetaan lengkap dan migrasi UI untuk detail tambahan.
Topik ini menunjukkan cara mendaftar dan mencabut delegasi penanganan peristiwa menggunakan C++/WinRT. Anda dapat menangani peristiwa menggunakan objek seperti fungsi C++ standar apa pun.
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.
Menggunakan Visual Studio untuk menambahkan penanganan aktivitas
Cara mudah menambahkan penanganan aktivitas ke proyek Anda adalah dengan menggunakan antarmuka pengguna (UI) XAML Designer di Visual Studio. Dengan halaman XAML Anda terbuka di Perancang XAML, pilih kontrol yang peristiwanya ingin Anda tangani. Pada halaman properti untuk kontrol tersebut, klik ikon petir untuk menampilkan semua kejadian yang berasal dari kontrol tersebut. Kemudian, klik dua kali pada peristiwa yang ingin Anda tangani; misalnya, OnClicked.
Perancang XAML menambahkan prototipe fungsi penanganan aktivitas yang sesuai (dan implementasi stub) ke file sumber Anda, siap untuk Anda ganti dengan implementasi Anda sendiri.
Note
Biasanya, penanganan aktivitas Anda tidak perlu dijelaskan dalam file Midl Anda (.idl). Jadi, Perancang XAML tidak menambahkan prototipe fungsi penanganan aktivitas ke file Midl Anda. Ini hanya menambahkannya ke file .h dan .cpp Anda.
Mendaftarkan delegasi untuk menangani peristiwa
Contoh sederhananya adalah menangani peristiwa klik tombol. Biasanya menggunakan markup XAML untuk mendaftarkan fungsi anggota untuk menangani peristiwa, seperti ini.
// MainPage.xaml
<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
// MainPage.h
void ClickHandler(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::RoutedEventArgs const& args);
// MainPage.cpp
void MainPage::ClickHandler(
IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
myButton().Content(box_value(L"Clicked"));
}
Kode di atas diambil dari proyek Blank App, Packaged (WinUI 3 in Desktop) di Visual Studio. Kode myButton() memanggil fungsi aksesor yang dihasilkan, yang mengembalikan Tombol yang kami beri nama myButton. Jika Anda mengubah elemen x:Name tersebut, maka nama fungsi aksesor yang dihasilkan juga berubah.
Note
Dalam hal ini, sumber peristiwa (objek yang menaikkan peristiwa) adalah Tombol bernama myButton. Dan penerima peristiwa (objek yang menangani peristiwa) adalah instans MainPage. Ada info lebih lanjut nanti dalam topik ini tentang mengelola masa pakai sumber peristiwa dan penerima peristiwa.
Alih-alih melakukannya secara deklaratif dalam markup, Anda dapat secara imperatif mendaftarkan fungsi anggota untuk menangani peristiwa. Mungkin hal ini tidak terlihat jelas dari contoh kode di bawah ini, tetapi argumen untuk pemanggilan ButtonBase::Click adalah sebuah instans dari delegasi RoutedEventHandler. Dalam hal ini, kami menggunakan kelebihan beban konstruktor RoutedEventHandler yang mengambil objek dan pointer-to-member-function.
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click({ this, &MainPage::ClickHandler });
}
Important
Saat mendaftarkan delegasi, contoh kode di atas meneruskan pointer ini mentah (menunjuk ke objek saat ini). Untuk mempelajari cara membuat referensi yang kuat atau lemah ke objek saat ini, lihat Jika Anda menggunakan fungsi anggota sebagai delegasi.
Berikut adalah contoh yang menggunakan fungsi anggota statis; perhatikan sintaks yang lebih sederhana.
// MainPage.h
static void ClickHandler(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::RoutedEventArgs const& args);
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click( MainPage::ClickHandler );
}
void MainPage::ClickHandler(
IInspectable const& /* sender */,
RoutedEventArgs const& /* args */) { ... }
Ada cara lain untuk membangun RoutedEventHandler. Di bawah ini adalah blok sintaksis yang diambil dari topik dokumentasi untuk RoutedEventHandler (pilih C++/WinRT dari drop-down Bahasa di sudut kanan atas halaman web). Perhatikan berbagai konstruktor: yang satu menerima lambda; yang lain menerima fungsi bebas; dan yang lain lagi (yang kita gunakan di atas) menerima sebuah objek dan pointer-to-member-function.
struct RoutedEventHandler : winrt::Windows::Foundation::IUnknown
{
RoutedEventHandler(std::nullptr_t = nullptr) noexcept;
template <typename L> RoutedEventHandler(L lambda);
template <typename F> RoutedEventHandler(F* function);
template <typename O, typename M> RoutedEventHandler(O* object, M method);
/* ... other constructors ... */
void operator()(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e) const;
};
Sintaks operator pemanggilan fungsi juga membantu untuk dipahami. Ini memberi tahu Anda apa yang diperlukan parameter delegasi Anda. Seperti yang Anda lihat, dalam hal ini sintaks operator panggilan fungsi cocok dengan parameter MainPage::ClickHandler kami.
Note
Untuk peristiwa tertentu, untuk mengetahui detail delegasinya, dan parameter delegasi tersebut, buka topik dokumentasi terlebih dahulu untuk peristiwa itu sendiri. Mari kita ambil peristiwa UIElement.KeyDown sebagai contoh. Kunjungi topik tersebut, dan pilih C++/WinRT dari menu drop-down Bahasa . Di blok sintaks di awal topik, Anda akan melihat ini.
// Register
event_token KeyDown(KeyEventHandler const& handler) const;
Informasi itu memberi tahu kita bahwa peristiwa UIElement.KeyDown (topik yang sedang kita bahas) memiliki tipe delegasi KeyEventHandler, karena itulah tipe yang Anda teruskan saat mendaftarkan delegasi untuk jenis peristiwa ini. Jadi, sekarang ikuti tautan pada topik tersebut menuju tipe KeyEventHandler delegate itu. Di sini, blok sintaks berisi operator panggilan fungsi. Dan, seperti yang disebutkan di atas, hal itu memberi tahu Anda seperti apa parameter delegat Anda harus ditetapkan.
void operator()(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;
Seperti yang Anda lihat, delegasi perlu dideklarasikan agar menerima IInspectable sebagai pengirim, dan sebuah instans dari kelas KeyRoutedEventArgs sebagai argumen.
Untuk mengambil contoh lain, mari kita lihat peristiwa Popup.Closed. Tipe delegatnya adalah EventHandler<IInspectable>. Jadi, delegasi Anda akan mengambil IInspectable sebagai pengirim, dan IInspectable lainnya (karena itulah parameter jenis EventHandler ) sebagai args.
Jika Anda tidak melakukan banyak pekerjaan di penanganan aktivitas Anda, maka Anda dapat menggunakan fungsi lambda alih-alih fungsi anggota. Sekali lagi, mungkin tidak jelas dari contoh kode di bawah ini, tetapi delegasi RoutedEventHandler sedang dibangun dari fungsi lambda yang, sekali lagi, perlu mencocokkan sintaks operator panggilan fungsi yang kita bahas di atas.
MainPage::MainPage()
{
InitializeComponent();
myButton().Click([this](IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
{
myButton().Content(box_value(L"Clicked"));
});
}
Anda dapat memilih untuk menjadi sedikit lebih eksplisit saat membuat delegasi Anda. Misalnya, jika Anda ingin membagikannya, atau menggunakannya lebih dari sekali.
MainPage::MainPage()
{
InitializeComponent();
auto click_handler = [](IInspectable const& sender, RoutedEventArgs const& /* args */)
{
sender.as<winrt::Microsoft::UI::Xaml::Controls::Button>().Content(box_value(L"Clicked"));
};
myButton().Click(click_handler);
AnotherButton().Click(click_handler);
}
Mencabut delegasi terdaftar
Saat Anda mendaftarkan delegasi, biasanya token dikembalikan kepada Anda. Anda selanjutnya dapat menggunakan token tersebut untuk mencabut delegat itu; artinya, delegat tersebut dibatalkan pendaftarannya dari peristiwa dan tidak akan dipanggil jika peristiwa itu dipicu lagi.
Demi kesederhanaan, tidak ada contoh kode di atas yang menunjukkan cara melakukannya. Tetapi contoh kode berikutnya ini menyimpan token di anggota data privat struct, dan mencabut handler-nya di destruktor.
struct Example : ExampleT<Example>
{
Example(winrt::Microsoft::UI::Xaml::Controls::Button const& button) : m_button(button)
{
m_token = m_button.Click([this](IInspectable const&, RoutedEventArgs const&)
{
// ...
});
}
~Example()
{
m_button.Click(m_token);
}
private:
winrt::Microsoft::UI::Xaml::Controls::Button m_button;
winrt::event_token m_token;
};
Alih-alih referensi yang kuat, seperti pada contoh di atas, Anda dapat menyimpan referensi lemah ke tombol (lihat Referensi yang kuat dan lemah di C++/WinRT).
Note
Ketika sumber peristiwa menaikkan peristiwanya secara sinkron, Anda dapat mencabut handler Anda dan yakin bahwa Anda tidak akan menerima peristiwa lagi. Tetapi untuk peristiwa asinkron, bahkan setelah pembatalan dilakukan (dan terutama jika pembatalan dilakukan di dalam destruktor), peristiwa yang sedang diproses masih bisa sampai ke objek Anda setelah objek tersebut mulai dihancurkan. Menemukan tempat untuk berhenti berlangganan sebelum penghancuran dapat mengurangi masalah, atau untuk solusi yang kuat, lihat Mengakses pointer ini dengan aman dengan delegasi penanganan peristiwa.
Atau, ketika Anda mendaftarkan delegasi, Anda dapat menentukan winrt::auto_revoke (yang merupakan nilai jenis winrt::auto_revoke_t) untuk meminta pencabut peristiwa (dari jenis winrt::event_revoker). Pencabut event menyimpan referensi lemah ke sumber event (objek yang memicu event) untuk Anda. Anda dapat membatalkannya secara manual dengan memanggil fungsi anggota event_revoker::revoke; tetapi event revoker akan memanggil fungsi tersebut secara otomatis saat keluar dari scope. Fungsi cabut memeriksa apakah sumber peristiwa masih ada dan, jika demikian, mencabut delegasi Anda. Dalam contoh ini, tidak perlu menyimpan sumber peristiwa, dan tidak perlu destruktor.
struct Example : ExampleT<Example>
{
Example(winrt::Microsoft::UI::Xaml::Controls::Button button)
{
m_event_revoker = button.Click(
winrt::auto_revoke,
[this](IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
// ...
});
}
private:
winrt::Microsoft::UI::Xaml::Controls::Button::Click_revoker m_event_revoker;
};
Di bawah ini adalah blok sintaksis yang diambil dari topik dokumentasi untuk peristiwa ButtonBase::Click . Ini menunjukkan tiga fungsi pendaftaran dan pencabutan yang berbeda. Anda dapat melihat dengan tepat jenis pembatal event yang perlu Anda deklarasikan dari overload ketiga. Dan Anda dapat meneruskan jenis delegasi yang sama ke kelebihan beban register dan revoke with event_revoker.
// Register
winrt::event_token Click(winrt::Microsoft::UI::Xaml::RoutedEventHandler const& handler) const;
// Revoke with event_token
void Click(winrt::event_token const& token) const;
// Revoke with event_revoker
Button::Click_revoker Click(winrt::auto_revoke_t,
winrt::Microsoft::UI::Xaml::RoutedEventHandler const& handler) const;
Note
Dalam contoh kode di atas, Button::Click_revoker adalah alias jenis untuk winrt::event_revoker<winrt::Microsoft::UI::Xaml::Controls::Primitives::IButtonBase>. Pola serupa berlaku untuk semua peristiwa C++/WinRT. Setiap peristiwa Windows Runtime memiliki fungsi pencabutan kelebihan beban yang mengembalikan pencabut peristiwa, dan jenis pencabut tersebut adalah anggota sumber peristiwa. Jadi, untuk mengambil contoh lain, peristiwa Window::SizeChanged memiliki kelebihan fungsi pendaftaran yang mengembalikan nilai jenis Window::SizeChanged_revoker.
Anda mungkin mempertimbangkan untuk mencabut handler dalam skenario navigasi halaman. Jika Anda berulang kali membuka suatu halaman lalu keluar lagi, Anda dapat melepas semua handler saat meninggalkan halaman tersebut. Atau, jika Anda menggunakan kembali instans halaman yang sama, maka periksa nilai token Anda dan hanya daftarkan token tersebut jika nilainya belum disetel (if (!m_token){ ... }). Opsi ketiga adalah menyimpan pencabut peristiwa di halaman sebagai anggota data. Dan opsi keempat, seperti yang dijelaskan nanti dalam topik ini, adalah menangkap referensi yang kuat atau lemah ke objek ini dalam fungsi lambda Anda.
Jika delegasi pencabutan otomatis Anda gagal terdaftar
Jika Anda mencoba menetapkan winrt::auto_revoke saat mendaftarkan delegat, dan hasilnya adalah pengecualian winrt::hresult_no_interface, maka biasanya itu berarti bahwa sumber event tidak mendukung referensi lemah. Itu adalah situasi yang umum di namespace Microsoft.UI.Composition, misalnya. Dalam situasi ini, Anda tidak dapat menggunakan fitur pencabutan otomatis. Anda harus mundur untuk mencabut penanganan aktivitas Anda secara manual.
Jenis delegasi untuk tindakan dan operasi asinkron
Contoh di atas menggunakan jenis delegasi RoutedEventHandler , tetapi tentu saja ada banyak jenis delegasi lainnya. Misalnya, tindakan dan operasi asinkron (dengan dan tanpa kemajuan) telah menyelesaikan dan/atau peristiwa kemajuan yang mengharapkan delegasi dari jenis yang sesuai. Misalnya, kejadian progres dari operasi asinkron dengan progres (yaitu apa pun yang mengimplementasikan IAsyncOperationWithProgress) memerlukan delegasi bertipe AsyncOperationProgressHandler. Berikut adalah contoh kode penulisan delegasi jenis tersebut menggunakan fungsi lambda. Contohnya juga menunjukkan cara membuat delegat AsyncOperationWithProgressCompletedHandler.
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
void ProcessFeedAsync()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
auto async_op_with_progress = syndicationClient.RetrieveFeedAsync(rssFeedUri);
async_op_with_progress.Progress(
[](
IAsyncOperationWithProgress<SyndicationFeed,
RetrievalProgress> const& /* sender */,
RetrievalProgress const& args)
{
uint32_t bytes_retrieved = args.BytesRetrieved;
// use bytes_retrieved;
});
async_op_with_progress.Completed(
[](
IAsyncOperationWithProgress<SyndicationFeed,
RetrievalProgress> const& sender,
AsyncStatus const /* asyncStatus */)
{
SyndicationFeed syndicationFeed = sender.GetResults();
// use syndicationFeed;
});
// or (but this function must then be a coroutine, and return IAsyncAction)
// SyndicationFeed syndicationFeed{ co_await async_op_with_progress };
}
Seperti yang ditunjukkan oleh komentar "coroutine" di atas, alih-alih menggunakan delegat dengan event penyelesaian dari tindakan dan operasi asinkron, Anda kemungkinan akan merasa lebih alami menggunakan coroutine. Untuk detail, dan contoh kode, lihat Operasi konkurensi dan asinkron dengan C++/WinRT.
Note
Tidak tepat mengimplementasikan lebih dari satu penangan penyelesaian untuk aksi atau operasi asinkron. Anda dapat memiliki satu delegasi untuk acaranya yang telah selesai, atau Anda bisa co_await melakukannya. Jika Anda memiliki keduanya, maka yang kedua akan gagal.
Jika Anda tetap menggunakan delegasi alih-alih coroutine, Anda dapat memilih sintaks yang lebih sederhana.
async_op_with_progress.Completed(
[](auto&& /*sender*/, AsyncStatus const /* args */)
{
// ...
});
Tipe delegasi yang mengembalikan nilai
Beberapa jenis delegasi harus mengembalikan nilai. Contohnya adalah ListViewItemToKeyHandler, yang mengembalikan string. Berikut adalah contoh penulisan delegasi jenis tersebut (perhatikan bahwa fungsi lambda mengembalikan nilai).
using namespace winrt::Microsoft::UI::Xaml::Controls;
winrt::hstring f(ListView listview)
{
return ListViewPersistenceHelper::GetRelativeScrollPosition(listview, [](IInspectable const& item)
{
return L"key for item goes here";
});
}
Mengakses pointer this secara aman menggunakan delegat penanganan peristiwa
Jika Anda menangani peristiwa dengan fungsi anggota objek, atau dari dalam fungsi lambda di dalam fungsi anggota objek, maka Anda perlu memikirkan masa pakai relatif penerima peristiwa (objek yang menangani peristiwa) dan sumber peristiwa (objek yang menaikkan peristiwa). Untuk informasi selengkapnya, dan contoh kode, lihat Referensi yang kuat dan lemah di C++/WinRT.
API penting
- winrt::auto_revoke_t marker struct
- winrt::implements::get_weak function
- winrt::implements::get_strong function
Topik terkait
Windows developer