Menangani peristiwa dengan menggunakan delegasi di C++/WinRT

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.

Catatan

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. Di atas halaman properti untuk kontrol tersebut, klik ikon petir-petir untuk mencantumkan semua peristiwa yang bersumber 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.

Catatan

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 menambahkan mereka file dan .cpp Anda.h.

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::Windows::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 Aplikasi Kosong (C++/WinRT) di Visual Studio. Kode myButton() memanggil fungsi aksesor yang dihasilkan, yang mengembalikan Tombol yang kami beri nama myButton. Jika Anda mengubah elemen Tombol tersebutx:Name, maka nama fungsi aksesor yang dihasilkan juga berubah.

Catatan

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 tidak jelas dari contoh kode di bawah ini, tetapi argumen ke ButtonBase::Klik panggilan adalah instans 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 });
}

Penting

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::Windows::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: satu mengambil lambda; fungsi bebas lainnya; dan yang lain (yang kami gunakan di atas) mengambil 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::Windows::UI::Xaml::RoutedEventArgs const& e) const;
};

Sintaks operator panggilan fungsi juga berguna untuk dilihat. 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.

Catatan

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;

Info itu memberi tahu kami bahwa peristiwa UIElement.KeyDown (topik yang kami gunakan) memiliki jenis delegasi KeyEventHandler, karena itulah jenis yang Anda lewati saat Mendaftarkan delegasi dengan jenis peristiwa ini. Jadi, sekarang ikuti tautan tentang topik ke jenis delegasi KeyEventHandler tersebut. Di sini, blok sintaks berisi operator panggilan fungsi. Dan, seperti disebutkan di atas, yang memberi tahu Anda apa yang diperlukan parameter delegasi Anda.

void operator()(
  winrt::Windows::Foundation::IInspectable const& sender,
  winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;

Seperti yang Anda lihat, delegasi perlu dinyatakan untuk mengambil IInspectable sebagai pengirim, dan instans kelas KeyRoutedEventArgs sebagai args.

Untuk mengambil contoh lain, mari kita lihat peristiwa Popup.Closed. Jenis delegasinya 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 meneruskannya, atau menggunakannya lebih dari sekali.

MainPage::MainPage()
{
    InitializeComponent();

    auto click_handler = [](IInspectable const& sender, RoutedEventArgs const& /* args */)
    {
        sender.as<winrt::Windows::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 kemudian dapat menggunakan token tersebut untuk mencabut delegasi Anda; yang berarti bahwa delegasi tidak terdaftar dari acara, dan tidak akan dipanggil jika peristiwa dinaikkan 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::Windows::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::Windows::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).

Catatan

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 mencabut (dan terutama ketika mencabut dalam destruktor), peristiwa dalam penerbangan mungkin mencapai objek Anda setelah mulai merusak. Menemukan tempat untuk berhenti berlangganan sebelum penghancuran mungkin mengurangi masalah, atau untuk solusi yang kuat, lihat Brankas mengakses pointer ini 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 peristiwa menyimpan referensi lemah ke sumber peristiwa (objek yang menaikkan peristiwa) untuk Anda. Anda dapat mencabut secara manual dengan memanggil fungsi anggota event_revoker::cabut ; tetapi pencabut peristiwa memanggil fungsi itu sendiri secara otomatis ketika keluar dari cakupan. 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::Windows::UI::Xaml::Controls::Button button)
    {
        m_event_revoker = button.Click(
            winrt::auto_revoke,
            [this](IInspectable const& /* sender */,
            RoutedEventArgs const& /* args */)
        {
            // ...
        });
    }

private:
    winrt::Windows::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 pencabut peristiwa apa yang perlu Anda nyatakan dari kelebihan beban ketiga. Dan Anda dapat meneruskan jenis delegasi yang sama ke register dan pencabutan dengan event_revoker kelebihan beban.

// Register
winrt::event_token Click(winrt::Windows::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::Windows::UI::Xaml::RoutedEventHandler const& handler) const;

Catatan

Dalam contoh kode di atas, Button::Click_revoker adalah alias jenis untuk winrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase>. Pola serupa berlaku untuk semua peristiwa C++/WinRT. Setiap peristiwa Windows Runtime memiliki kelebihan beban fungsi pencabutan yang mengembalikan pencabut peristiwa, dan jenis pencabut tersebut adalah anggota sumber peristiwa. Jadi, untuk mengambil contoh lain, peristiwa CoreWindow::SizeChanged memiliki kelebihan fungsi pendaftaran yang mengembalikan nilai jenis CoreWindow::SizeChanged_revoker.

Anda mungkin mempertimbangkan untuk mencabut handler dalam skenario navigasi halaman. Jika Anda berulang kali menavigasi ke halaman lalu keluar kembali, maka Anda dapat mencabut handler apa pun saat menavigasi jauh dari halaman. Atau, jika Anda menggunakan kembali instans halaman yang sama, periksa nilai token Anda dan hanya daftar jika belum ditetapkan (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 mendaftar

Jika Anda mencoba menentukan winrt::auto_revoke saat mendaftarkan delegasi, dan hasilnya adalah pengecualian winrt::hresult_no_interface , maka biasanya itu berarti bahwa sumber peristiwa tidak mendukung referensi yang lemah. Itu adalah situasi umum di namespace Windows.UI.Composition , misalnya. Dalam situasi ini, Anda tidak dapat menggunakan fitur pencabutan otomatis. Anda harus mundur untuk mencabut penanganan aktivitas Anda secara manual.

Mendelegasikan jenis 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, peristiwa kemajuan operasi asinkron dengan kemajuan (yang merupakan apa pun yang mengimplementasikan IAsyncOperationWithProgress) memerlukan delegasi jenis AsyncOperationProgressHandler. Berikut adalah contoh kode penulisan delegasi jenis tersebut menggunakan fungsi lambda. Contohnya juga menunjukkan cara menulis delegasi 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 disarankan komentar "coroutine" di atas, alih-alih menggunakan delegasi dengan peristiwa lengkap tindakan dan operasi asinkron, Anda mungkin akan merasa lebih alami untuk menggunakan koroutin. Untuk detail, dan contoh kode, lihat Operasi konkurensi dan asinkron dengan C++/WinRT.

Catatan

Tidak benar untuk mengimplementasikan lebih dari satu handler penyelesaian untuk tindakan 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 dengan delegasi alih-alih koroutine, maka Anda dapat memilih sintaks yang lebih sederhana.

async_op_with_progress.Completed(
    [](auto&& /*sender*/, AsyncStatus const /* args */)
{
    // ...
});

Mendelegasikan jenis 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::Windows::UI::Xaml::Controls;

winrt::hstring f(ListView listview)
{
    return ListViewPersistenceHelper::GetRelativeScrollPosition(listview, [](IInspectable const& item)
    {
        return L"key for item goes here";
    });
}

Brankas mengakses pointer ini dengan delegasi 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