Złączenia i obsługa zdarzeń w .NET MAUI

Wstęp – co to są złączenia i po co one są?

W aplikacji mobilnej w .NET MAUI mamy dwie części:

  • XAML – opis wyglądu strony (jak HTML w stronach internetowych),
  • C# – logika, czyli co się ma stać po kliknięciu, wpisaniu tekstu itp.

Aby aplikacja zareagowała na akcję użytkownika (np. kliknięcie przycisku), trzeba połączyć kontrolkę z metodą w C#. Takie połączenie nazywamy złączeniem zdarzenia z metodą.

W praktyce wygląda to tak:

W XAML:

<Button Text="Kliknij mnie" Clicked="PrzyciskKlik_Click"/>

W C#:

private void PrzyciskKlik_Click(object sender, EventArgs e)
{
    // kod, który wykona się po kliknięciu
}

Czyli: złączenie = wskazanie, że dana kontrolka ma uruchomić konkretną metodę, gdy wydarzy się jakieś zdarzenie (np. Clicked).


Z czego składa się metoda obsługująca zdarzenie?

Każda taka metoda ma swoją „anatomie” – elementy, które zawsze się pojawiają.
Przykład:

private async void Zaloguj_Click(object sender, EventArgs e)
{
    await DisplayAlert("Logowanie", "Kliknięto przycisk Zaloguj.", "OK");
}

Rozbijmy to na kawałki:

  • private – metoda jest widoczna tylko w tej klasie. To wystarcza w naszych projektach.
  • async – oznacza, że metoda może korzystać z await, czyli np. czekać na wyświetlenie okna (DisplayAlert, DisplayPromptAsync). Dzięki temu aplikacja się nie zawiesza.
  • void – metoda nic nie zwraca. Handlery zdarzeń (czyli metody reagujące na akcje) prawie zawsze są void.
  • Zaloguj_Click – nazwa metody. Zwykle tworzymy ją tak: nazwa kontrolki + rodzaj zdarzenia (np. Przycisk_Click, Suwak_ValueChanged).
  • object sender – to obiekt, który wywołał zdarzenie. Jeśli klikniemy przycisk, to w sender siedzi właśnie ten przycisk.
  • EventArgs e – dodatkowe dane o zdarzeniu.
    • Przy prostym kliknięciu to „puste” dane (EventArgs).
    • Przy suwaku (Slider) mamy ValueChangedEventArgs z nową wartością.
    • Przy wyborze daty (DatePicker) dostajemy DateChangedEventArgs z nową i starą datą.

1) Komunikat – DisplayAlert

XAML

<?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="PrzewodnikZoo.Page03_Alert"
             Title="Alert">
    <VerticalStackLayout Padding="20" Spacing="12">
        <!-- Nagłówek sekcji -->
        <Label Text="Wyświetl informację" FontSize="20"/>
        <!-- Po kliknięciu wywoła metodę Info_Click w C# -->
        <Button Text="Pokaż Alert" Clicked="Info_Click"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page03_Alert : ContentPage
{
    public Page03_Alert() => InitializeComponent(); // łączy XAML z C#

    // Metoda zdarzeniowa – uruchamia się po kliknięciu przycisku.
    // 'async' pozwala użyć await; 'void' jest typowe dla handlerów zdarzeń.
    private async void Info_Click(object sender, EventArgs e)
    {
        try
        {
            // DisplayAlert: (tytuł, treść, tekst przycisku)
            await DisplayAlert("Informacja", "To jest przykładowy komunikat.", "OK");
        }
        catch (Exception ex)
        {
            await DisplayAlert("Błąd", ex.Message, "OK");
        }
    }
}

Co tu się dzieje:

  • Gdy użytkownik kliknie przycisk, MAUI wywołuje metodę zdarzeniową Info_Click.
  • async/await – bo DisplayAlert jest operacją asynchroniczną (okno dialogowe).
  • InitializeComponent() – tworzy kontrolki z XAML i wiąże je z klasą.
  • Try/catch zabezpiecza aplikację, żeby nie wysypała się w razie błęd

