Bagikan melalui


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.

Nota

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 halaman properti untuk kontrol tersebut, klik ikon 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.

Nota

Biasanya, pengendali acara 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 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::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 Blank App (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 tersebut, maka nama fungsi aksesor yang dihasilkan juga berubah.

Nota

Dalam hal ini, sumber peristiwa (objek yang menaikkan peristiwa) adalah tombol bernama myButton. Dan penerima peristiwa (objek yang menangani peristiwa) adalah instance dari 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 dari RoutedEventHandler yang menerima sebuah objek dan penunjuk ke fungsi anggota.

// MainPage.cpp
MainPage::MainPage()
{
    InitializeComponent();

    myButton().Click({ this, &MainPage::ClickHandler });
}

Penting

Saat mendaftarkan delegasi, contoh kode di atas melewati mentah ini pointer (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 membuat RoutedEventHandler. Di bawah ini adalah blok sintaksis yang diambil dari topik dokumentasi untuk routedEventHandler (pilih C++/WinRT dari menu drop-down Bahasa di sudut kanan atas halaman web). Perhatikan berbagai konstruktor: satu mengambil lambda; yang lain adalah fungsi bebas; dan yang lainnya (yang kami gunakan di atas) mengambil objek dan penunjuk-ke-fungsi-anggota.

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 dari operator pemanggil fungsi juga bermanfaat untuk dipahami. Ini memberi tahu Anda apa yang harus menjadi parameter delegasi Anda. Seperti yang dapat Anda lihat, dalam kasus ini, sintaks operator pemanggil fungsi sesuai dengan parameter dari MainPage::ClickHandler.

Nota

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 sedang kita bahas) memiliki jenis delegasi KeyEventHandler, karena itulah jenis yang Anda gunakan ketika mendaftarkan delegasi dengan jenis peristiwa ini. Jadi, sekarang ikuti tautan pada topik ke delegasi KeyEventHandler jenis. Di sini, blok sintaks berisi operator panggilan fungsi. Dan, seperti disebutkan di atas, ini menjelaskan kepada Anda mengenai kebutuhan 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 arg.

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 lain (karena itulah parameter jenis EventHandler) sebagai argumen.

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 pendelegasian Anda; yang berarti bahwa delegasi dihapus pendaftarannya dari acara, dan tidak akan dipanggil jika peristiwa tersebut terjadi lagi.

Demi kesederhanaan, tidak ada contoh kode di atas yang menunjukkan cara melakukannya. Namun, contoh kode berikutnya ini menyimpan token di elemen data privat struct, dan mencabut handler-nya saat destruktor dijalankan.

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 Kuat dan lemah di C++/WinRT).

Nota

Ketika sumber peristiwa memunculkan peristiwanya secara sinkron, Anda dapat menghapus 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 dapat mengurangi masalah, atau untuk solusi yang kuat, lihat 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 (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::revoke; namun, event revoker tersebut akan memanggil fungsi itu secara otomatis ketika keluar dari cakupan. Fungsi mencabut 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 kode sintaksis yang diambil dari topik dokumentasi untuk event 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 daftarkan dan membatalkan dengan overload event_revoker.

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

Nota

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 overload fungsi pendaftaran yang mengembalikan nilai dengan tipe 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 ke-4, seperti yang dijelaskan nanti dalam topik ini, adalah menangkap referensi yang kuat atau lemah ke objek ini dalam fungsi lambda Anda.

Jika delegasi auto-revoke Anda gagal terdaftar

Jika Anda mencoba menentukan winrt::auto_revoke saat mendaftarkan delegasi, dan hasilnya adalah pengecualian winrt::hresult_no_interface, maka itu biasanya berarti bahwa sumber peristiwa tidak mendukung referensi yang lemah. Itu adalah situasi umum dalam namespace Windows.UI.Composition, misalnya. Dalam situasi ini, Anda tidak dapat menggunakan fitur pencabutan otomatis. Anda harus kembali ke mencabut penanganan acara 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) mungkin sudah selesai, dan/atau memiliki kejadian progres 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 pendelegasian dengan peristiwa penyelesaian dari tindakan dan operasi asinkron, Anda mungkin akan merasa lebih alami menggunakan korutin. Untuk informasi lebih lanjut dan contoh kode, lihat kekongruenan dan operasi asinkron dengan C++/WinRT.

Nota

Tidak tepat untuk mengimplementasikan lebih dari satu pengendali penyelesaian untuk tindakan atau operasi asinkron. Anda dapat memiliki satu delegasi untuk acara yang telah diselesaikan, atau Anda dapat co_await. Jika Anda memiliki keduanya, maka yang kedua akan gagal.

Jika Anda menggunakan delegasi alih-alih korutin, maka 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 sendiri 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";
    });
}

Mengakses dengan aman 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 Kuat dan lemah di C++/WinRT.

API penting