Zaprojektowanie estetycznego formularza w XAML z ramkami GroupBox i równo ułożonymi kontrolkami CheckBox czy RadioButton to świetny początek. Prawda jest jednak taka, że bez kodu w C# Twój formularz jest jak samochód bez silnika - ładnie wygląda na parkingu, ale nigdzie nim nie pojedziesz.
Czas połączyć widok z logiką, czyli wejść do pliku Code-Behind (MainWindow.xaml.cs).
Dowiesz się dzisiaj, dlaczego kompilator buntuje się przy zwykłym if (box.IsChecked), jak napisać kalkulator usług na egzamin INF.04 oraz jak sprawić, by aplikacja reagowała na kliknięcia użytkownika w ułamku sekundy!
1. Tajemnica bool?: Dlaczego if (mojBox.IsChecked) wyrzuca błąd?
Gdy zaczynasz pisać logikę w C#, Twoim naturalnym odruchem jest sprawdzenie stanu zaznaczenia kontrolki tak jak zwykłej zmiennej logicznej:
// BŁĄD KOMPILACJI! Kod się nie uruchomi!
if (cbKlimatyzacja.IsChecked)
{
// ...
}
Dlaczego kompilator krzyczy: "Cannot implicitly convert type 'bool?' to 'bool'"?
O co chodzi z tym pytajnikiem?
W WPF kontrolka CheckBox może mieć nie dwa, a trzy stany:
true- zaznaczony (ptaszek),false- odznaczony (pusty),null- stan nieokreślony (często wyświetlany jako wypełniony kwadracik, gdy zaznaczono tylko część podopcji).
Z tego powodu właściwość IsChecked nie jest zwykłym typem bool, lecz typem bool? (Nullable<bool>). Zwykła instrukcja if oczekuje twardej prawdy lub fałszu i nie wie, co zrobić z wartością null.
🧠 Złota reguła na egzamin:
Zawsze jawnie porównuj stan kontrolki z wartością
true:if (cbKlimatyzacja.IsChecked == true) { cena += 150; }Ten prosty zapis eliminuje problem z typem nullowalnym raz na zawsze.
2. Dwa podejścia do formularzy: "Przycisk" vs "W locie"
W aplikacjach okienkowych stan kontrolek wyboru możemy przetwarzać na dwa sposoby. Zobaczmy różnicę na przykładzie cennika warsztatu samochodowego.

3. Podejście #1: Odczyt po kliknięciu przycisku (Standard INF.04)
Użytkownik klika przycisk "Podsumuj usługę", a my w metodzie obsługującej zdarzenie Click sprawdzamy zaznaczone pozycje:
private void btnPodsumuj_Click(object sender, RoutedEventArgs e)
{
decimal suma = 0;
// 1. Sprawdzamy opcje wykluczające się (RadioButton)
if (rbMyciePodstawowe.IsChecked == true)
{
suma += 50;
}
else if (rbMycieKompleksowe.IsChecked == true)
{
suma += 120;
}
// 2. Sprawdzamy opcje dodatkowe (CheckBox)
if (cbWoskowanie.IsChecked == true)
{
suma += 40;
}
if (cbOdkurzanie.IsChecked == true)
{
suma += 30;
}
// 3. Wyświetlamy wynik na ekranie
lblKosztCalkowity.Content = $"Razem do zapłaty: {suma} zł";
}
To podejście jest proste i niezawodne, ale wymaga od użytkownika wciśnięcia przycisku, by zobaczyć jakąkolwiek zmianę.
4. Podejście #2: Reakcja w czasie rzeczywistym (Checked i Unchecked)
Nowoczesne aplikacje nie każą klikać przycisku po każdej drobnej zmianie - reagują natychmiast!
Służą do tego dwa kluczowe zdarzenia:
Checked- odpala się w momencie, gdy użytkownik zaznaczy opcję.Unchecked- odpala się w momencie, gdy użytkownik odznaczy opcję.
Przykład: Dynamiczne odblokowywanie pola (Klasyk egzaminacyjny!)
Wyobraź sobie formularz zgłoszeniowy z opcją:
[ ] Inna usterka (opisz poniżej)
Pole tekstowe pod spodem powinno być zablokowane i aktywować się tylko wtedy, gdy ten ptaszek jest zaznaczony.
Krok 1: XAML
<StackPanel Margin="10">
<!-- TextBox jest domyślnie zablokowany: IsEnabled="False" -->
<CheckBox x:Name="cbInnaUsterka"
Content="Inna usterka (wymaga opisu)"
Checked="cbInnaUsterka_Checked"
Unchecked="cbInnaUsterka_Unchecked" />
<TextBox x:Name="txtOpisUsterki"
IsEnabled="False"
Margin="0,5,0,0"
Height="60"
TextWrapping="Wrap" />
</StackPanel>
Krok 2: Code-Behind (C#)
Do obsługi zdarzeń tworzymy metody zwane EventHandlerami:
// Użytkownik zaznaczył opcję -> Aktywujemy pole
private void cbInnaUsterka_Checked(object sender, RoutedEventArgs e)
{
txtOpisUsterki.IsEnabled = true;
txtOpisUsterki.Focus(); // Automatycznie ustawiamy kursor w polu tekstowym
}
// Użytkownik odznaczył opcję -> Blokujemy pole i czyścimy jego treść
private void cbInnaUsterka_Unchecked(object sender, RoutedEventArgs e)
{
txtOpisUsterki.IsEnabled = false;
txtOpisUsterki.Clear(); // Usuwamy tekst, skoro opcja została cofnięta
}
Efekt

⚠️ Pamiętaj o parze zdarzeń!
Jeśli podepniesz tylkoChecked, a zapomnisz oUnchecked, pole odblokuje się po zaznaczeniu, ale po odznaczeniu ptaszka pozostanie aktywne. Aplikacja zachowa się nielogicznie!
5. Trik zawodowca: Jeden EventHandler dla wielu kontrolek
Chcesz przeliczać łączną kwotę zamówienia w czasie rzeczywistym po każdym kliknięciu w dowolny CheckBox? Nie musisz tworzyć 10 osobnych metod!
Stwórz jedną ogólną metodę przeliczającą:
private void AktualizujCene(object sender, RoutedEventArgs e)
{
decimal suma = 0;
if (cbOpcja1.IsChecked == true) suma += 100;
if (cbOpcja2.IsChecked == true) suma += 200;
if (cbOpcja3.IsChecked == true) suma += 300;
lblSuma.Content = $"Aktualna kwota: {suma} zł";
}
A w pliku XAML podepnij ją pod Checked oraz Unchecked każdego z pól:
<CheckBox x:Name="cbOpcja1" Content="Opcja 1" Checked="AktualizujCene" Unchecked="AktualizujCene" />
<CheckBox x:Name="cbOpcja2" Content="Opcja 2" Checked="AktualizujCene" Unchecked="AktualizujCene" />
<CheckBox x:Name="cbOpcja3" Content="Opcja 3" Checked="AktualizujCene" Unchecked="AktualizujCene" />
Dzięki temu Twój kod w C# pozostaje czysty, zwięzły i łatwy w modyfikacji!
