Aplikacje mobilne · Zdarzenia
Obsługa zdarzeń: anatomia handlera i okna DisplayAlert
Aplikacja ma dwie części: XAML (wygląd) i C# (logika). Żeby zareagowała na kliknięcie, trzeba połączyć kontrolkę z metodą. Rozbierzesz na części metodę obsługi zdarzenia, a potem użyjesz okien DisplayAlert i DisplayPromptAsync.
- około 45 minut
- .NET MAUI · zdarzenia · async/await · DisplayAlert
- INF.04.6.2
Po tej lekcji: wyjaśnić każdy element sygnatury handlera (private async void Nazwa(object sender, EventArgs e)), użyć DisplayAlert z jednym i dwoma przyciskami oraz DisplayPromptAsync i obsłużyć wynik null.
Bezpieczne potwierdzenie i mały quiz
Kliknięcie Usuń nie powinno od razu niszczyć danych. Odczytamy odpowiedź z okna, a dopiero potem podejmiemy decyzję. Później dodamy gest i krótki efekt karty.
Najpierw uruchomisz mały ekran. Potem połączysz go z kodem i sprawdzisz konkretne wyniki. Dodatki wykonuj dopiero, gdy wersja podstawowa działa. Quiz i oceniane zadania pozostają w biurku po zalogowaniu.
Laboratorium · ćwiczenie w przeglądarce, do którego odsyłają rozgrzewki na końcu lekcji
2 · Sprawdź w działaniu
Co zwracają DisplayAlert i DisplayPromptAsync
Kliknij „Usuń element” i wybierz Tak oraz Nie. Potem „Podaj swoje imię”: wpisz imię, zostaw puste pole, a w drugiej próbie kliknij Anuluj. Obserwuj zmienne po prawej.
Najpierw przewidź, potem sprawdź: W laboratorium okien wybierz potwierdzenie. Najpierw anuluj, potem zaakceptuj. Porównaj wartość bool i komunikat; brak zgody nie jest błędem programu.
Okno z dwoma przyciskami zwraca bool, a okno z polem tekstowym zwraca string albo null po anulowaniu. Zawsze sprawdzaj wynik. Model pomaga zobaczyć mechanizm; właściwą aplikację uruchomisz z plików poniżej.
1. Przygotuj miejsce pracy
Co dodajesz, a co podmieniasz?
Stronę dodaj przez Projekt → Dodaj → Nowy element → .NET MAUI ContentPage (XAML). Powstaje para .xaml i .xaml.cs. Dla ContentView wybierz szablon ContentView; dla modeli i serwisów zwykłą klasę. Jeśli plik już istnieje, podmień wskazaną zawartość — nie twórz drugiej klasy o tej samej nazwie.
| Plik | Rola w ćwiczeniu |
|---|---|
| PotwierdzeniePage.xaml | Wygląd strony lub kontrolki |
| PotwierdzeniePage.xaml.cs | Logika i obsługa danych |
| QuizPage.xaml.cs | Logika i obsługa danych |
| QuizPage.xaml | Wygląd strony lub kontrolki |
| AnimacjePage.xaml | Wygląd strony lub kontrolki |
| AnimacjePage.xaml.cs | Logika i obsługa danych |
Zanim uruchomisz: trzy połączenia
x:Class="MojaAplikacja.NazwaStrony" w XAML musi zgadzać się z namespace MojaAplikacja i nazwą klasy C#. InitializeComponent() tworzy kontrolki opisane w XAML.
x:Name="PoleImie" daje kodowi C# dostęp przez PoleImie.Text. Z kolei Clicked="Zapisz_Clicked" wskazuje metodę o tej nazwie w pliku .xaml.cs. Sam napis przycisku w Text niczego nie podpina.
Gdy dodajesz XAML z obsługą zdarzeń, od razu uzupełnij też odpowiadający C#. Uruchamiaj dopiero po skończeniu oznaczonej pary plików; brakujący handler nie jest błędem środowiska.
2. Uruchom sam wygląd — jeszcze bez logiki
W świeżym projekcie zastąp MainPage.xaml poniższym ekranem. W MainPage.xaml.cs zachowaj konstruktor z InitializeComponent(); usuń szablonowy licznik i OnCounterClicked, bo nie ma ich już w XAML. Na tym etapie App i AppShell pozostają z szablonu. To pełny, tymczasowy XAML — później świadomie go podmienisz, nie dokleisz do drugiego ContentPage.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MojaAplikacja.MainPage">
<VerticalStackLayout Padding="24" Spacing="14">
<Label Text="Usuwanie wymaga decyzji" FontSize="24" />
<Button Text="Usuń — na razie bez działania" />
</VerticalStackLayout>
</ContentPage>
- Pierwsza próba Uruchom. Zobaczysz kontrolki, ale przyciski bez Clicked jeszcze nic nie robią. Sprawdź rozmiar i kolejność elementów. Zatrzymaj aplikację przed następnym etapem.
Zacznijmy od połączenia
Zdarzenie to wskazanie metody
W XAML wpisujesz <Button Text="Kliknij mnie" Clicked="PrzyciskKlik_Click" />. W C# istnieje metoda o dokładnie tej samej nazwie, wykonywana po kliknięciu. To jest złączenie zdarzenia z metodą.
Umiejętność poprawnego napisania handlera (sygnatura, async/await, okna komunikatów) sprawdza się niemal w każdym zadaniu praktycznym z aplikacją mobilną.
1 · Rozbierz sygnaturę
private async void Zaloguj_Click(object sender, EventArgs e)
Kliknij kolejne części.
Kto widzi metodę?
Metoda widoczna tylko w tej klasie. W naszych projektach to wystarcza.
Nie wystawiasz metody na zewnątrz.
Po co?
Pozwala użyć await, np. aby poczekać na zamknięcie okna dialogowego. Dzięki temu aplikacja się nie zawiesza.
Brak async przy użyciu await oznacza błąd kompilacji.
Co zwraca?
Nic. Handlery zdarzeń prawie zawsze są void.
Nie ma wyniku do odebrania.
Jak ją nazwać?
Konwencja: nazwa kontrolki i rodzaj zdarzenia (Przycisk_Click, Suwak_ValueChanged). Nazwa musi być identyczna jak w XAML.
Literówka uniemożliwia kompilację.
Kto wywołał zdarzenie?
Obiekt, który zgłosił zdarzenie, np. konkretny przycisk.
Jedna metoda może obsłużyć kilka przycisków.
Jakie są szczegóły?
Dodatkowe dane o zdarzeniu. Dla suwaka to ValueChangedEventArgs, dla DatePickera DateChangedEventArgs.
Typ zależy od kontrolki.
5 · Interfejs nie może stać
async i await w interfejsie
DisplayAlert i DisplayPromptAsync są asynchroniczne: nie blokują wątku, który rysuje interfejs. Dzięki async/await aplikacja pozostaje płynna, a okno pojawia się natychmiast. Kod po await wykona się dopiero po odpowiedzi użytkownika.
Dobra praktyka: opakuj kod handlera w try/catch, aby ewentualny błąd pokazał krótki komunikat zamiast wywalić całą aplikację.
Sprawdź w działaniu
Dotknij karty: mały ruch daje informację
Wybierz efekt, zmień czas i dotknij karty. Obserwuj kod, który odpowiada gestowi. To model przeglądarkowy — właściwy kod MAUI jest poniżej.
Gest uruchamia zdarzenie. Animacja ma wartość docelową i czas, a await pozwala wykonać ruchy w kolejności.
3. Wskaż stronę startową ćwiczenia
Po dodaniu docelowych stron podmień App.xaml.cs na poniższy. App.xaml zostaje z szablonu. Od tej chwili startuje PotwierdzeniePage, a nie wcześniejszy tymczasowy MainPage z AppShell. Uzupełnij jej pliki w kolejnych etapach, zanim ponownie uruchomisz projekt.
namespace MojaAplikacja;
public partial class App : Application
{
public App() => InitializeComponent();
protected override Window CreateWindow(IActivationState? activationState)
=> new Window(new NavigationPage(new PotwierdzeniePage()));
}
CreateWindowTworzy okno aplikacji. NavigationPage jest kontenerem zwykłych stron i daje przycisk powrotu; nie łączymy go z Shell.new PotwierdzeniePage()Tworzy instancję docelowej strony. Aby testować drugi niezależny przykład, zatrzymaj program, zmień tylko ten typ i uruchom ponownie.
4. Połącz wygląd z działaniem
Teraz zbuduj docelowy przykład. Pełne pliki poniżej zastępują swoje wcześniejsze wersje. Przeczytaj opis nad kodem i objaśnienia pod nim; potem sprawdź przepływ danych, nie tylko brak czerwonych podkreśleń.
Gdy przykład ma kilka plików zależnych od siebie, najpierw dodaj wszystkie wskazane typy, a potem uruchom całość. Ustaw breakpoint w obsłudze głównego przycisku i prześledź jedną próbę.
Budowa krok po kroku
PotwierdzeniePage.xaml
Dodaj PotwierdzeniePage. Każdy Clicked wskazuje inną decyzję użytkownika. Nie zmieniaj nazwy metody tylko w jednym pliku.
Trzy przyciski, każdy z własną metodą Clicked, i etykieta na wynik.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MojaAplikacja.PotwierdzeniePage" Title="Potwierdzenie">
<VerticalStackLayout Padding="20" Spacing="12">
<Label Text="Potwierdzenie usunięcia" FontSize="20" />
<Button Text="Usuń element" Clicked="Usun_Click" />
<Button Text="Podaj swoje imię" Clicked="PodajImie_Click" />
<Button Text="Wyloguj" Clicked="Wyloguj_Click" />
<Label x:Name="StatusLabel" FontSize="16" />
</VerticalStackLayout>
</ContentPage>
Clicked="Usun_Click"Nazwa metody w C# musi być identyczna.Title="Potwierdzenie"Tytuł strony widoczny na pasku nawigacji.
Budowa krok po kroku
PotwierdzeniePage.xaml.cs
DisplayAlertAsync z dwoma odpowiedziami zwraca bool. await czeka na decyzję, nie blokując całej aplikacji. Prompt może zwrócić null po anulowaniu.
Trzy handlery pokazują trzy sposoby użycia okien dialogowych.
namespace MojaAplikacja;
public partial class PotwierdzeniePage : ContentPage
{
public PotwierdzeniePage() => InitializeComponent();
private async void Usun_Click(object sender, EventArgs e)
{
// DisplayAlert z dwoma przyciskami zwraca bool:
// true = kliknięto pierwszy („Tak”), false = drugi („Nie”)
bool decyzja = await DisplayAlertAsync(
"Usuwanie", "Czy na pewno chcesz usunąć?", "Tak", "Nie");
StatusLabel.Text = decyzja ? "Usunięto." : "Anulowano.";
}
private async void PodajImie_Click(object sender, EventArgs e)
{
// DisplayPromptAsync to szybkie okno wprowadzania tekstu
string imie = await DisplayPromptAsync("Wpisz imię", "Podaj swoje imię:");
if (!string.IsNullOrWhiteSpace(imie))
StatusLabel.Text = $"Witaj, {imie}!";
}
private async void Wyloguj_Click(object sender, EventArgs e)
{
// Wersja z jednym przyciskiem: tylko informuje użytkownika
await DisplayAlertAsync("Wylogowanie", "Zostałeś wylogowany", "OK");
}
}
public PotwierdzeniePage() => InitializeComponent();Konstruktor zapisany krótko (wyrażenie=>). Robi to samo co wersja z klamrami.bool decyzja = await DisplayAlertAsync(..., "Tak", "Nie")Dwa przyciski:truedla pierwszego,falsedla drugiego.decyzja ? "Usunięto." : "Anulowano."Operator warunkowy wybiera napis w zależności od decyzji.DisplayPromptAsync(...)Odpowiednik klasycznego „InputBox”. Zwracastringalbonullpo anulowaniu.!string.IsNullOrWhiteSpace(imie)Odrzucanull, pusty napis i same spacje.DisplayAlertAsync(..., "OK")z jednym przyciskiem Tylko informuje użytkownika. Wynik nie jest potrzebny.
Zatrzymaj się przed kolejnym wariantem
Nie zastępuj jeszcze działającej wersji. Wykonaj tę próbę i dopiero potem przejdź do dodatku.
Dopiero teraz: drugi wariant lub rozszerzenie
To następny etap tej lekcji, nie warunek pierwszego uruchomienia. Porównaj jego rolę z działającą bazą. Jeśli brakuje czasu, wróć do niego po sprawdzeniu podstawowej wersji.
Dodatek do działającej wersji
Widok do przykładu QuizPage
Druga mała aplikacja na osobnej QuizPage. Przycisk wywołuje istniejącą metodę Usun_Click; nazwa jest historyczna, ale musi zgadzać się z C#.
Dodaj ContentPage (XAML) o nazwie QuizPage. Wstaw ten XAML i kod QuizPage.xaml.cs z poprzedniej sekcji. Pozostałe puste metody ze starego formularza nie są potrzebne.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MojaAplikacja.QuizPage" Title="Krótki quiz">
<VerticalStackLayout Padding="20" Spacing="12">
<Button Text="Pytanie: 7 razy 8" Clicked="Usun_Click" />
</VerticalStackLayout>
</ContentPage>
- Przeczytaj powiązanie x:Name identyfikuje kontrolkę, a Binding odczytuje właściwość obiektu ustawionego jako BindingContext. To dwa różne mechanizmy.
Dodatek do działającej wersji
Pytanie matematyczne z obsługą błędów
Najpierw sprawdź null, później TryParse, na końcu wynik56. Dzięki temu Anuluj, tekst i błędna liczba nie są traktowane tak samo.
Przykład z DisplayPromptAsync, int.TryParse i try/catch, który rozwiązuje zadanie trudniejsze w prostszej wersji.
namespace MojaAplikacja;
public partial class QuizPage : ContentPage
{
public QuizPage() => InitializeComponent();
private async void Usun_Click(object sender, EventArgs e)
{
try
{
string? odpowiedz = await DisplayPromptAsync("Quiz", "Ile to 7 razy 8?", keyboard: Keyboard.Numeric);
if (odpowiedz is null)
return; // użytkownik kliknął „Anuluj”
if (int.TryParse(odpowiedz, out int liczba) && liczba == 56)
await DisplayAlertAsync("Wynik", "Brawo!", "OK");
else
await DisplayAlertAsync("Wynik", "Niestety, spróbuj ponownie", "OK");
}
catch (Exception ex)
{
await DisplayAlertAsync("Błąd", ex.Message, "OK");
}
}
}
string? odpowiedz = await DisplayPromptAsync(...)Typstring?przyznaje, że wynik może byćnull.if (odpowiedz is null) return;Po „Anuluj” kończymy metodę, zanim cokolwiek zrobimy z wynikiem.int.TryParse(odpowiedz, out int liczba)Bezpieczna zamiana tekstu na liczbę. Nie rzuca wyjątku.catch (Exception ex)Chroni przed awarią i pokazuje komunikat błędu.
Dodatek do działającej wersji
TapGestureRecognizer: gest na karcie
Dodatek na osobnej AnimacjePage. Border reaguje na TapGestureRecognizer, a przycisk zapewnia także uruchomienie z klawiatury.
Dodaj ContentPage (XAML) AnimacjePage. Border nie ma Clicked jak Button: rozpoznawanie dotknięcia dodajemy przez GestureRecognizers. Przycisk zapewnia również wygodną obsługę klawiaturą.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MojaAplikacja.AnimacjePage">
<VerticalStackLayout Padding="20" Spacing="12">
<Border x:Name="Karta" Padding="28" BackgroundColor="#19376E"
Stroke="#4EE0B5" StrokeShape="RoundRectangle 16">
<Border.GestureRecognizers>
<TapGestureRecognizer Tapped="Karta_Tapped" />
</Border.GestureRecognizers>
<Label Text="Dotknij mnie" TextColor="White" FontSize="22" />
</Border>
<Button Text="Odtwórz efekt (także z klawiatury)" Clicked="Efekt_Clicked" />
</VerticalStackLayout>
</ContentPage>
- Przeczytaj powiązanie x:Name identyfikuje kontrolkę, a Binding odczytuje właściwość obiektu ustawionego jako BindingContext. To dwa różne mechanizmy.
Dodatek do działającej wersji
Fade, Scale i Rotate w kontrolowanej kolejności
Oba handlery wywołują tę samą metodę. Flaga chroni przed nakładaniem animacji, a finally przywraca zwykłą skalę, obrót i przezroczystość.
W wariancie podstawowym to krótka sekwencja po świadomym dotknięciu. Zadbaj o dostępność: animacja nie może być jedynym sygnałem wyniku; w prawdziwej aplikacji dodaj możliwość ograniczenia ruchu.
namespace MojaAplikacja;
public partial class AnimacjePage : ContentPage
{
private bool _animacja;
public AnimacjePage() => InitializeComponent();
private async void Karta_Tapped(object? sender, TappedEventArgs e)
=> await PokazEfektAsync();
private async void Efekt_Clicked(object? sender, EventArgs e)
=> await PokazEfektAsync();
private async Task PokazEfektAsync()
{
if (_animacja) return;
_animacja = true;
try
{
await Karta.FadeToAsync(0.55, 180);
await Karta.FadeToAsync(1, 180);
await Karta.ScaleToAsync(1.04, 180, Easing.CubicOut);
await Karta.ScaleToAsync(1, 180);
await Karta.RotateToAsync(6, 140);
await Karta.RotateToAsync(0, 140);
}
finally
{
Karta.Opacity = 1; Karta.Scale = 1; Karta.Rotation = 0;
_animacja = false;
}
}
}
TappedEventArgsDane gestu dotknięcia. Dla Button nadal używasz EventArgs i Clicked.FadeToAsync(0.55, 180)Docelowe Opacity=0.55 i czas180ms. Skala i obrót mają swoje odrębne właściwości.ScaleToAsync(1.04, ...)1 to rozmiar bazowy,1.04 to104%. Nie jest to rozmiar w pikselach.RotateToAsync(6, ...)Obrót do6stopni, nie o6kolejnych stopni. Powrót do0 przywraca pozycję.awaitKażda kolejna animacja czeka na poprzednią. Bez await ruchy mogą wystartować jednocześnie.- Flaga i finally Kolejne dotknięcie nie nakłada drugiej sekwencji; finally przywraca stan.
5. Odbiór: czy naprawdę działa?
Wykonaj próby w swojej aplikacji, nie tylko w modelu na stronie. Zaznaczaj dopiero po sprawdzeniu wyniku. Funkcje urządzenia wymagają testu na właściwej platformie.
7 · Czego unikać
Cztery błędy z handlerami
`await` bez `async`
private void Usun_Click(object sender, EventArgs e)
{
await DisplayAlertAsync("A", "B", "OK");
}Błąd kompilatora: await wymaga metody oznaczonej async.
private async void Usun_Click(object sender, EventArgs e)
{
await DisplayAlertAsync("A", "B", "OK");
}Metoda używająca await musi być async.
Brak sprawdzenia `null` po `DisplayPromptAsync`
string imie = await DisplayPromptAsync("Imię", "Podaj:");
StatusLabel.Text = imie.ToUpper();Po „Anuluj” wynik to null, więc ToUpper() rzuca wyjątek.
if (!string.IsNullOrWhiteSpace(imie))
StatusLabel.Text = imie.ToUpper();Zawsze sprawdź wynik przed użyciem.
Literówka w nazwie metody
<Button Clicked="Usun_Clik" />Aplikacja się nie skompiluje: w klasie nie ma takiej metody.
<Button Clicked="Usun_Click" />Nazwa w XAML musi być identyczna z metodą w C#.
Czasochłonna operacja bez `await`
Thread.Sleep(3000);Interfejs zamarza na czas trwania operacji.
await Task.Delay(3000);await oddaje sterowanie wątkowi interfejsu.
Ćwiczenie z pomocą · samodzielna praca
6. Mała zmiana, którą zrobisz samodzielnie
Zamień quiz na pytanie o liczbę dni tygodnia i popraw sprawdzaną odpowiedź.
Zamień quiz na pytanie o liczbę dni tygodnia i popraw sprawdzaną odpowiedź. Dotychczasowe działanie ma pozostać poprawne.
? Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź
Zmień treść przycisku i DisplayPromptAsync.
Krok 2 — pokaż podpowiedź
W warunku porównania zmień 56 na 7.
Krok 3 — pokaż podpowiedź
Sprawdź ponownie poprawną odpowiedź, błędną i anulowanie — sam nowy napis nie zmienia logiki.
Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.
8 · Sprawdź się
Handlery i okna dialogowe
Cztery krótkie sytuacje.
Podsumowanie
Co warto zapamiętać
- Zdarzenie łączy kontrolkę z metodą po identycznej nazwie w XAML i C#.
- Sygnatura:
private async void Nazwa(object sender, EventArgs e).asyncjest wymagany przyawait. DisplayAlertz dwoma przyciskami zwracabool, a z jednym tylko informuje.DisplayPromptAsynczwracastringalbonullpo anulowaniu. Sprawdź wynik.- Kod handlera warto opakować w
try/catch, a czekanie robić przezawait.
Dalej na swoim koncie
Rozgrzewki, quiz i zadania są w biurku ucznia.
Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.
Zaloguj się →