2) Potwierdzenie akcji (Tak/Nie)

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page04_Potwierdzenie" Title="Potwierdzenie">
    <VerticalStackLayout Padding="20" Spacing="12">
        <Label Text="Potwierdzenie usunięcia" FontSize="20"/>
        <!-- Po kliknięciu zapyta użytkownika o decyzję -->
        <Button Text="Usuń element" Clicked="Usun_Click"/>
        <!-- Tutaj pokażemy wynik decyzji -->
        <Label x:Name="StatusLabel" FontSize="16"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page04_Potwierdzenie : ContentPage
{
    public Page04_Potwierdzenie() => InitializeComponent();

    private async void Usun_Click(object sender, EventArgs e)
    {
        // DisplayAlert zwraca bool: True gdy kliknięto pierwszy przycisk ("Tak"), False gdy drugi ("Nie").
        bool decyzja = await DisplayAlert("Usuwanie", "Czy na pewno chcesz usunąć?", "Tak", "Nie");
        StatusLabel.Text = decyzja ? "Usunięto." : "Anulowano.";
    }
}

Co tu się dzieje:

  • DisplayAlert z dwoma przyciskami zwraca bool.
  • Używamy operatora warunkowego ?: do ustawienia wyniku w StatusLabel.

3) Okno wprowadzania danych – DisplayPromptAsync

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page03a_Prompt" Title="Prompt">
    <VerticalStackLayout Padding="20" Spacing="12">
        <Button Text="Podaj imię" Clicked="PodajImie_Click"/>
        <Label x:Name="WynikLabel" />
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page03a_Prompt : ContentPage
{
    public Page03a_Prompt() => InitializeComponent();

    private async void PodajImie_Click(object sender, EventArgs e)
    {
        // Wyświetla okno z polem do wpisania tekstu i zwraca string (lub null, gdy anulujesz).
        string imie = await DisplayPromptAsync("Wpisz imię", "Podaj swoje imię:");
        if (!string.IsNullOrWhiteSpace(imie))
            WynikLabel.Text = $"Witaj, {imie}!";
    }
}

Co tu się dzieje:

  • DisplayPromptAsync to szybkie InputBox.
  • Zwraca tekst, który od razu pokazujemy w Label.

4) Odczyt z Entry

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page05_EntryOdczyt" Title="Entry">
    <VerticalStackLayout Padding="20" Spacing="12">
        <Entry x:Name="PoleTekstowe" Placeholder="Wpisz coś..."/>
        <Button Text="Pokaż" Clicked="PokazTekst_Click"/>
        <Label x:Name="WynikLabel" FontSize="18"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page05_EntryOdczyt : ContentPage
{
    public Page05_EntryOdczyt() => InitializeComponent();

    private void PokazTekst_Click(object sender, EventArgs e)
    {
        // Czytamy wartość z Entry przez jego właściwość Text.
        WynikLabel.Text = $"Wpisałeś: {PoleTekstowe.Text}";
    }
}

Co tu się dzieje:

  • x:Name="PoleTekstowe" pozwala odwołać się w C# do tej kontrolki.
  • Klik przycisku = odczyt Text i wyświetlenie w Label.

5) Zmiana tła strony

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page06_Tlo" Title="Tło">
    <VerticalStackLayout Padding="20" Spacing="12">
        <Label Text="Zmienianie koloru tła" FontSize="20"/>
        <Button Text="Ustaw zielone tło" Clicked="ZmienTlo_Click"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page06_Tlo : ContentPage
{
    public Page06_Tlo() => InitializeComponent();

    private void ZmienTlo_Click(object sender, EventArgs e)
    {
        // BackgroundColor należy do ContentPage – zmienia cały ekran.
        BackgroundColor = Colors.LightGreen;
    }
}

Co tu się dzieje:

  • Modyfikujemy właściwość strony (ContentPage) – zmiana dotyczy całego widoku.

6) Podmiana obrazka po kliknięciu

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page07_ZmienObraz" Title="Obraz">
    <VerticalStackLayout Padding="20" Spacing="12">
        <!-- Obraz startowy -->
        <Image x:Name="Obrazek" Source="sowa.png" HeightRequest="180"/>
        <Button Text="Zmień na słonia" Clicked="ZmienObraz_Click"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page07_ZmienObraz : ContentPage
{
    public Page07_ZmienObraz() => InitializeComponent();

    private void ZmienObraz_Click(object sender, EventArgs e)
    {
        // Pliki obrazów trzymaj w Resources/Images i ustaw akcję kompilacji na MauiImage
        // Wtedy możesz się odwoływać tylko nazwą pliku:
        Obrazek.Source = "slon.png";
    }
}

