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.

Ta interakcja wymaga włączonego JavaScriptu.

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.

PlikRola w ćwiczeniu
PotwierdzeniePage.xamlWygląd strony lub kontrolki
PotwierdzeniePage.xaml.csLogika i obsługa danych
QuizPage.xaml.csLogika i obsługa danych
QuizPage.xamlWygląd strony lub kontrolki
AnimacjePage.xamlWygląd strony lub kontrolki
AnimacjePage.xaml.csLogika 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.

MainPage.xaml — etap tymczasowyxaml
<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>
  1. 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.

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.

Ta interakcja wymaga włączonego JavaScriptu.

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.

App.xaml.cscsharp
namespace MojaAplikacja;

public partial class App : Application
{
    public App() => InitializeComponent();
    protected override Window CreateWindow(IActivationState? activationState)
        => new Window(new NavigationPage(new PotwierdzeniePage()));
}
  1. CreateWindow Tworzy okno aplikacji. NavigationPage jest kontenerem zwykłych stron i daje przycisk powrotu; nie łączymy go z Shell.
  2. 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.

PotwierdzeniePage.xamlxaml
<?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>
  1. Clicked="Usun_Click" Nazwa metody w C# musi być identyczna.
  2. 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.

PotwierdzeniePage.xaml.cscsharp
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");
    }
}
  1. public PotwierdzeniePage() => InitializeComponent(); Konstruktor zapisany krótko (wyrażenie =>). Robi to samo co wersja z klamrami.
  2. bool decyzja = await DisplayAlertAsync(..., "Tak", "Nie") Dwa przyciski: true dla pierwszego, false dla drugiego.
  3. decyzja ? "Usunięto." : "Anulowano." Operator warunkowy wybiera napis w zależności od decyzji.
  4. DisplayPromptAsync(...) Odpowiednik klasycznego „InputBox”. Zwraca string albo null po anulowaniu.
  5. !string.IsNullOrWhiteSpace(imie) Odrzuca null, pusty napis i same spacje.
  6. 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.

QuizPage.xamlxaml
<?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>
  1. 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.

QuizPage.xaml.cscsharp
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");
        }
    }

}
  1. string? odpowiedz = await DisplayPromptAsync(...) Typ string? przyznaje, że wynik może być null.
  2. if (odpowiedz is null) return; Po „Anuluj” kończymy metodę, zanim cokolwiek zrobimy z wynikiem.
  3. int.TryParse(odpowiedz, out int liczba) Bezpieczna zamiana tekstu na liczbę. Nie rzuca wyjątku.
  4. 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ą.

AnimacjePage.xamlxaml
<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>
  1. 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.

AnimacjePage.xaml.cscsharp
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;
        }
    }
}
  1. TappedEventArgs Dane gestu dotknięcia. Dla Button nadal używasz EventArgs i Clicked.
  2. FadeToAsync(0.55, 180) Docelowe Opacity=0.55 i czas180ms. Skala i obrót mają swoje odrębne właściwości.
  3. ScaleToAsync(1.04, ...) 1 to rozmiar bazowy,1.04 to104%. Nie jest to rozmiar w pikselach.
  4. RotateToAsync(6, ...) Obrót do6stopni, nie o6kolejnych stopni. Powrót do0 przywraca pozycję.
  5. await Każda kolejna animacja czeka na poprzednią. Bez await ruchy mogą wystartować jednocześnie.
  6. 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`
Tak nie
private void Usun_Click(object sender, EventArgs e)
{
    await DisplayAlertAsync("A", "B", "OK");
}

Błąd kompilatora: await wymaga metody oznaczonej async.

Tak
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`
Tak nie
string imie = await DisplayPromptAsync("Imię", "Podaj:");
StatusLabel.Text = imie.ToUpper();

Po „Anuluj” wynik to null, więc ToUpper() rzuca wyjątek.

Tak
if (!string.IsNullOrWhiteSpace(imie))
    StatusLabel.Text = imie.ToUpper();

Zawsze sprawdź wynik przed użyciem.

Literówka w nazwie metody
Tak nie
<Button Clicked="Usun_Clik" />

Aplikacja się nie skompiluje: w klasie nie ma takiej metody.

Tak
<Button Clicked="Usun_Click" />

Nazwa w XAML musi być identyczna z metodą w C#.

Czasochłonna operacja bez `await`
Tak nie
Thread.Sleep(3000);

Interfejs zamarza na czas trwania operacji.

Tak
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ź.

Oczekiwany rezultat
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.

Ta interakcja wymaga włączonego JavaScriptu.

Podsumowanie

Co warto zapamiętać

  1. Zdarzenie łączy kontrolkę z metodą po identycznej nazwie w XAML i C#.
  2. Sygnatura: private async void Nazwa(object sender, EventArgs e). async jest wymagany przy await.
  3. DisplayAlert z dwoma przyciskami zwraca bool, a z jednym tylko informuje.
  4. DisplayPromptAsync zwraca string albo null po anulowaniu. Sprawdź wynik.
  5. Kod handlera warto opakować w try/catch, a czekanie robić przez await.

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ę →
Źródła i dalsza nauka