CheckBox — pole wyboru
Zgoda na regulamin, dodatki do zamówienia, opcje w ustawieniach — wszędzie tam, gdzie użytkownik odpowiada „tak” albo „nie” na każde pytanie osobno, stoi CheckBox. To pierwsza kontrolka wyboru w kursie. Nauczysz się odczytywać jej stan, reagować na zmianę na żywo i obsłużyć trzeci stan — „częściowo”, który w C# zapisuje się jako null.
Czego się dziś nauczysz
- Dodasz pole wyboru z opisem i ustawisz jego stan początkowy.
- Odczytasz stan pola i wyjaśnisz, dlaczego
IsCheckedma typbool?, a niebool. - Zareagujesz na zaznaczenie i odznaczenie jedną metodą podpiętą pod
CheckediUnchecked. - Rozróżnisz zdarzenie
Click(tylko użytkownik) odChecked(także zmiana z kodu). - Zbudujesz pole „Zaznacz wszystkie” z trzecim stanem „częściowo”.
- Przejdziesz pętlą po grupie pól i policzysz zaznaczone.
CheckBox w XAML
<StackPanel Margin="15">
<CheckBox x:Name="Newsletter" Content="Chcę otrzymywać newsletter" Margin="0,4"/>
<CheckBox x:Name="Powiadomienia" Content="Powiadomienia o ocenach"
IsChecked="True" Margin="0,4"/>
<CheckBox Content="Tryb ciemny (niedostępny)" IsEnabled="False" Margin="0,4"/>
</StackPanel>
kliknięcie w kwadrat albo w opis zmienia stan pola
Opis pola to Content — jak w przycisku. Kliknięcie w tekst opisu też przełącza pole, dlatego nie potrzebujesz osobnej etykiety. Pola w grupie są od siebie niezależne: zaznaczenie jednego nie zmienia pozostałych.
IsChecked i typ bool?
Stan pola zapisuje właściwość IsChecked. Ma ona typ bool? — „bool, który może być pusty”. Oprócz true i false może więc mieć wartość null, oznaczającą stan nieokreślony. Przez to nie da się jej wstawić wprost do if:
if (Newsletter.IsChecked) // ✗ CS0266: nie można zamienić bool? na bool
{
}
if (Newsletter.IsChecked == true) // ✓ prawda tylko dla zaznaczonego pola
{
Info.Text = "Zapisano do newslettera.";
}
bool zapisany = Newsletter.IsChecked == true; // zamiana na zwykły bool
Newsletter.IsChecked = false; // odznaczenie z kodu
Porównanie == true daje zwykły bool: true dla zaznaczonego pola, false dla odznaczonego i dla null. To najprostszy i najczęściej używany zapis.
Checked, Unchecked i Click
Gdy wynik ma się zmieniać od razu, bez przycisku „Oblicz”, reagujesz na zdarzenia pola:
| Zdarzenie | Kiedy | Uwaga |
|---|---|---|
Checked | pole stało się zaznaczone | także po IsChecked = true w kodzie |
Unchecked | pole stało się odznaczone | także po zmianie z kodu |
Click | użytkownik kliknął pole | nie wywołuje się przy zmianie z kodu |
<CheckBox x:Name="Pogrubienie" Content="Pogrubienie"
Checked="Styl_Zmiana" Unchecked="Styl_Zmiana"/>
// jedna metoda dla obu zdarzeń — po prostu sprawdza aktualny stan
private void Styl_Zmiana(object sender, RoutedEventArgs e)
{
Podglad.FontWeight = Pogrubienie.IsChecked == true ? FontWeights.Bold : FontWeights.Normal;
}
Najprostszy i najbezpieczniejszy wzór to jedna metoda pod oba zdarzenia, która nie pyta, co się zmieniło, tylko przelicza wszystko od nowa na podstawie aktualnych stanów pól.
IsChecked="True" w XAML i zdarzenie Checked
Jeśli pole ma w XAML IsChecked="True" i Checked="…", zdarzenie wywoła się już podczas InitializeComponent() — zanim powstaną kontrolki zapisane niżej w pliku. Metoda, która zmienia taką kontrolkę, zakończy się wtedy błędem NullReferenceException. Ustaw stan początkowy w konstruktorze, po InitializeComponent(), albo sprawdź na początku metody, czy kontrolka nie jest null.
Trzeci stan — „częściowo”
Pole z IsThreeState="True" ma trzy stany: zaznaczone (true), odznaczone (false) i nieokreślone (null), rysowane jako wypełniony kwadrat. Najczęściej spotkasz je w polu „Zaznacz wszystkie”: gdy zaznaczona jest tylko część pozycji, pole pokazuje stan pośredni.
kliknięcia przełączają pole trójstanowe w kolejności: odznaczone → zaznaczone → nieokreślone → odznaczone
Wiele pól naraz
Gdy pól jest kilka, nie trzeba nadawać każdemu nazwy. Wkładasz je do jednego panelu z nazwą i przechodzisz po nim pętlą — tak jak po przyciskach w lekcji 12. Dane potrzebne do obliczeń (cenę, punkty) trzymasz w Tag:
int zaznaczone = 0;
foreach (CheckBox pole in Opcje.Children)
{
if (pole.IsChecked == true)
{
zaznaczone++;
}
}
Info.Text = $"Wybrano {zaznaczone} z {Opcje.Children.Count}";
Zamówienie pizzy z dodatkami
Klient wybiera dodatki do pizzy. Cena przelicza się przy każdym kliknięciu, a pole „Zaznacz wszystkie dodatki” pokazuje, czy zaznaczono wszystkie, żadnego, czy tylko część. Przycisk Zamów jest aktywny dopiero po zaakceptowaniu regulaminu.
dwa dodatki — „Zaznacz wszystkie” w stanie częściowym
<Window x:Class="Lekcja14.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Pizzeria „Pod Dębem”" Height="420" Width="440"
ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Margherita 32 cm — 24,00 zł" FontSize="18"
FontWeight="Bold" Margin="0,0,0,10"/>
<!-- Dodatki w ramce -->
<Border Grid.Row="1" BorderBrush="LightGray" BorderThickness="1"
CornerRadius="6" Padding="10">
<StackPanel>
<CheckBox x:Name="Wszystkie" Content="Zaznacz wszystkie dodatki"
IsThreeState="True" FontWeight="Bold" Margin="0,0,0,8"
Click="Wszystkie_Click"/>
<UniformGrid x:Name="Dodatki" Columns="2">
<CheckBox Content="Podwójny ser (+4 zł)" Tag="4" Margin="0,4"
Checked="Dodatek_Zmiana" Unchecked="Dodatek_Zmiana"/>
<CheckBox Content="Pieczarki (+3 zł)" Tag="3" Margin="0,4"
Checked="Dodatek_Zmiana" Unchecked="Dodatek_Zmiana"/>
<CheckBox Content="Szynka (+5 zł)" Tag="5" Margin="0,4"
Checked="Dodatek_Zmiana" Unchecked="Dodatek_Zmiana"/>
<CheckBox Content="Oliwki (+4 zł)" Tag="4" Margin="0,4"
Checked="Dodatek_Zmiana" Unchecked="Dodatek_Zmiana"/>
<CheckBox Content="Jalapeño (+3 zł)" Tag="3" Margin="0,4"
Checked="Dodatek_Zmiana" Unchecked="Dodatek_Zmiana"/>
<CheckBox Content="Cebula (+2 zł)" Tag="2" Margin="0,4"
Checked="Dodatek_Zmiana" Unchecked="Dodatek_Zmiana"/>
</UniformGrid>
</StackPanel>
</Border>
<CheckBox x:Name="Regulamin" Grid.Row="2" Margin="0,12,0,0"
Content="Akceptuję regulamin dostawy"
Checked="Regulamin_Zmiana" Unchecked="Regulamin_Zmiana"/>
<Grid Grid.Row="3" Margin="0,12,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="Suma" Text="Do zapłaty: 24,00 zł" FontSize="16"
VerticalAlignment="Center"/>
<Button x:Name="Zamow" Grid.Column="1" Content="Zamów" Width="100" Height="30"
IsEnabled="False" Click="Zamow_Click"/>
</Grid>
</Grid>
</Window>
using System.Windows;
using System.Windows.Controls;
namespace Lekcja14;
public partial class MainWindow : Window
{
private const decimal CenaPizzy = 24m;
public MainWindow()
{
InitializeComponent();
}
// Wywoływana przy zaznaczeniu i przy odznaczeniu każdego dodatku
private void Dodatek_Zmiana(object sender, RoutedEventArgs e)
{
Przelicz();
}
// „Zaznacz wszystkie” — tylko kliknięcie użytkownika, nie zmiana z kodu
private void Wszystkie_Click(object sender, RoutedEventArgs e)
{
bool zaznacz = Wszystkie.IsChecked == true;
foreach (CheckBox dodatek in Dodatki.Children)
{
dodatek.IsChecked = zaznacz;
}
}
private void Regulamin_Zmiana(object sender, RoutedEventArgs e)
{
Zamow.IsEnabled = Regulamin.IsChecked == true;
}
private void Zamow_Click(object sender, RoutedEventArgs e)
{
string lista = "";
foreach (CheckBox dodatek in Dodatki.Children)
{
if (dodatek.IsChecked == true)
{
lista += $"\n• {dodatek.Content}";
}
}
if (lista == "")
{
lista = "\n(bez dodatków)";
}
MessageBox.Show($"Margherita{lista}\n\n{Suma.Text}", "Zamówienie przyjęte",
MessageBoxButton.OK, MessageBoxImage.Information);
foreach (CheckBox dodatek in Dodatki.Children)
{
dodatek.IsChecked = false;
}
Regulamin.IsChecked = false;
}
private void Przelicz()
{
decimal suma = CenaPizzy;
int zaznaczone = 0;
foreach (CheckBox dodatek in Dodatki.Children)
{
if (dodatek.IsChecked == true)
{
suma += decimal.Parse($"{dodatek.Tag}"); // cenę wpisaliśmy sami w XAML
zaznaczone++;
}
}
// stan „Zaznacz wszystkie”: tak / nie / częściowo (null)
if (zaznaczone == 0)
{
Wszystkie.IsChecked = false;
}
else if (zaznaczone == Dodatki.Children.Count)
{
Wszystkie.IsChecked = true;
}
else
{
Wszystkie.IsChecked = null;
}
Suma.Text = $"Do zapłaty: {suma:F2} zł";
}
}
Co dzieje się po kolei
- Sześć dodatków stoi w
UniformGridz dwiema kolumnami. Panel ma nazwęDodatki, więc kod przechodzi po nich pętlą — same pola nazw nie potrzebują. - Cena dodatku jest w
Tag.decimal.Parsejest tu bezpieczne, bo te wartości wpisaliśmy sami, a nie użytkownik. - Każdy dodatek ma
CheckediUncheckedpodpięte pod tę samą metodę. Ona tylko wywołujePrzelicz, które liczy wszystko od zera. Przeliczustawia też stan pola „Zaznacz wszystkie”:false,truealbonull— w zależności od liczby zaznaczonych dodatków.- „Zaznacz wszystkie” reaguje na
Click, a nie naChecked. GdyPrzeliczzmienia jego stan z kodu,Clicksię nie wywołuje, więc pole nie zaczyna samo przestawiać dodatków. - Po kliknięciu „Zaznacz wszystkie” w stanie częściowym pole przechodzi na „odznaczone” i odznacza wszystkie dodatki.
Zamow.IsEnabled = Regulamin.IsChecked == true;— jedna linia włącza i wyłącza przycisk.- Po zamówieniu kod odznacza wszystkie pola. Każde odznaczenie wywołuje
Unchecked, więc cena i przycisk wracają do stanu początkowego bez dodatkowego kodu.
Sprawdź trzy rzeczy
- Zmień w „Zaznacz wszystkie” zdarzenie
ClicknaChecked(i nazwę metody). Zaznacz dwa dodatki, a potem kliknij trzeci — zaczną się dziać dziwne rzeczy, bo zmiana z kodu uruchamia metodę zaznaczającą wszystko. - Usuń
Unchecked="Dodatek_Zmiana"przy szynce. Po odznaczeniu szynki cena się nie zmniejszy. - Usuń
IsThreeState="True". Przypisanienullz kodu nadal pokaże stan pośredni, ale użytkownik nie będzie mógł go wybrać kliknięciem.
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
Content | object | Opis pola; kliknięcie w opis też przełącza pole. |
IsChecked | bool? | Stan pola: true, false lub null. W if porównuj z true. |
IsThreeState | bool | Czy użytkownik może kliknięciem wybrać stan null. |
Checked, Unchecked | zdarzenia | Po zaznaczeniu / odznaczeniu — także z kodu. |
Indeterminate | zdarzenie | Po przejściu w stan null. |
Click | zdarzenie | Tylko po kliknięciu przez użytkownika. |
FontWeights.Bold, FontWeights.Normal | wartości | Grubość czcionki ustawiana z kodu. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
if (Zgoda.IsChecked) | CS0266 — bool? to nie bool. Napisz == true. |
bool b = Zgoda.IsChecked; | CS0266 — to samo. bool b = Zgoda.IsChecked == true; |
if (Zgoda.IsChecked != false) | Prawda także dla null — przy polu trójstanowym to zwykle nie to, o co chodzi. |
tylko Checked, bez Unchecked | Po odznaczeniu wynik się nie aktualizuje. |
IsChecked="True" + Checked zmieniające kontrolkę niżej w XAML | NullReferenceException przy starcie okna. |
„Zaznacz wszystkie” na Checked | Zmiana stanu z kodu znów zaznacza wszystkie pola — pętla zależności. |
<CheckBox Text="…"/> | Błąd XAML — opis to Content. |
Zadania
Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Tam, gdzie to możliwe, wynik zmieniaj na żywo, bez przycisku „Oblicz”. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
Uczeń zapisuje się na koła pozalekcyjne. W oknie osiem pól (np. robotyka, e-sport, chór, siatkówka, fotografia, szachy, teatr, programowanie gier) w dwóch kolumnach. Pod nimi napis „Wybrano 2 z 8” zmieniany na żywo. Można wybrać najwyżej 3 koła — gdy uczeń zaznaczy czwarte, program od razu je odznacza i wyświetla czerwony komunikat. Przycisk Zapisz pokazuje w MessageBox listę wybranych kół; przy braku wyboru pokazuje ostrzeżenie.
pokaż wskazówkę
- Kliknięte pole to
(CheckBox)sender— to je odznaczysz po przekroczeniu limitu. - Odznaczenie z kodu wywoła
Unchecked, więc licznik sam się poprawi.
Pole tekstowe na napis i duży podgląd pod spodem. Pola wyboru: Pogrubienie, Kursywa, Duża czcionka (28 zamiast 16), Ciemne tło (czarne tło, biały tekst), WIELKIE LITERY. Każda zmiana pola albo tekstu od razu odświeża podgląd. Przycisk Resetuj odznacza wszystkie pola.
pokaż wskazówkę
- Jedna metoda
OdswiezPodglad()ustawia wszystko na podstawie stanów pól; wywołuj ją zChecked,UncheckediTextChanged. - Kursywa z kodu:
FontStyles.ItaliciFontStyles.Normal.
Rower kosztuje 12 zł za godzinę. Pole Liczba godzin (1–12) i opcje: kask (+5 zł jednorazowo), koszyk (+3 zł), fotelik dla dziecka (+4 zł za godzinę), ubezpieczenie (+10% od całej kwoty). Koszt przelicza się przy każdej zmianie pola godzin i każdej opcji. Przy niepoprawnej liczbie godzin zamiast kwoty widać „Podaj liczbę godzin od 1 do 12”. Wynik pokazuje rozbicie: „Rower: 36,00 zł, dodatki: 20,00 zł, ubezpieczenie: 5,60 zł, razem: 61,60 zł”.
pokaż wskazówkę
- Ubezpieczenie liczysz na końcu, od sumy roweru i dodatków.
TextChangedmoże przyjść przed utworzeniem napisu z wynikiem — sprawdźnullalbo nie wpisuj tekstu startowego w XAML.
Lista ośmiu czynności przed egzaminem praktycznym (np. „Sprawdzono numer stanowiska”, „Utworzono folder z numerem PESEL”, „Uruchomiono Visual Studio”…). Nad listą napis „Gotowe: 5 / 8”. Gdy wszystkie są zaznaczone, pod listą pojawia się zielona ramka „Możesz zaczynać. Powodzenia!”. Pole Ukryj wykonane chowa zaznaczone pozycje (i pokazuje je po odznaczeniu tej opcji).
pokaż wskazówkę
- Pozycje listy i pole Ukryj wykonane muszą być w różnych panelach — inaczej pętla policzy też to pole.
- Widoczność każdej pozycji:
Collapsed, gdy jest zaznaczona i opcja ukrywania jest włączona; w przeciwnym razieVisible.
Formularz rejestracji w sklepie internetowym: pole e-mail i cztery zgody — regulamin (wymagana), polityka prywatności (wymagana), newsletter i oferty partnerów (opcjonalne). Wymagane mają czerwoną gwiazdkę. Pole „Zaznacz wszystkie zgody” działa jak w przykładzie (z trzecim stanem). Przycisk Załóż konto jest aktywny tylko przy obu wymaganych zgodach i e-mailu zawierającym @. Po kliknięciu MessageBox podsumowuje, na co klient się zgodził.
pokaż wskazówkę
- Gwiazdkę zrobisz w
Contentpola zTextBlockiRun(lekcja 02) —Contentmoże być kontrolką, nie tylko tekstem. - Stan przycisku licz w jednej metodzie wywoływanej przez zgody i
TextChangedpola e-mail.
Zestaw bazowy kosztuje 2500 zł. Opcje: karta graficzna RTX (+1800 zł), zasilacz 750 W (+350 zł), dysk 2 TB (+420 zł), chłodzenie wodne (+500 zł), chłodzenie powietrzne (+150 zł), Windows 11 Pro (+600 zł). Zasady:
- karta RTX wymaga zasilacza 750 W — po jej zaznaczeniu zasilacz zaznacza się sam i nie da się go odznaczyć, dopóki karta jest wybrana;
- chłodzenie wodne i powietrzne wykluczają się — zaznaczenie jednego odznacza drugie;
- pod opcjami lista uwag, np. „Zasilacz dodany automatycznie (wymaga go karta RTX)”.
Cena aktualizuje się na żywo. Przycisk Kopiuj specyfikację pokazuje pełną listę komponentów z cenami w MessageBox.
pokaż wskazówkę
- Zasilacz „nie do odznaczenia” =
IsEnabled = falseprzy zaznaczonej karcie. - Wykluczanie zrób w osobnej metodzie podpiętej pod
Checkedobu rodzajów chłodzenia: jeślisenderto chłodzenie wodne, odznacz powietrzne, i odwrotnie. - Kolejność ma znaczenie: najpierw zastosuj zasady, na końcu policz cenę.
Co trzeba zapamiętać
CheckBoxto niezależne „tak / nie” — każde pole osobno. Opis wContent.IsCheckedma typbool?; w warunku piszIsChecked == true.CheckediUncheckedpodpinaj pod jedną metodę, która przelicza wszystko od nowa.Clickreaguje tylko na użytkownika;Checked— także na zmiany z kodu.IsThreeStatei wartośćnullto stan „częściowo” — typowo w „Zaznacz wszystkie”.- Grupa pól w nazwanym panelu +
foreach+ dane wTag= kod bez powtórzeń.
Dokumentacja: Microsoft Learn — klasa CheckBox.