Co tu się dzieje:

  • Image.Source może wskazywać plik z Resources/Images lub ścieżkę lokalną/URL.

7) Zmiana tekstu w Label

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page09_LabelTekst" Title="Label">
    <VerticalStackLayout Padding="20" Spacing="12">
        <Label x:Name="MojaEtykieta" Text="Tekst początkowy" FontSize="20"/>
        <Button Text="Zmień tekst" Clicked="ZmienTekst_Click"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page09_LabelTekst : ContentPage
{
    public Page09_LabelTekst() => InitializeComponent();

    private void ZmienTekst_Click(object sender, EventArgs e)
    {
        // Podstawowa manipulacja właściwością Text
        MojaEtykieta.Text = "Nowy tekst ustawiony w kodzie!";
    }
}

Co tu się dzieje:

  • Klasyczny handler zdarzenia → modyfikuje właściwość kontrolki.

8) Switch – tryb nocny

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page10_SwitchTryb" Title="Switch">
    <VerticalStackLayout Padding="20" Spacing="12">
        <Label Text="Tryb nocny" FontSize="20"/>
        <!-- Zdarzenie Toggled wyśle e.Value: True / False -->
        <Switch Toggled="TrybNocny_Toggled"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page10_SwitchTryb : ContentPage
{
    public Page10_SwitchTryb() => InitializeComponent();

    private void TrybNocny_Toggled(object sender, ToggledEventArgs e)
    {
        // e.Value to aktualny stan przełącznika
        BackgroundColor = e.Value ? Colors.Black : Colors.White;
    }
}

Co tu się dzieje:

  • ToggledEventArgs przekazuje nowy stan przełącznika (e.Value).

9) Slider – odczyt wartości

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page11_Slider" Title="Slider">
    <VerticalStackLayout Padding="20" Spacing="12">
        <Label x:Name="JasnoscLabel" Text="Jasność: 50%" FontSize="20"/>
        <!-- ValueChanged daje nam starą i nową wartość -->
        <Slider Minimum="0" Maximum="100" Value="50" ValueChanged="JasnoscSlider_ValueChanged"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page11_Slider : ContentPage
{
    public Page11_Slider() => InitializeComponent();

    private void JasnoscSlider_ValueChanged(object sender, ValueChangedEventArgs e)
    {
        // e.NewValue to nowa wartość suwaka (double)
        JasnoscLabel.Text = $"Jasność: {e.NewValue:F0}%";
    }
}

Co tu się dzieje:

  • Format {F0} – zaokrąglenie do 0 miejsc po przecinku (ładniejszy zapis).

10) Stepper – licznik

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page12_Stepper" Title="Stepper">
    <VerticalStackLayout Padding="20" Spacing="12">
        <Label x:Name="LicznikLabel" Text="Wartość: 0" FontSize="20"/>
        <Stepper Minimum="0" Maximum="10" Increment="1" ValueChanged="LicznikStepper_ValueChanged"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page12_Stepper : ContentPage
{
    public Page12_Stepper() => InitializeComponent();

    private void LicznikStepper_ValueChanged(object sender, ValueChangedEventArgs e)
    {
        LicznikLabel.Text = $"Wartość: {e.NewValue}";
    }
}

Co tu się dzieje:

  • Stepper zmienia wartość skokowo – kontrolujesz to Increment.

11) DatePicker – wybór daty

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page13_DatePicker" Title="DatePicker">
    <VerticalStackLayout Padding="20" Spacing="12">
        <!-- DateSelected odpala się po zmianie daty -->
        <DatePicker DateSelected="DataWybor_DateSelected"/>
        <Label x:Name="WynikLabel" FontSize="18"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page13_DatePicker : ContentPage
{
    public Page13_DatePicker() => InitializeComponent();

    private void DataWybor_DateSelected(object sender, DateChangedEventArgs e)
    {
        // e.NewDate – nowo wybrana data (DateTime)
        WynikLabel.Text = $"Wybrana data: {e.NewDate:d}";
    }
}

Co tu się dzieje:

  • Ładny skrót formatu :d – data w krótkim formacie lokalnym.

