Aplikacje mobilne · Zdarzenia
Zdarzenia kontrolek: Switch, Slider, Stepper, Picker i pickery dat
Każda kontrolka wyboru ma własne zdarzenie i własny typ EventArgs z konkretnymi danymi. Przejdziesz przez zdarzenia Switch, Slider, Stepper, Picker, DatePicker, TimePicker i SearchBar oraz zobaczysz, skąd wziąć nową wartość w każdym z nich.
- około 50 minut
- .NET MAUI · Toggled · ValueChanged · DateSelected
- INF.04.6.2
Po tej lekcji: wskazać zdarzenie i typ EventArgs dla każdej kontrolki wyboru, odczytać nową wartość (e.Value, e.NewValue, e.NewDate) i trzymać stan między zdarzeniami w polu klasy.
Panel sterowany zdarzeniami kontrolek
Nie każda zmiana wymaga przycisku Zapisz. Suwak może od razu zmienić tekst, a przełącznik stan ekranu. Zobaczysz, jakie dane niesie samo zdarzenie.
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
1 · Sprawdź w działaniu
Co zgłasza każda kontrolka
Dotknij każdej kontrolki i przeczytaj dziennik: nazwę zdarzenia i dane, które przekazuje. Zwróć uwagę, które zdarzenia mają e.NewValue, a które wymagają odczytu z kontrolki.
Najpierw przewidź, potem sprawdź: Zmień stan jednej kontrolki i odszukaj nowe dane zdarzenia. Tym razem interesuje nas e.Value lub e.NewValue, a nie sam wygląd kontrolki jak w lekcji o ankiecie.
Nowa wartość jest w e (np. e.Value, e.NewValue, e.NewDate) albo w samej kontrolce (SelectedItem, Text). 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 |
|---|---|
| ZdarzeniaKontrolekPage.xaml | Wygląd strony lub kontrolki |
| ZdarzeniaKontrolekPage.xaml.cs | Logika i obsługa danych |
| LogowaniePage.xaml | Wygląd strony lub kontrolki |
| LogowaniePage.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="Panel sterowania" FontSize="24" />
<Switch />
<Slider Minimum="0" Maximum="100" Value="50" />
</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 egzaminu
Konkretne nazwy zdarzeń to częste pytanie
W zadaniach egzaminacyjnych trzeba reagować na konkretną kontrolkę: przełącznik, suwak, datę. Znajomość nazw zdarzeń i typów EventArgs (Toggled, ValueChanged, DateSelected…) jest regularnie sprawdzana.
Kluczowa wskazówka: w EventArgs zwykle jest nowa wartość, a gdy jej tam nie ma (np. Picker, SearchBar), odczytujesz ją z samej kontrolki.
2 · Ściągawka
Zdarzenia i typy EventArgs
| Kontrolka | Zdarzenie | Typ `EventArgs` i dane |
|---|---|---|
Switch | Toggled | ToggledEventArgs: e.Value (bool), nowy stan. |
Slider | ValueChanged | ValueChangedEventArgs: e.NewValue (double), także e.OldValue. |
Stepper | ValueChanged | ValueChangedEventArgs: e.NewValue (double). |
DatePicker | DateSelected | DateChangedEventArgs: e.NewDate (DateTime? w MAUI10). Sprawdź brak wybranej daty. |
TimePicker | TimeSelected (MAUI10) | TimeChangedEventArgs: e.NewTime (TimeSpan?). W starszych projektach spotkasz obserwację PropertyChanged. |
Picker | SelectedIndexChanged | Zwykłe EventArgs: wartość z SelectedItem lub SelectedIndex. |
SearchBar | SearchButtonPressed | Zwykłe EventArgs: wartość z Text kontrolki (lub z sender). |
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 ZdarzeniaKontrolekPage, 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 ZdarzeniaKontrolekPage()));
}
CreateWindowTworzy okno aplikacji. NavigationPage jest kontenerem zwykłych stron i daje przycisk powrotu; nie łączymy go z Shell.new ZdarzeniaKontrolekPage()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
ZdarzeniaKontrolekPage.xaml
Dodaj ZdarzeniaKontrolekPage. Przy każdej kontrolce znajdź zdarzenie i nazwę handlera. Zapis x:Name nie zastępuje podłączenia zdarzenia.
Każda kontrolka ma w XAML własny atrybut zdarzenia. Nazwy metod muszą być identyczne z C#.
<?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.ZdarzeniaKontrolekPage">
<ScrollView>
<VerticalStackLayout Padding="20" Spacing="12">
<Switch x:Name="TrybNocny" Toggled="TrybNocny_Toggled" />
<Label x:Name="EtykietaJasnosc" Text="Jasność: 50" />
<Slider x:Name="JasnoscSlider" Minimum="0" Maximum="100" Value="50"
ValueChanged="JasnoscSlider_ValueChanged" />
<Label x:Name="EtykietaWiek" Text="Wiek: 18" />
<Stepper x:Name="WiekStepper" Minimum="13" Maximum="99" Value="18"
ValueChanged="WiekStepper_ValueChanged" />
<DatePicker x:Name="DataUrodzenia" DateSelected="DataUrodzenia_DateSelected" />
<TimePicker x:Name="GodzinaKontaktu" TimeSelected="GodzinaKontaktu_TimeSelected" />
<Picker x:Name="PytaniePomocnicze" Title="Pytanie pomocnicze"
SelectedIndexChanged="Pytanie_SelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Imię pierwszego zwierzaka</x:String>
<x:String>Miasto urodzenia</x:String>
<x:String>Ulubiona książka</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
<SearchBar x:Name="SzukajMiasta" Placeholder="Szukaj miasta"
SearchButtonPressed="SzukajMiasta_SearchButtonPressed" />
<Label x:Name="EtykietaWynik" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>
Toggled="...",ValueChanged="..."Atrybuty zdarzeńSwitch,SlideriStepper.DateSelected,TimeSelectedZdarzenia pickerów daty i godziny.SelectedIndexChanged,SearchButtonPressedZdarzeniaPickeriSearchBar.
Budowa krok po kroku
ZdarzeniaKontrolekPage.xaml.cs
Porównuj handler z jego kontrolką: Toggled daje bool, ValueChanged nową liczbę, a Picker wymaga odczytu SelectedItem. SearchButtonPressed uruchamia wyszukanie dopiero po zatwierdzeniu.
Zwróć uwagę na typ drugiego parametru: dla każdej kontrolki jest inny.
namespace MojaAplikacja;
public partial class ZdarzeniaKontrolekPage : ContentPage
{
private readonly string[] _miasta = { "Kraków", "Warszawa", "Gdańsk", "Poznań", "Wrocław" };
public ZdarzeniaKontrolekPage()
{
InitializeComponent();
}
// Switch: e.Value to nowy stan (bool)
private void TrybNocny_Toggled(object sender, ToggledEventArgs e)
{
BackgroundColor = e.Value ? Colors.Black : Colors.White;
}
// Slider: e.NewValue to double
private void JasnoscSlider_ValueChanged(object sender, ValueChangedEventArgs e)
{
EtykietaJasnosc.Text = $"Jasność: {(int)e.NewValue}";
}
// Stepper: też ValueChangedEventArgs
private void WiekStepper_ValueChanged(object sender, ValueChangedEventArgs e)
{
EtykietaWiek.Text = $"Wiek: {e.NewValue:F0}";
}
// DatePicker: e.NewDate to DateTime
private void DataUrodzenia_DateSelected(object sender, DateChangedEventArgs e)
{
EtykietaWynik.Text = $"Data: {e.NewDate:dd.MM.yyyy}";
}
// TimePicker (nowsze wersje MAUI): e.NewTime to TimeSpan
private void GodzinaKontaktu_TimeSelected(object sender, TimeChangedEventArgs e)
{
EtykietaWynik.Text = $"Godzina: {e.NewTime:hh\\:mm}";
}
// Picker: zwykłe EventArgs, wartość czytamy z kontrolki
private void Pytanie_SelectedIndexChanged(object sender, EventArgs e)
{
if (PytaniePomocnicze.SelectedIndex >= 0)
EtykietaWynik.Text = $"Pytanie: {PytaniePomocnicze.SelectedItem}";
}
// SearchBar: tekst czytamy z kontrolki
private void SzukajMiasta_SearchButtonPressed(object sender, EventArgs e)
{
string fraza = SzukajMiasta.Text ?? "";
bool jest = _miasta.Any(m => m.Equals(fraza, StringComparison.OrdinalIgnoreCase));
EtykietaWynik.Text = jest ? $"{fraza}: jest na liście" : $"{fraza}: brak na liście";
}
}
e.Value ? Colors.Black : Colors.Whitee.Valueto nowy stan przełącznika. Trzeba go odczytać ze, bo kontrolka mogła się już zmienić.(int)e.NewValuei{e.NewValue:F0}NewValuetodouble. Rzutowanie albo formatF0zamieniają go na liczbę bez miejsc po przecinku.e.NewDate:dd.MM.yyyyFormat daty zapisany w interpolacji.e.NewTime:hh\\:mmGodzina zTimeSpan. Dwukropek w formacie trzeba poprzedzić ukośnikiem.PytaniePomocnicze.SelectedItemWPickerwartość czytasz z kontrolki, boEventArgsnic nie niesie._miasta.Any(m => m.Equals(fraza, OrdinalIgnoreCase))Sprawdza, czy miasto jest na liście, bez rozróżniania wielkości liter.
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
LogowaniePage.xaml
Osobny wariant LogowaniePage łączy kliknięcie z wcześniej zmienioną flagą przełącznika. Nie dodawaj go jako drugiego korzenia poprzedniej strony.
Przełącznik „Zapamiętaj mnie” ustawia stan, który potem zużyje przycisk „Zaloguj”.
<?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.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>
- 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
LogowaniePage.xaml.cs
admin/1234 to jawne dane ćwiczeniowe. _zapamietajZaznaczone istnieje tylko w pamięci strony; nie przechowuje hasła i nie tworzy sesji.
To lokalna demonstracja zdarzeń na fikcyjnych danych admin/1234, nie bezpieczne logowanie. Switch zmienia wyłącznie flagę w pamięci; nie zapisuje sesji ani hasła. Nie używaj prawdziwych danych logowania.
namespace MojaAplikacja;
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 przełącznika (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 DisplayAlertAsync("Błąd", "Uzupełnij login i hasło.", "OK");
return;
}
// Prosta „logika” logowania: w prawdziwej aplikacji tu byłby
// dostęp do bazy danych albo zapytanie do serwera (REST API)
bool ok = (login == "admin" && haslo == "1234");
StatusLabel.Text = ok
? $"Zalogowano{(_zapamietajZaznaczone ? " (zaznaczono opcję)" : "")}"
: "Nieprawidłowe dane";
}
}
private bool _zapamietajZaznaczonePole klasy: widoczne dla wszystkich metod i żyje tak długo, jak strona._zapamietajZaznaczone = e.ValueHandlerToggledzapisuje nowy stan.bool ok = (login == "admin" && haslo == "1234")Prosta „logika” logowania. W prawdziwej aplikacji byłaby baza lub zapytanie do serwera.$"Zalogowano{(… ? " (zapamiętano)" : "")}"Operator warunkowy wewnątrz interpolacji, w nawiasach.
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 zdarzeniami kontrolek
Odczyt `e.NewValue` jako `int` bez rzutowania
int poziom = e.NewValue;Błąd kompilatora: double nie konwertuje się niejawnie na int.
int poziom = (int)e.NewValue; // albo Math.Round(e.NewValue)Slider i Stepper przekazują double.
Stan w zmiennej lokalnej zamiast w polu klasy
private void Zapamietaj_Toggled(object sender, ToggledEventArgs e)
{
bool zapamietaj = e.Value; // zniknie po wyjściu z metody
}Przycisk „Zaloguj” nie widzi stanu przełącznika.
private bool _zapamietaj;
private void Zapamietaj_Toggled(object sender, ToggledEventArgs e)
{
_zapamietaj = e.Value;
}Dane współdzielone między handlerami trzymaj w polu klasy.
Nazwa właściwości wpisana na sztywno
if (e.PropertyName == "Time")Działa, ale literówka ("Tiem") nie zostanie wykryta przez kompilator.
if (e.PropertyName == nameof(TimePicker.Time))nameof jest sprawdzane w czasie kompilacji.
Odczyt `SelectedItem` bez sprawdzenia
EtykietaWynik.Text = PytaniePomocnicze.SelectedItem.ToString();Gdy nic nie wybrano, SelectedItem jest null i metoda zgłasza wyjątek.
if (PytaniePomocnicze.SelectedIndex >= 0)
EtykietaWynik.Text = $"Pytanie: {PytaniePomocnicze.SelectedItem}";Sprawdź wybór, zanim użyjesz wartości.
Ćwiczenie z pomocą · samodzielna praca
6. Mała zmiana, którą zrobisz samodzielnie
Dodaj etykietę opisującą poziom suwaka: niski lub wysoki.
Dodaj etykietę opisującą poziom suwaka: niski lub wysoki. Dotychczasowe działanie ma pozostać poprawne.
? Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź
W istniejącym ValueChanged odczytaj e.NewValue.
Krok 2 — pokaż podpowiedź
Porównaj wartość z wybranym progiem i ustaw Text nowego Label.
Krok 3 — pokaż podpowiedź
Sprawdź wartości poniżej progu, równą progowi i powyżej.
Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.
8 · Sprawdź się
Które zdarzenie i skąd wartość?
Cztery sytuacje.
Podsumowanie
Co warto zapamiętać
Switch:Toggledie.Value.Slider,Stepper:ValueChangedie.NewValue(double).DatePicker:DateSelectedie.NewDate.TimePicker:TimeSelected(nowsze) alboPropertyChangedznameof(TimePicker.Time).PickeriSearchBar: zwykłeEventArgs, wartość czytasz z kontrolki.doublenaintwymaga rzutowania.- Stan współdzielony przez kilka handlerów trzymaj w polu klasy.
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ę →