C# · WPF · INF.04 · Kontrolki

CheckBox – pole wyboru

Poznasz CheckBox – kontrolkę z polem do zaznaczenia. Nauczysz się odczytywać jej stan w C#, korzystać z trzeciego, nieokreślonego stanu oraz grupować wiele checkboxów w jedną logiczną całość.

CheckBox IsChecked IsThreeState grupowanie
1

Czym jest CheckBox?

CheckBox to mały kwadratowy przełącznik, który użytkownik może zaznaczyć (✓) lub odznaczyć. Służy do włączania/wyłączania opcji – „Zapamiętaj mnie”, „Akceptuję regulamin”, „Pokaż hasło”.

Analogia

CheckBox to jak lista zakupów na papierze, gdzie zaznaczasz ✓ przy kupionym produkcie. Każdy checkbox działa niezależnie od innych – możesz zaznaczyć jeden, wszystkie albo żaden. To różni go od RadioButton, gdzie tylko jedna opcja z grupy może być wybrana.

Najprostszy CheckBox
<CheckBox x:Name="chkZapamietaj"
          Content="Zapamiętaj mnie" />
2

IsChecked – stan zaznaczenia

Ustawianie domyślnego stanu w XAML

IsChecked w XAML
<!-- Domyślnie zaznaczony -->
<CheckBox x:Name="chkNewsletter"
          Content="Wysyłaj mi newsletter"
          IsChecked="True" />

<!-- Domyślnie odznaczony (to też wartość domyślna, można pominąć) -->
<CheckBox x:Name="chkRegulamin"
          Content="Akceptuję regulamin"
          IsChecked="False" />

Odczyt i zapis w C#

Odczyt stanu CheckBox
private void btnZapisz_Click(object sender, RoutedEventArgs e)
{
    // IsChecked to bool? (nullable bool) – może być true, false lub null
    if (chkRegulamin.IsChecked == true)
    {
        lblWynik.Text = "Regulamin zaakceptowany";
    }
    else
    {
        lblWynik.Text = "Musisz zaakceptować regulamin!";
    }
}
Zapis stanu z C#
// Zaznaczenie / odznaczenie z kodu
chkNewsletter.IsChecked = true;
chkNewsletter.IsChecked = false;

// Przełączenie na odwrotny stan
chkNewsletter.IsChecked = !chkNewsletter.IsChecked;
IsChecked to bool?, nie bool!

IsChecked jest typu bool? (nullable bool) – może mieć wartość true, false albo null. Dlatego porównanie if (chkRegulamin.IsChecked == true) jest bezpieczniejsze niż if (chkRegulamin.IsChecked) – to drugie nie skompiluje się bez dodatkowej konwersji, bo bool? nie jest tym samym co bool.

3

Trzy stany – IsThreeState

Domyślnie CheckBox ma dwa stany: zaznaczony i odznaczony. Ustawiając IsThreeState="True" dodajesz trzeci stan – nieokreślony (wyświetlany jako wypełniony kwadrat bez ptaszka, czasem zwany „indeterminate”).

StanIsCheckedWygląd
Zaznaczonytrue☑ ptaszek
Odznaczonyfalse☐ pusty kwadrat
Nieokreślonynull▪ wypełniony kwadrat
CheckBox z trzema stanami
<CheckBox x:Name="chkWybierzWszystko"
          Content="Zaznacz wszystkie"
          IsThreeState="True" />
<!-- Kliknięcia cyklicznie przechodzą:
     odznaczony → zaznaczony → nieokreślony → odznaczony → ... -->

Dlaczego bool? (nullable bool)?

Trzeci stan to właśnie powód, dla którego IsChecked jest typu bool?, a nie zwykłego bool. Zwykły bool może być tylko true lub false – nie ma miejsca na trzecią opcję. bool? dodaje możliwość null, reprezentującego stan „nieokreślony”.

Stan nieokreślony w praktyce

Najczęstsze zastosowanie trzeciego stanu to checkbox „Zaznacz wszystko” nad listą innych checkboxów. Gdy część jest zaznaczona, a część nie – główny checkbox pokazuje stan nieokreślony.

Sprawdzanie wszystkich trzech stanów
private void SprawdzStan()
{
    if (chkWybierzWszystko.IsChecked == true)
    {
        lblInfo.Text = "Wszystkie zaznaczone";
    }
    else if (chkWybierzWszystko.IsChecked == false)
    {
        lblInfo.Text = "Żadne nie zaznaczone";
    }
    else  // IsChecked == null
    {
        lblInfo.Text = "Część zaznaczona, część nie";
    }
}
4

Zdarzenia Checked / Unchecked / Indeterminate