12) TimePicker – wybór czasu

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page14_TimePicker" Title="TimePicker">
    <VerticalStackLayout Padding="20" Spacing="12">
        <!-- Słuchamy PropertyChanged, bo TimePicker nie ma osobnego eventu -->
        <TimePicker x:Name="GodzinaWybor" PropertyChanged="GodzinaWybor_PropertyChanged"/>
        <Label x:Name="WynikLabel" FontSize="18"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page14_TimePicker : ContentPage
{
    public Page14_TimePicker() => InitializeComponent();

    private void GodzinaWybor_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        // Reagujemy tylko, gdy zmieniła się właściwość 'Time'
        if (e.PropertyName == nameof(TimePicker.Time))
            WynikLabel.Text = $"Wybrana godzina: {GodzinaWybor.Time}";
    }
}

Co tu się dzieje:

  • PropertyChanged obsługuje zmiany różnych właściwości – filtrujemy po nazwie.

13) Picker – lista wyboru

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page15_PickerLista" Title="Picker">
    <VerticalStackLayout Padding="20" Spacing="12">
        <Picker x:Name="GatunkiPicker" Title="Wybierz gatunek"
                SelectedIndexChanged="Picker_SelectedIndexChanged">
            <!-- Wbudowane źródło danych (na szybko) -->
            <Picker.ItemsSource>
                <x:Array Type="{x:Type x:String}">
                    <x:String>Słoń</x:String>
                    <x:String>Sowa</x:String>
                    <x:String>Pingwin</x:String>
                </x:Array>
            </Picker.ItemsSource>
        </Picker>
        <Label x:Name="WynikLabel" FontSize="18"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page15_PickerLista : ContentPage
{
    public Page15_PickerLista() => InitializeComponent();

    private void Picker_SelectedIndexChanged(object sender, EventArgs e)
    {
        // SelectedItem – bezpieczny rzut do string
        if (GatunkiPicker.SelectedItem is string nazwa)
            WynikLabel.Text = $"Wybrano: {nazwa}";
    }
}

Co tu się dzieje:

  • Na egzaminie często zmieniasz Label po wyborze pozycji z Picker.

14) CheckBox – wybór wielu opcji

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page16_CheckBox" Title="CheckBox">
    <VerticalStackLayout Padding="20" Spacing="12">
        <CheckBox x:Name="Opcja1"/>
        <Label Text="Akceptuję regulamin" />
        <Button Text="Sprawdź" Clicked="Sprawdz_Click"/>
        <Label x:Name="StatusLabel"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page16_CheckBox : ContentPage
{
    public Page16_CheckBox() => InitializeComponent();

    private void Sprawdz_Click(object sender, EventArgs e)
    {
        // IsChecked – True gdy zaznaczony
        StatusLabel.Text = Opcja1.IsChecked ? "Regulamin zaakceptowany" : "Musisz zaakceptować!";
    }
}

Co tu się dzieje:

  • CheckBox przechowuje stan logiczny w IsChecked.

15) RadioButton – jedna opcja z wielu

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page17_Radio" Title="RadioButton">
    <VerticalStackLayout Padding="20" Spacing="8">
        <!-- Ta sama grupa = tylko jedna opcja zaznaczona -->
        <RadioButton x:Name="OpcjaA" Content="Opcja A" GroupName="Grupa"/>
        <RadioButton x:Name="OpcjaB" Content="Opcja B" GroupName="Grupa"/>
        <RadioButton x:Name="OpcjaC" Content="Opcja C" GroupName="Grupa"/>
        <Button Text="Sprawdź" Clicked="SprawdzWybor_Click"/>
        <Label x:Name="WynikLabel"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page17_Radio : ContentPage
{
    public Page17_Radio() => InitializeComponent();

    private void SprawdzWybor_Click(object sender, EventArgs e)
    {
        if (OpcjaA.IsChecked) WynikLabel.Text = "Wybrano A";
        else if (OpcjaB.IsChecked) WynikLabel.Text = "Wybrano B";
        else if (OpcjaC.IsChecked) WynikLabel.Text = "Wybrano C";
        else WynikLabel.Text = "Nie wybrano niczego";
    }
}

Co tu się dzieje:

  • GroupName łączy przyciski w jedną grupę – jedna opcja aktywna.

