Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym temacie przedstawiono sposób rejestrowania i odwoływanie delegatów obsługi zdarzeń przy użyciu C++/WinRT. Zdarzenie można obsłużyć przy użyciu dowolnego standardowego obiektu przypominającego funkcję języka C++.
Uwaga / Notatka
Aby uzyskać informacje na temat instalowania i używania rozszerzenia C++/WinRT Visual Studio (VSIX) i pakietu NuGet (które razem zapewniają obsługę szablonu projektu i kompilacji), zobacz obsługa programu Visual Studio dla języka C++/WinRT.
Dodawanie programu obsługi zdarzeń przy użyciu programu Visual Studio
Wygodnym sposobem dodawania programu obsługi zdarzeń do projektu jest użycie interfejsu użytkownika projektanta XAML (UI) w programie Visual Studio. Po otwarciu strony XAML w projektancie XAML wybierz kontrolkę, której zdarzenie chcesz obsłużyć. Na stronie właściwości dla tej kontrolki kliknij ikonę błyskawicy, aby wyświetlić listę wszystkich zdarzeń, które ta kontrolka inicjuje. Następnie kliknij dwukrotnie zdarzenie, które chcesz obsłużyć; na przykład OnClicked.
Projektant XAML dodaje do plików źródłowych prototyp odpowiedniej funkcji obsługi zdarzeń (oraz implementację szkieletową), która jest gotowa do zastąpienia własną implementacją.
Uwaga / Notatka
Zazwyczaj programy obsługi zdarzeń nie muszą być opisane w pliku Midl (.idl). Dlatego projektant XAML nie dodaje prototypów funkcji obsługi zdarzeń do pliku Midl. Dodaje tylko pliki .h i .cpp.
Rejestrowanie pełnomocnika w celu obsługi zdarzenia
Prostym przykładem jest obsługa zdarzenia kliknięcia przycisku. Typowe jest używanie notacji XAML do rejestrowania funkcji składowej w celu obsługi zdarzenia.
// 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"));
}
Kod powyżej jest zaczerpnięty z projektu Pustej Aplikacji () w C++/WinRT w programie Visual Studio. Kod myButton() wywołuje wygenerowaną funkcję dostępu, która zwraca przycisk , który nazwaliśmy myButton. Jeśli zmienisz x:Name elementu Button, zmieni się także nazwa wygenerowanej funkcji akcesora.
Uwaga / Notatka
W tym przypadku źródłem zdarzenia (obiektem, który zgłasza zdarzenie) jest przycisk o nazwie myButton. Adresat zdarzenia (obiekt obsługujący zdarzenie) jest wystąpieniem MainPage. Więcej informacji znajduje się w dalszej części tego tematu na temat zarządzania okresem istnienia źródeł zdarzeń i adresatów zdarzeń.
Zamiast robić to deklaratywnie w znacznikach, możesz imperatywnie zarejestrować funkcję składową do obsługi zdarzenia. W poniższym przykładzie kodu może to nie być oczywiste, ale argument do wywołania ButtonBase::Kliknięcie jest wystąpienie delegata RoutedEventHandler. W tym przypadku używamy przeciążenia konstruktora RoutedEventHandler, które przyjmuje obiekt i wskaźnik do funkcji składowej.
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click({ this, &MainPage::ClickHandler });
}
Ważne
Podczas rejestrowania delegata, powyższy przykład kodu przekazuje nieprzetworzony wskaźnik (który wskazuje na bieżący obiekt). Aby dowiedzieć się, jak ustanowić silne lub słabe odwołanie do bieżącego obiektu, zobacz sekcję . Jeśli używasz funkcji składowej jako delegata, zapoznaj się z sekcją.
Oto przykład, który używa statycznej funkcji składowej; Zwróć uwagę na prostszą składnię.
// 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 */) { ... }
Istnieją inne sposoby konstruowania RoutedEventHandler. Poniżej znajduje się blok składni pobrany z tematu dokumentacji dla RoutedEventHandler (wybierz C++/WinRT z listy rozwijanej Language w prawym górnym rogu strony internetowej). Zwróć uwagę na różne konstruktory: jeden przyjmuje wyrażenie lambda; drugi przyjmuje wolną funkcję; a trzeci (użyty powyżej) przyjmuje obiekt i wskaźnik do funkcji członkowskiej.
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;
};
Składnia operatora wywołania funkcji jest również przydatna do zobaczenia. Informuje o tym, jakie powinny być parametry twojego delegata. Jak widać, w tym przypadku składnia operatora wywołania funkcji jest zgodna z parametrami naszego MainPage::ClickHandler.
Uwaga / Notatka
Aby dla dowolnego zdarzenia ustalić szczegóły jego delegata i parametry tego delegata, najpierw przejdź do tematu dokumentacji dotyczącego tego zdarzenia. Weźmy jako przykład zdarzenie UIElement.KeyDown. Odwiedź ten temat i wybierz C++/WinRT z listy rozwijanej Language. W bloku składni na początku tematu zobaczysz to.
// Register
event_token KeyDown(KeyEventHandler const& handler) const;
Informacje te wskazują, że zdarzenie UIElement.KeyDown (temat, który omawiamy) ma typ delegata KeyEventHandler, ponieważ jest to typ przekazywany podczas rejestrowania delegata dla tego typu zdarzenia. Teraz kliknij w link w temacie, by przejść do typu delegata KeyEventHandler . W tym miejscu blok składni zawiera operator wywołania funkcji. Jak wspomniano powyżej, informuje o tym, jakie powinny być parametry delegata.
void operator()(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;
Jak widać, delegat musi zostać zadeklarowany, aby jako nadawca oraz wystąpienie klasy KeyRoutedEventArgs jako args.
Aby podjąć inny przykład, przyjrzyjmy się zdarzeniu Popup.Closed. Jego typ delegata to EventHandler<IInspectable>. Dlatego delegat będzie przyjmował IInspectable jako nadawcę, a inny IInspectable jako argumenty (ponieważ jest to parametr typu EventHandler).
Jeśli nie wykonujesz dużo pracy w procedurze obsługi zdarzeń, możesz użyć funkcji lambda zamiast funkcji składowej. Ponownie, może nie być oczywiste z poniższego przykładu kodu, ale delegat RoutedEventHandler jest konstruowany z funkcji lambda, która musi ponownie być zgodna ze składnią operatora wywołania funkcji, o którym mówiliśmy powyżej.
MainPage::MainPage()
{
InitializeComponent();
myButton().Click([this](IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
{
myButton().Content(box_value(L"Clicked"));
});
}
Możesz zdecydować się być nieco bardziej precyzyjny podczas konstruowania delegata. Jeśli na przykład chcesz go przekazywać dalej lub użyć więcej niż raz.
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);
}
Odwoływanie zarejestrowanego pełnomocnika
Podczas rejestrowania delegata zazwyczaj zwracany jest token. Następnie możesz użyć tego tokenu, aby odwołać pełnomocnika; oznacza to, że delegat jest wyrejestrowany ze zdarzenia i nie będzie wywoływany, jeśli zdarzenie zostanie zgłoszone ponownie.
Dla uproszczenia żaden z powyższych przykładów kodu nie pokazał, jak to zrobić. Jednak w następującym przykładzie kodu token jest przechowywany w składowej danych prywatnych struktury i cofa jego obsługę w ramach destruktora.
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;
};
Zamiast silnego odwołania, jak w powyższym przykładzie, można przechowywać słabe odwołanie do przycisku (zobacz Silne i słabe odwołania w języku C++/WinRT).
Uwaga / Notatka
Gdy źródło zdarzeń zgłasza swoje zdarzenia synchronicznie, można odwołać procedurę obsługi zdarzeń i mieć pewność, że nie otrzymasz więcej zdarzeń. Jednak w przypadku zdarzeń asynchronicznych, nawet po odwołaniu (a zwłaszcza podczas odwoływania w destruktorze), zdarzenie "w locie" może dotrzeć do Twojego obiektu po rozpoczęciu destrukcji. Znalezienie miejsca anulowania subskrypcji przed zniszczeniem może rozwiązać ten problem lub w przypadku niezawodnego rozwiązania zobacz Bezpieczne uzyskiwanie dostępu do tego wskaźnika za pomocą delegata obsługi zdarzeń.
Alternatywnie podczas rejestrowania delegata można określić winrt::auto_revoke (która jest wartością typu winrt::auto_revoke_t), aby zażądać odwołania zdarzeń (typu winrt::event_revoker). Odwołujący zdarzenie przechowuje słabe odwołanie do źródła zdarzenia (obiektu, który wywołuje to zdarzenie). Możesz ręcznie odwołać subskrypcję, wywołując funkcję składową event_revoker::revoke; ale obiekt event_revoker automatycznie wywołuje tę funkcję, gdy wychodzi poza zakres. Funkcja cofania sprawdza, czy źródło zdarzeń nadal istnieje, a jeśli tak, cofa twojego delegata. W tym przykładzie nie ma potrzeby przechowywania źródła zdarzeń i nie ma potrzeby destruktora.
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;
};
Poniżej znajduje się blok składni pobrany z tematu dokumentacji dla zdarzenia ButtonBase::Kliknij zdarzenie. Przedstawia trzy różne funkcje rejestracji i cofania. Możesz zobaczyć dokładnie, jakiego typu odwołanie zdarzeń należy zadeklarować z trzeciego przeciążenia. Można również przekazać te same rodzaje delegatów zarówno do rejestru , jak i odwołać za pomocą przeciążeń 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;
Uwaga / Notatka
W powyższym przykładzie kodu Button::Click_revoker jest aliasem typu dla winrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase>. Podobny wzorzec dotyczy wszystkich zdarzeń C++/WinRT. Każde zdarzenie środowiska uruchomieniowego systemu Windows ma przeciążoną funkcję usuwającą, która zwraca odwoływacz zdarzeń, i typ tego odwoływacza jest elementem członkowskim źródła zdarzeń. Aby więc podjąć inny przykład, zdarzenie CoreWindow::SizeChanged ma przeciążenie funkcji rejestracji, które zwraca wartość typu CoreWindow::SizeChanged_revoker.
Możesz rozważyć odwołanie procedur obsługi w scenariuszu nawigacji między stronami. Jeśli wielokrotnie przechodzisz do strony, a następnie się cofasz, możesz odwołać wszystkie procedury obsługi przy opuszczeniu strony. Alternatywnie, jeśli ponownie używasz tej samej instancji strony, sprawdź wartość swojego tokenu i zarejestruj się tylko wtedy, gdy jeszcze nie został ustawiony (if (!m_token){ ... }). Trzecią opcją jest przechowywanie dezaktywatora zdarzeń na stronie jako członek danych. Czwartą opcją, jak opisano w dalszej części tego tematu, jest przechwycenie silnego lub słabego odwołania do tego obiektu w funkcji lambda.
Jeśli nie można zarejestrować delegata z automatycznym wycofywaniem
Jeśli spróbujesz określić winrt::auto_revoke podczas rejestrowania delegata, a wynikiem jest winrt::hresult_no_interface wyjątek, zwykle oznacza to, że źródło zdarzeń nie obsługuje słabych odwołań. Jest to na przykład typowa sytuacja w przestrzeni nazw Windows.UI.Composition. W takiej sytuacji nie można użyć funkcji automatycznego odwoływanie. Będziesz musiał przejść do ręcznego usuwania obsługi zdarzeń.
Typy delegatów dla akcji i operacji asynchronicznych
W powyższych przykładach użyto delegata typu RoutedEventHandler, ale oczywiście istnieje wiele innych typów delegatów. Na przykład asynchroniczne akcje i operacje (zarówno te z postępem, jak i bez niego) mają ukończone operacje i/lub zdarzenia postępu, które oczekują delegatów odpowiedniego typu. Na przykład zdarzenie dotyczące postępu w operacji asynchronicznej (czyli wszystko, co implementuje IAsyncOperationWithProgress) wymaga delegata typu AsyncOperationProgressHandler. Oto przykład kodu tworzenia delegata tego typu przy użyciu funkcji lambda. W przykładzie pokazano również, jak utworzyć 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 };
}
Jak sugeruje powyższy komentarz "coroutine", zamiast używać delegata z ukończonymi zdarzeniami asynchronicznych akcji i operacji, prawdopodobnie okaże się, że bardziej naturalne jest użycie kohroutyn. Aby uzyskać szczegółowe informacje i przykłady kodu, zapoznaj się z Współbieżnością i Operacjami Asynchronicznymi za pomocą języka C++/WinRT.
Uwaga / Notatka
Nie jest poprawne zaimplementowanie więcej niż jednej procedury kończącej dla asynchronicznej akcji lub operacji. Możesz mieć pojedynczego delegata na ukończone zdarzenie lub co_await go. Jeśli masz obie te elementy, drugi zakończy się niepowodzeniem.
Jeśli będziesz trzymać się delegatów zamiast coroutine, możesz wybrać prostszą składnię.
async_op_with_progress.Completed(
[](auto&& /*sender*/, AsyncStatus const /* args */)
{
// ...
});
Typy delegatów zwracające wartość
Niektóre typy delegatów muszą samodzielnie zwrócić wartość. Przykładem jest ListViewItemToKeyHandler, który zwraca ciąg. Oto przykład tworzenia delegata tego typu (zwróć uwagę, że funkcja lambda zwraca wartość).
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";
});
}
Bezpieczne uzyskiwanie dostępu do tego wskaźnika za pomocą delegata obsługi zdarzeń
Jeśli obsłużysz zdarzenie za pomocą funkcji składowej obiektu lub z poziomu funkcji lambda wewnątrz funkcji składowej obiektu, należy zastanowić się nad względnymi okresami istnienia adresata zdarzenia (obiekt obsługujący zdarzenie) i źródłem zdarzenia (obiekt wywołujący zdarzenie). Aby uzyskać więcej informacji i przykłady kodu, zobacz Silne i słabe odwołania w języku C++/WinRT.
Ważne interfejsy API
- winrt::auto_revoke_t struktura znacznika
- funkcja get_weak w winrt::implements
- funkcja winrt::implements::get_strong