CheckBox ma trzy osobne zdarzenia, odpowiadające trzem stanom. Możesz podpiąć każde z osobna, albo użyć jednego wspólnego zdarzenia Click, jeśli nie potrzebujesz rozróżniać stanów.

XAML – podpięcie wszystkich trzech zdarzeń
<CheckBox x:Name="chkOpcja"
          Content="Włącz funkcję"
          IsThreeState="True"
          Checked="chkOpcja_Checked"
          Unchecked="chkOpcja_Unchecked"
          Indeterminate="chkOpcja_Indeterminate" />
C# – obsługa każdego stanu
private void chkOpcja_Checked(object sender, RoutedEventArgs e)
{
    lblInfo.Text = "Funkcja włączona";
    lblInfo.Foreground = Brushes.LimeGreen;
}

private void chkOpcja_Unchecked(object sender, RoutedEventArgs e)
{
    lblInfo.Text = "Funkcja wyłączona";
    lblInfo.Foreground = Brushes.Gray;
}

private void chkOpcja_Indeterminate(object sender, RoutedEventArgs e)
{
    lblInfo.Text = "Stan nieokreślony";
    lblInfo.Foreground = Brushes.Gold;
}
Click – prostsza alternatywa

Jeśli CheckBox ma tylko dwa stany (bez IsThreeState), wygodniej jest użyć jednego zdarzenia Click i sprawdzić IsChecked w środku – tak jak robiliśmy w sekcji 2. Trzy osobne zdarzenia mają sens głównie przy trzystanowych checkboxach.

5

Odczyt stanu wielu CheckBoxów

Typowy formularz ma kilka checkboxów – np. wybór zainteresowań, opcji dodatkowych. Oto jak odczytać je wszystkie razem.

Formularz z kilkoma niezależnymi opcjami
<StackPanel>
    <TextBlock Text="Wybierz zainteresowania:" Margin="0,0,0,8" />
    <CheckBox x:Name="chkProgramowanie" Content="Programowanie" Margin="0,4" />
    <CheckBox x:Name="chkElektronika"  Content="Elektronika"   Margin="0,4" />
    <CheckBox x:Name="chkGry"           Content="Gry komputerowe" Margin="0,4" />
    <CheckBox x:Name="chkSport"         Content="Sport"            Margin="0,4" />
    <Button    Content="Pokaż wybrane" Margin="0,12,0,0" Click="btnPokaz_Click" />
</StackPanel>
C# – zbieranie wybranych opcji do listy
private void btnPokaz_Click(object sender, RoutedEventArgs e)
{
    List<string> wybrane = new List<string>();

    if (chkProgramowanie.IsChecked == true) wybrane.Add("Programowanie");
    if (chkElektronika.IsChecked == true)  wybrane.Add("Elektronika");
    if (chkGry.IsChecked == true)          wybrane.Add("Gry komputerowe");
    if (chkSport.IsChecked == true)        wybrane.Add("Sport");

    if (wybrane.Count == 0)
    {
        MessageBox.Show("Nie wybrano żadnego zainteresowania");
        return;
    }

    string tekst = string.Join(", ", wybrane);
    MessageBox.Show($"Wybrano: {tekst}");
}
string.Join – łączenie listy w tekst

string.Join(", ", wybrane) łączy elementy listy w jeden tekst, oddzielając je podanym separatorem. Dla listy ["Sport", "Gry"] wynikiem będzie "Sport, Gry". To wygodniejsze niż ręczne sklejanie tekstu w pętli.

6

Grupowanie CheckBoxów

W przeciwieństwie do RadioButton, CheckBoxy nie mają wbudowanego mechanizmu grupowania (jak GroupName) – każdy działa niezależnie. Ale możemy je „grupować” na dwa sposoby: wizualnie i logicznie.

Grupowanie wizualne – GroupBox

Najprostszy sposób na pogrupowanie powiązanych checkboxów to umieszczenie ich w GroupBox z nagłówkiem.

CheckBoxy w GroupBox
<GroupBox Header="Powiadomienia" Padding="10">
    <StackPanel>
        <CheckBox x:Name="chkEmail"  Content="Powiadomienia e-mail" Margin="0,4" />
        <CheckBox x:Name="chkSms"    Content="Powiadomienia SMS"    Margin="0,4" />
        <CheckBox x:Name="chkPush"   Content="Powiadomienia push"   Margin="0,4" />
    </StackPanel>
</GroupBox>

Grupowanie logiczne – „Zaznacz wszystko”

Częsty wzorzec: jeden „nadrzędny” checkbox kontroluje wszystkie inne. To tutaj trzeci stan (IsThreeState) okazuje się przydatny.