16) SearchBar – wyszukiwanie

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page18_Search" Title="SearchBar">
    <VerticalStackLayout Padding="20" Spacing="12">
        <!-- Enter / ikona lupy wywołuje SearchButtonPressed -->
        <SearchBar x:Name="Szukaj" Placeholder="Szukaj..." SearchButtonPressed="SearchBar_SearchButtonPressed"/>
        <Label x:Name="WynikLabel"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page18_Search : ContentPage
{
    public Page18_Search() => InitializeComponent();

    private void SearchBar_SearchButtonPressed(object sender, EventArgs e)
    {
        var sb = (SearchBar)sender; // 'sender' to kontrolka, która wywołała zdarzenie
        WynikLabel.Text = $"Wyszukano: {sb.Text}";
    }
}

Co tu się dzieje:

  • sender często rzutujemy na właściwy typ kontrolki, by czytać jej właściwości.

17) ProgressBar – pasek postępu

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page19_Progress" Title="ProgressBar">
    <VerticalStackLayout Padding="20" Spacing="12">
        <ProgressBar x:Name="PostepBar" Progress="0.3"/>
        <Button Text="Dodaj 20%" Clicked="DodajPostep_Click"/>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page19_Progress : ContentPage
{
    public Page19_Progress() => InitializeComponent();

    private void DodajPostep_Click(object sender, EventArgs e)
    {
        // Progress: 0.0 – 1.0
        PostepBar.Progress = Math.Min(1.0, PostepBar.Progress + 0.2);
    }
}

Co tu się dzieje:

  • Zabezpieczamy się przed przekroczeniem 1.0 (Math.Min).

18) ActivityIndicator – animacja ładowania

XAML

<ContentPage ... x:Class="PrzewodnikZoo.Page20_Loader" Title="Ładowanie">
    <VerticalStackLayout Padding="20" Spacing="12">
        <ActivityIndicator x:Name="Loader" IsRunning="False" Color="Red"/>
        <HorizontalStackLayout Spacing="10">
            <Button Text="Start" Clicked="Start_Click"/>
            <Button Text="Stop" Clicked="Stop_Click"/>
        </HorizontalStackLayout>
    </VerticalStackLayout>
</ContentPage>

C#

namespace PrzewodnikZoo;

public partial class Page20_Loader : ContentPage
{
    public Page20_Loader() => InitializeComponent();

    private void Start_Click(object sender, EventArgs e) => Loader.IsRunning = true;
    private void Stop_Click(object sender, EventArgs e)  => Loader.IsRunning = false;
}

Co tu się dzieje:

  • IsRunning steruje animacją „kółeczka”.

19) Wybór pliku – FilePicker (obraz do podglądu)

XAML

<ContentPage ... x:Class="PrzewodnikZoo.FormularzObraz" Title="Formularz z obrazem">
    <VerticalStackLayout Padding="16" Spacing="12">
        <Label Text="Tytuł:" FontAttributes="Bold"/>
        <Entry x:Name="PoleTytul" Placeholder="Wpisz tytuł..." />

        <Label Text="Opis:" FontAttributes="Bold"/>
        <Editor x:Name="PoleOpis" Placeholder="Wpisz opis..." AutoSize="TextChanges" />

        <Label Text="Obraz:" FontAttributes="Bold"/>
        <Image x:Name="PodgladObraz" HeightRequest="200" Aspect="AspectFit" />

        <Button Text="Wybierz obraz" Clicked="WybierzObraz_Click"/>
    </VerticalStackLayout>
</ContentPage>

C#

using Microsoft.Maui.Storage; // FilePicker

namespace PrzewodnikZoo;

public partial class FormularzObraz : ContentPage
{
    public FormularzObraz() => InitializeComponent();

    private async void WybierzObraz_Click(object sender, EventArgs e)
    {
        // Okno systemowe wyboru pliku graficznego
        var wynik = await FilePicker.PickAsync(new PickOptions
        {
            PickerTitle = "Wybierz obraz",
            FileTypes = FilePickerFileType.Images
        });

        if (wynik != null)
        {
            // Ustawiamy obraz jako podgląd
            PodgladObraz.Source = wynik.FullPath;
        }
    }
}

