Lekcja 10. Obsługa zdarzeń kontrolek — Switch, Slider, Picker, Date/TimePicker
ŚredniPo co się tego uczymy?
Każda kontrolka wyboru ma swoje WŁASNE zdarzenie i swój WŁASNY typ EventArgs z konkretnymi danymi. Ta lekcja systematycznie przechodzi przez zdarzenia Switch, Slider, Stepper, Picker, DatePicker, TimePicker i SearchBar — a na koniec pokazuje "wzorzec egzaminacyjny" łączący to wszystko w jeden przykład logowania.
Teoria
Tabela zdarzeń poszczególnych kontrolek
| Kontrolka | Zdarzenie | Typ EventArgs / dane |
|---|---|---|
| Switch | Toggled |
ToggledEventArgs — e.Value to nowy stan (bool). |
| Slider | ValueChanged |
ValueChangedEventArgs — e.NewValue (double), czasem też e.OldValue. |
| Stepper | ValueChanged |
ValueChangedEventArgs — e.NewValue (double). |
| DatePicker | DateSelected |
DateChangedEventArgs — e.NewDate (DateTime). |
| TimePicker | PropertyChanged (brak własnego eventu) |
PropertyChangedEventArgs — trzeba sprawdzić e.PropertyName == nameof(TimePicker.Time). |
| Picker | SelectedIndexChanged |
Zwykłe EventArgs — wartość odczytujesz z SelectedItem/SelectedIndex kontrolki. |
| SearchBar | SearchButtonPressed |
Zwykłe EventArgs — wartość odczytujesz z Text kontrolki (albo z sender). |
Przykłady zdarzeń — Switch, Slider, DatePicker
// Switch - tryb nocny
private void TrybNocny_Toggled(object sender, ToggledEventArgs e)
{
BackgroundColor = e.Value ? Colors.Black : Colors.White;
}
// Slider - odczyt wartosci
private void JasnoscSlider_ValueChanged(object sender, ValueChangedEventArgs e)
{
JasnoscLabel.Text = $"Jasność: {e.NewValue:F0}%";
}
// DatePicker - wybor daty
private void DataWybor_DateSelected(object sender, DateChangedEventArgs e)
{
WynikLabel.Text = $"Wybrana data: {e.NewDate:d}";
}
Szczególny przypadek: TimePicker
W przeciwieństwie do DatePickera, TimePicker NIE MA własnego zdarzenia w rodzaju "TimeSelected" — trzeba nasłuchiwać ogólnego PropertyChanged i sprawdzić, czy zmieniła się WŁAŚNIE właściwość Time:
private void GodzinaWybor_PropertyChanged(object sender,
System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(TimePicker.Time))
WynikLabel.Text = $"Wybrana godzina: {GodzinaWybor.Time}";
}
Użycie nameof(TimePicker.Time) zamiast wpisania na sztywno stringa "Time" to dobry nawyk — kompilator sam sprawdzi poprawność pisowni.
SearchBar — wyszukiwanie
private void SearchBar_SearchButtonPressed(object sender, EventArgs e)
{
var pasekWyszukiwania = (SearchBar)sender;
WynikLabel.Text = $"Wyszukano: {pasekWyszukiwania.Text}";
}
Tu sender rzutujemy na konkretny typ (SearchBar), żeby odczytać jego właściwość Text — przydatne, gdy chcemy uniknąć dodatkowego x:Name.
Minimalny "wzorzec egzaminacyjny"
Formularz logowania łączący nazwane kontrolki, odczyt wartości, handler, async/await, komunikat i prostą walidację pokazuje wszystko, czego zwykle oczekuje się w zadaniu egzaminacyjnym z obsługą zdarzeń — zobacz pełny przykład w sekcji kodu tej lekcji.
Schemat
Ktore zdarzenie i jaki typ danych dla kazdej kontrolki: Switch -> Toggled -> e.Value (bool) Slider -> ValueChanged -> e.NewValue (double) Stepper -> ValueChanged -> e.NewValue (double) DatePicker -> DateSelected -> e.NewDate (DateTime) TimePicker -> PropertyChanged -> sprawdz e.PropertyName == nameof(TimePicker.Time) Picker -> SelectedIndexChanged -> czytaj z SelectedItem kontrolki SearchBar -> SearchButtonPressed -> czytaj z Text kontrolki
Przykład z życia
Aplikacja pogodowa, w której przesuwasz suwak czasu, żeby zobaczyć prognozę na różne godziny dnia, wykorzystuje dokładnie zdarzenie ValueChanged Slidera — za każdym przesunięciem palca aplikacja aktualizuje wyświetlaną temperaturę i ikonę pogody na podstawie e.NewValue.
LogowaniePage.xaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ZdarzeniaApp2.LogowaniePage"
Title="Logowanie">
<VerticalStackLayout Padding="20" Spacing="12">
<Entry x:Name="PoleLogin" Placeholder="Login" />
<Entry x:Name="PoleHaslo" Placeholder="Hasło" IsPassword="True" />
<HorizontalStackLayout Spacing="10">
<Switch x:Name="PrzelacznikZapamietaj" Toggled="Zapamietaj_Toggled" />
<Label Text="Zapamiętaj mnie" VerticalOptions="Center" />
</HorizontalStackLayout>
<Button Text="Zaloguj" Clicked="Zaloguj_Click" />
<Label x:Name="StatusLabel" />
</VerticalStackLayout>
</ContentPage>
LogowaniePage.xaml.cs
namespace ZdarzeniaApp2;
public partial class LogowaniePage : ContentPage
{
private bool _zapamietajZaznaczone = false;
public LogowaniePage() => InitializeComponent();
private void Zapamietaj_Toggled(object sender, ToggledEventArgs e)
{
// e.Value to aktualny stan przelacznika (true/false)
_zapamietajZaznaczone = e.Value;
}
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" logowania - w prawdziwej aplikacji tu bylby
// dostep do bazy danych albo zapytanie do serwera (REST API)
bool ok = (login == "admin" && haslo == "1234");
StatusLabel.Text = ok
? $"Zalogowano{(_zapamietajZaznaczone ? " (zapamiętano)" : "")}"
: "Nieprawidłowe dane";
}
}
Komentarz i wyjaśnienie kodu
To jest właśnie "wzorzec egzaminacyjny" — pokazuje WSZYSTKO, czego zwykle oczekuje zadanie z obsługą zdarzeń: nazwane kontrolki (x:Name), odczyt wartości z pól tekstowych, osobny handler dla przełącznika (Zapamietaj_Toggled) zapamiętujący stan w polu prywatnym klasy, główny handler async z walidacją pustych pól, prosty komunikat błędu przez DisplayAlert i finalną zmianę tekstu w zależności od wyniku.
Zwróć uwagę na pole prywatne _zapamietajZaznaczone — przechowuje stan między wywołaniami różnych metod tej samej klasy. To prosty, ale ważny wzorzec: zdarzenie jednej kontrolki (Switch) wpływa na zachowanie zupełnie innej metody (obsługa przycisku Zaloguj).
Ćwiczenie samodzielne
Utwórz stronę z powyższym kodem. Zaloguj się loginem "admin" i hasłem "1234" — raz z zaznaczonym przełącznikiem "Zapamiętaj mnie", raz bez — i sprawdź, czy komunikat się różni.
Zadania do pracy własnej
Dodaj Slider (zakres 0-100) reprezentujący 'siłę hasła' i etykietę pokazującą jego wartość na bieżąco.
Dodaj DatePicker 'Data urodzenia' z DateSelected wypisującym wybraną datę, oraz Picker z listą 3 pytań pomocniczych do odzyskiwania hasła (SelectedIndexChanged wypisujący wybrane pytanie).
Zbuduj formularz rejestracji z: TimePicker 'Preferowana godzina kontaktu' (obsłuż przez PropertyChanged i nameof), SearchBar do wyszukania miasta z zaszytej na sztywno listy 5 miast (SearchButtonPressed pokazujący, czy miasto jest na liście), oraz Stepper 'Wiek' (zakres 13-99) z etykietą aktualizowaną na bieżąco. Po kliknięciu 'Zarejestruj' wszystkie dane powinny się pojawić w jednym DisplayAlert jako podsumowanie.
Typowe błędy
Szukanie nieistniejącego zdarzenia TimeSelected — TimePicker nie ma takiego zdarzenia; trzeba użyć PropertyChanged i sprawdzić nazwę właściwości.
Odczyt e.NewValue jako int bez rzutowania — Slider i Stepper przekazują wartości typu double; przypisanie wprost do zmiennej int wymaga jawnego rzutowania albo zaokrąglenia.
Zapominanie o polu prywatnym do przechowania stanu między zdarzeniami — jeśli jeden handler potrzebuje wiedzieć, co ustawił inny (jak w przykładzie z Switch i przyciskiem logowania), potrzebne jest pole klasy, a nie zmienna lokalna wewnątrz jednej metody.
Wpisywanie stringa "Time" na sztywno zamiast nameof(TimePicker.Time) — działa, ale jest podatne na literówki niewykrywalne przez kompilator.
Nawiązanie do egzaminu zawodowego
To dopełnienie realizacji INF.04.6.2 w zakresie obsługi zdarzeń różnych kontrolek. Znajomość SPECYFICZNYCH nazw zdarzeń i typów EventArgs (Toggled/ValueChanged/DateSelected/PropertyChanged) jest regularnie sprawdzana w zadaniach egzaminacyjnych wymagających reagowania na konkretną kontrolkę.