XAML – „Zaznacz wszystko” + lista checkboxów
<StackPanel>
    <CheckBox x:Name="chkWszystko"
              Content="Zaznacz wszystko"
              FontWeight="Bold"
              IsThreeState="True"
              Click="chkWszystko_Click"
              Margin="0,0,0,8" />

    <CheckBox x:Name="chkPozycja1" Content="Pozycja 1"
              Margin="20,4,0,0" Click="chkPozycja_Click" />
    <CheckBox x:Name="chkPozycja2" Content="Pozycja 2"
              Margin="20,4,0,0" Click="chkPozycja_Click" />
    <CheckBox x:Name="chkPozycja3" Content="Pozycja 3"
              Margin="20,4,0,0" Click="chkPozycja_Click" />
</StackPanel>
C# – logika „Zaznacz wszystko”
private bool _aktualizujeProgramowo = false;

private void chkWszystko_Click(object sender, RoutedEventArgs e)
{
    // Gdy użytkownik kliknie główny checkbox, ustawiamy wszystkie pozycje
    bool nowyStan = chkWszystko.IsChecked == true;

    _aktualizujeProgramowo = true;  // flaga zapobiegająca pętli zdarzeń
    chkPozycja1.IsChecked = nowyStan;
    chkPozycja2.IsChecked = nowyStan;
    chkPozycja3.IsChecked = nowyStan;
    _aktualizujeProgramowo = false;
}

private void chkPozycja_Click(object sender, RoutedEventArgs e)
{
    if (_aktualizujeProgramowo) return;  // ignoruj zmiany wywołane programowo

    // Sprawdzamy ile pozycji jest zaznaczonych
    int zaznaczone = 0;
    if (chkPozycja1.IsChecked == true) zaznaczone++;
    if (chkPozycja2.IsChecked == true) zaznaczone++;
    if (chkPozycja3.IsChecked == true) zaznaczone++;

    _aktualizujeProgramowo = true;
    if (zaznaczone == 0)
        chkWszystko.IsChecked = false;       // żadna nie zaznaczona
    else if (zaznaczone == 3)
        chkWszystko.IsChecked = true;        // wszystkie zaznaczone
    else
        chkWszystko.IsChecked = null;        // część zaznaczona – stan nieokreślony
    _aktualizujeProgramowo = false;
}
Dlaczego flaga _aktualizujeProgramowo?

Gdy w kodzie ustawiamy chkPozycja1.IsChecked = ..., to też odpala zdarzenie Click tej kontrolki! Bez flagi _aktualizujeProgramowo wpadlibyśmy w nieskończoną pętlę wzajemnych aktualizacji. Flaga mówi: „te zmiany są wynikiem mojego kodu, zignoruj zdarzenie”.

7

Wygląd CheckBox

Podstawowe właściwości wizualne
<CheckBox Content="Zgadzam się z warunkami"
          FontSize="14"
          Foreground="White"
          Margin="0,8"
          Cursor="Hand" />

<!-- Content może być też złożonym elementem, jak w Button -->
<CheckBox>
    <CheckBox.Content>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Akceptuję " />
            <TextBlock Text="regulamin" TextDecorations="Underline" Foreground="SteelBlue" />
        </StackPanel>
    </CheckBox.Content>
</CheckBox>
8

Praktyczny przykład – konfigurator pizzy

MainWindow.xaml
<StackPanel Margin="20">

    <TextBlock Text="Wybierz dodatki:" FontWeight="Bold" Margin="0,0,0,10" />

    <CheckBox x:Name="chkSer"      Content="Dodatkowy ser (+3 zł)"     Tag="3"  Margin="0,4" Checked="Dodatek_Changed" Unchecked="Dodatek_Changed" />
    <CheckBox x:Name="chkPiecarka" Content="Pieczarki (+2 zł)"          Tag="2"  Margin="0,4" Checked="Dodatek_Changed" Unchecked="Dodatek_Changed" />
    <CheckBox x:Name="chkOliwki"   Content="Oliwki (+2 zł)"             Tag="2"  Margin="0,4" Checked="Dodatek_Changed" Unchecked="Dodatek_Changed" />
    <CheckBox x:Name="chkSzynka"   Content="Szynka (+4 zł)"             Tag="4"  Margin="0,4" Checked="Dodatek_Changed" Unchecked="Dodatek_Changed" />

    <TextBlock x:Name="lblCena"
               Text="Cena bazowa: 25 zł"
               FontSize="16"
               FontWeight="Bold"
               Margin="0,16,0,0" />

</StackPanel>
MainWindow.xaml.cs
private const int CENA_BAZOWA = 25;