Co tu się dzieje:

  • FilePicker.PickAsync otwiera natywne okno wyboru pliku i zwraca ścieżkę.
  • Przypisujemy ścieżkę do Image.Source.

20) Nawigacja między stronami – Shell (bonus, często przydatne)

Rejestracja trasy (AppShell.xaml.cs)

public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();
        // Rejestrujemy stronę, aby można było nawigować po nazwie
        Routing.RegisterRoute(nameof(DrugaStrona), typeof(DrugaStrona));
    }
}

XAML (przycisk przejścia)

<Button Text="Przejdź do strony 2" Clicked="Idz_Click"/>

C# (nawigacja)

private async void Idz_Click(object sender, EventArgs e)
{
    // Nawigacja do zarejestrowanej strony
    await Shell.Current.GoToAsync(nameof(DrugaStrona));
}

Co tu się dzieje:

  • Shell upraszcza nawigację – rejestrowanie trasy + GoToAsync.
  • Na INF.04 często wystarczy 1–2 ekrany – taki schemat jest idealny.

Szybkie wyjaśnienie elementów metody zdarzeniowej

  • private – dostęp tylko w tej klasie.
  • async – pozwala użyć await (operacje asynchroniczne: okna dialogowe, I/O).
  • void – metody zdarzeniowe zwykle nic nie zwracają.
  • Info_Click(object sender, EventArgs e) – standardowa sygnatura zdarzenia:
    • sender → kontrolka, która wywołała zdarzenie (możesz ją zrzutować, np. (Button)sender),
    • e → dane o zdarzeniu (dla różnych kontrolek różne typy, np. ToggledEventArgs, ValueChangedEventArgs).

***

Kiedy użyć sender, a kiedy x:Name?

  • Masz jedną kontrolkę i używasz jej często → daj x:Name i odwołuj się po nazwie (prościej, czytelniej).
  • Jeden handler dla wielu kontrolek → czytaj sender i rzutuj do właściwego typu: private void Wspolny_Click(object sender, EventArgs e) { if (sender is Button b) b.Text = "OK"; }

Dlaczego async/await w UI?

  • Nie blokujesz wątku UI (apka nie „zamarza”), a użytkownik widzi dialogi od razu.
  • W handlerach trzymaj prostą zasadę: private async void Handler(object s, EventArgs e) { try { await DisplayAlert("Tytuł", "Treść", "OK"); } catch (Exception ex) { // awaryjna informacja (na egzaminie: krótki komunikat) await DisplayAlert("Błąd", ex.Message, "OK"); } }

nameof – drobny, ale ważny nawyk

Używaj nameof(Właściwość) zamiast „magicznego stringa” – kompilator sprawdza pisownię:

if (e.PropertyName == nameof(TimePicker.Time))
{
    WynikLabel.Text = $"Wybrana godzina: {GodzinaWybor.Time}";
}

Minimalny „wzorzec egzaminacyjny” (do szybkiego startu)

XAML:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="AplikacjaEgzaminowa.LogowaniePage"
             Title="Logowanie">
    <VerticalStackLayout Padding="20" Spacing="12">
        <Entry x:Name="PoleLogin" Placeholder="Login"/>
        <Entry x:Name="PoleHaslo" Placeholder="Hasło" IsPassword="True"/>
        <Button Text="Zaloguj" Clicked="Zaloguj_Click"/>
        <Label x:Name="StatusLabel"/>
    </VerticalStackLayout>
</ContentPage>

C#:

public partial class LogowaniePage : ContentPage
{
    public LogowaniePage() => InitializeComponent();

    private async void Zaloguj_Click(object sender, EventArgs e)
    {
        string login = PoleLogin.Text?.Trim();
        string haslo = PoleHaslo.Text;

        if (string.IsNullOrWhiteSpace(login) || string.IsNullOrWhiteSpace(haslo))
        {
            await DisplayAlert("Błąd", "Uzupełnij login i hasło.", "OK");
            return;
        }

        // prosta „logika”
        bool ok = (login == "admin" && haslo == "1234");
        StatusLabel.Text = ok ? "Zalogowano" : "Nieprawidłowe dane";
    }
}

Ten przykład pokazuje wszystko, co egzaminator chce zobaczyć: nazwane kontrolki, odczyt wartości, handler, async/await, komunikat, prostą walidację i zmianę właściwości w reakcji na zdarzenie.