Pizza, formularze i trudne wybory w WPF: CheckBox, RadioButton i GroupBox

Przechodzimy do kolejnego poziomu wtajemniczenia w budowie aplikacji desktopowych: jak sprawić, by użytkownik wprowadzał dokładnie takie dane, jakich oczekujemy, bez ryzyka literówek i chaosu?

Wiesz już, jak stworzyć pole tekstowe TextBox, ale czy dałbyś użytkownikowi wpisać w nim z klawiatury rozmiar zamawianej pizzy? Ktoś wpisze „średnia”, ktoś „32cm”, a ktoś inny „duuuża placka”. Walidacja takiego tekstu to koszmar każdego programisty.

Właśnie dlatego profesjonalne GUI korzysta z kontrolek wyboru. Dzisiaj nauczysz się, kiedy używać kwadratów (CheckBox), kiedy kółek (RadioButton) i jak elegancko zamknąć je w estetycznych ramkach (GroupBox).

1. Metafora pizzerii: Kiedy kółko, a kiedy kwadrat?

Wybór odpowiedniej kontrolki w WPF to nie kwestia gustu - to kwestia logiki biznesowej Twojej aplikacji.

Opcja A: RadioButton (Przycisk opcji) - Rozmiar pizzy

Pizzę możesz zamówić małą, średnią ALBO dużą. Nie możesz zamówić jednej pizzy, która jest jednocześnie mała i duża.

Opcja B: CheckBox (Pole wyboru) - Dodatki do pizzy

Do pizzy możesz dobrać pieczarki, podwójny ser ORAZ sos czosnkowy. Możesz też nie wybrać nic albo wybrać wszystkie możliwe pozycje.

2. GroupBox: Zadbaj o porządek w oknie

Rzucenie kilku luźnych przycisków na okno wygląda nieprofesjonalnie. Kontrolka GroupBox pozwala narysować elegancką, cienką ramkę z podpisem, która wizualnie grupuje powiązane ze sobą opcje.

Posiada ona dwie kluczowe właściwości:

⚠️ Pamiętaj o regule pojedynczej zawartości (Single Child Rule)!

GroupBox jest kontenerem jednoelementowym. Jeśli spróbujesz wstawić do niego bezpośrednio trzy przyciski RadioButton, kompilator zgłosi błąd. Zawsze wkładaj do środka kontener układu - najczęściej StackPanel/DockPanel!

3. Atrybut GroupName: Tajna broń RadioButtona

Domyślnie WPF traktuje wszystkie kontrolki RadioButton znajdujące się w tym samym panelu (np. w jednym StackPanelu) jako jedną wspólną grupę.

Co jednak zrobić, gdy w formularzu masz wybór rozmiaru pizzy oraz wybór metody płatności? Jeśli nie zachowasz ostrożności, zaznaczenie płatności kartą mogłoby odznaczyć rozmiar pizzy!

Do pełnej izolacji grup służy właściwość GroupName:

<!-- Grupa 1: Rozmiar ciasta -->
<RadioButton Content="Mała (25 cm)" GroupName="RozmiarPizzy" IsChecked="True" />
<RadioButton Content="Duża (40 cm)" GroupName="RozmiarPizzy" />

<!-- Grupa 2: Płatność (działa całkowicie niezależnie!) -->
<RadioButton Content="Gotówka" GroupName="Platnosc" IsChecked="True" />
<RadioButton Content="Karta" GroupName="Platnosc" />

Dzięki podaniu GroupName masz 100% pewności, że opcje nigdy nie wejdą sobie w drogę.

4. Zobaczmy to w akcji: XAML kalkulatora zamówienia

Oto kompletny, czytelny fragment XAML, który łączy poznane kontrolki w profesjonalnie wyglądający formularz:

<StackPanel Margin="15">
    
    <!-- SEKCJA 1: Wybór wykluczający się (RadioButton) -->
    <GroupBox Header="1. Wybierz rozmiar ciasta" Margin="0,0,0,15" Padding="10">
        <StackPanel>
            <!-- Atrybut IsChecked="True" ustawia opcję domyślną! -->
            <RadioButton x:Name="rbMala" 
                         Content="Mała (20 zł)" 
                         GroupName="Rozmiar" 
                         IsChecked="True" 
                         Margin="0,0,0,5" />
            
            <RadioButton x:Name="rbSrednia" 
                         Content="Średnia (30 zł)" 
                         GroupName="Rozmiar" 
                         Margin="0,0,0,5" />
            
            <RadioButton x:Name="rbDuza" 
                         Content="Duża (40 zł)" 
                         GroupName="Rozmiar" />
        </StackPanel>
    </GroupBox>

    <!-- SEKCJA 2: Wybór wielokrotny (CheckBox) -->
    <GroupBox Header="2. Dobierz dodatki (opcjonalnie)" Margin="0,0,0,15" Padding="10">
        <StackPanel>
            <CheckBox x:Name="cbSer" Content="Podwójny ser (+4 zł)" Margin="0,0,0,5" />
            <CheckBox x:Name="cbPieczarki" Content="Pieczarki (+3 zł)" Margin="0,0,0,5" />
            <CheckBox x:Name="cbSos" Content="Sos czosnkowy (+2 zł)" />
        </StackPanel>
    </GroupBox>

    <Button x:Name="btnPodsumuj" Content="Oblicz cenę zamówienia" Height="35" Click="btnPodsumuj_Click" />
    
</StackPanel>

5. Jak odczytać zaznaczenie w C#?

W kodzie C# sprawdzamy stan właściwości IsChecked.

🧠 Ważny detal techniczny:
W WPF właściwość IsChecked zwraca typ bool? (Nullable<bool>), a nie zwykły bool. Dlatego sprawdzając warunek w instrukcji if, zawsze dopisuj == true:

private void btnPodsumuj_Click(object sender, RoutedEventArgs e)
{
    int suma = 0;

    // 1. Sprawdzamy RadioButtony
    if (rbMala.IsChecked == true) suma += 20;
    else if (rbSrednia.IsChecked == true) suma += 30;
    else if (rbDuza.IsChecked == true) suma += 40;

    // 2. Sprawdzamy CheckBoxy (każdy osobno!)
    if (cbSer.IsChecked == true) suma += 4;
    if (cbPieczarki.IsChecked == true) suma += 3;
    if (cbSos.IsChecked == true) suma += 2;

    MessageBox.Show($"Łączny koszt zamówienia: {suma} zł", "Podsumowanie");
}