private void Dodatek_Changed(object sender, RoutedEventArgs e)
{
    int sumaDodatkow = 0;

    // Przechodzimy po wszystkich CheckBoxach w panelu
    foreach (var dziecko in panelGlowny.Children)
    {
        if (dziecko is CheckBox chk && chk.IsChecked == true && chk.Tag != null)
        {
            sumaDodatkow += int.Parse(chk.Tag.ToString());
        }
    }

    int cenaCalkowita = CENA_BAZOWA + sumaDodatkow;
    lblCena.Text = $"Cena: {cenaCalkowita} zł (baza: {CENA_BAZOWA} zł + dodatki: {sumaDodatkow} zł)";
}
dziecko is CheckBox chk – pattern matching

if (dziecko is CheckBox chk) to nowoczesny C# – sprawdza czy dziecko jest typu CheckBox i jeśli tak, jednocześnie przypisuje je do zmiennej chk tego typu. To wygodniejsze niż osobne sprawdzenie i rzutowanie.

9

Częste błędy

❌ Błąd 1: Porównanie IsChecked bez == true

❌ Błąd kompilacji

if (chkRegulamin.IsChecked)
{
    // Błąd! IsChecked to bool?,
    // nie da się go użyć
    // bezpośrednio w if
}

✅ Porównanie z true

if (chkRegulamin.IsChecked == true)
{
    // Działa poprawnie
}

❌ Błąd 2: Pętla nieskończona przy „Zaznacz wszystko”

❌ Brak flagi zabezpieczającej

private void chkWszystko_Click(...)
{
    chkPoz1.IsChecked = true; // odpala
    chkPoz2.IsChecked = true; // Click tych
    chkPoz3.IsChecked = true; // checkboxów!
}
// Bez flagi może dojść do
// nieoczekiwanych, trudnych
// do debugowania zapętleń

✅ Z flagą zabezpieczającą

private void chkWszystko_Click(...)
{
    _aktualizujeProgramowo = true;
    chkPoz1.IsChecked = true;
    chkPoz2.IsChecked = true;
    _aktualizujeProgramowo = false;
}

❌ Błąd 3: Mylenie CheckBox z RadioButton

❌ Zły wybór kontrolki

<!-- Płeć: tylko jedna opcja możliwa –
     ale użyto CheckBox! -->
<CheckBox Content="Kobieta" />
<CheckBox Content="Mężczyzna" />
<!-- Można zaznaczyć OBIE! -->

✅ RadioButton dla wyboru jednej opcji

<RadioButton Content="Kobieta"
             GroupName="Plec" />
<RadioButton Content="Mężczyzna"
             GroupName="Plec" />
<!-- Tylko jedna może być
     zaznaczona -->

❌ Błąd 4: Zapomniany Tag przy liczeniu sumy

❌ NullReferenceException

int wartosc = int.Parse(chk.Tag.ToString());
// Jeśli zapomniałeś dodać
// Tag="3" w XAML –
// chk.Tag jest null, wyjątek!

✅ Sprawdzenie czy Tag istnieje

if (chk.Tag != null)
{
    int wartosc = int.Parse(chk.Tag.ToString());
}
10

Zadania do wykonania

Zadanie 1 łatwe

Stwórz okno z 4 CheckBoxami reprezentującymi dni weekendu pracy (sobota, niedziela – pracuję rano, pracuję wieczorem, jestem dostępny telefonicznie). Przycisk „Pokaż grafik” wyświetla MessageBox z listą zaznaczonych opcji oddzielonych przecinkiem (użyj string.Join).

Zadanie 2 łatwe

Stwórz CheckBox „Pokaż hasło” obok pola PasswordBox. Po zaznaczeniu, obok PasswordBox pojawia się (Visibility.Visible) zwykły TextBox z tą samą wartością, a PasswordBox się chowa – i odwrotnie po odznaczeniu. (Podpowiedź: użyj zdarzeń Checked/Unchecked).

Zadanie 3 średnie

Zbuduj listę 5 zadań (TODO) z CheckBoxami i nadrzędnym checkboxem „Zaznacz wszystkie” z IsThreeState="True". Zaimplementuj pełną logikę: zaznaczenie nadrzędnego zaznacza wszystkie, odznaczenie wszystkich daje stan nieokreślony na nadrzędnym, zaznaczenie wszystkich pozycji ręcznie też ustawia nadrzędny na zaznaczony.

Zadanie 4 średnie

Rozbuduj przykład konfiguratora pizzy z sekcji 8: dodaj ComboBox z wyborem rozmiaru pizzy (Mała – cena bazowa 20 zł, Średnia – 25 zł, Duża – 32 zł) oraz przycisk „Zamów”, który wyświetla MessageBox z pełnym podsumowaniem zamówienia: rozmiar, lista wybranych dodatków i cena całkowita.