Walidacja formularza

Znasz już wszystkie podstawowe kontrolki formularza. Teraz czas połączyć je w jeden porządny formularz, który sprawdza dane tak, jak robią to prawdziwe aplikacje: pokazuje wszystkie błędy naraz, zaznacza błędne pola na czerwono, podpowiada w dymku, co jest nie tak, i przenosi kursor do pierwszego problemu. To jedna z najczęściej ocenianych umiejętności na egzaminie praktycznym.

XAML C# walidacja BorderBrush ToolTip DateTime.TryParse 90 min
CEL LEKCJI

Czego się dziś nauczysz

  • Wybierzesz moment sprawdzania danych: przy zapisie, na żywo albo oba — i uzasadnisz wybór.
  • Sprawdzisz typowe dane: tekst, liczbę w zakresie, datę, e-mail, numer telefonu, wybór z listy i zgody.
  • Policzysz wiek na podstawie daty urodzenia.
  • Zaznaczysz błędne pole czerwoną ramką i dymkiem, a potem przywrócisz jego zwykły wygląd.
  • Zbierzesz wszystkie błędy w podsumowaniu i przeniesiesz kursor do pierwszego z nich.
TEORIA

Kiedy i jak sprawdzać

Do tej pory sprawdzałeś pola po kolei i kończyłeś metodę po pierwszym błędzie. To wystarcza w małych oknach, ale przy dłuższym formularzu użytkownik poprawia jedno pole, klika „Zapisz” i… dowiaduje się o kolejnym błędzie. Lepiej:

ZasadaDlaczego
sprawdź wszystkie pola narazużytkownik od razu widzi pełną listę poprawek
zaznacz błędne pola na czerwonowzrok od razu trafia w miejsce problemu
komunikat mówi, co poprawić„Numer telefonu musi mieć 9 cyfr” zamiast „Błąd w polu”
kursor w pierwszym błędnym polumożna od razu zacząć poprawiać
na żywo — dopiero po pierwszej próbie zapisuczerwień przy pierwszym znaku pustego formularza tylko irytuje; po próbie zapisu użytkownik widzi, jak błędy znikają podczas poprawiania
TEORIA

Typowe reguły

DaneJak sprawdzić
tekst wymaganystring.IsNullOrWhiteSpace(pole.Text); „imię i nazwisko” — co najmniej dwa słowa po Split(' ', StringSplitOptions.RemoveEmptyEntries)
liczba w zakresieint.TryParse(…, out int x) && x >= min && x <= max
e-mail (prosto)dokładnie jedno @, nie na początku; po nim kropka, ale nie zaraz po @ i nie na końcu
telefon, kod pocztowyusuń spacje i myślniki (Replace), sprawdź długość i char.IsDigit dla każdego znaku
wybór z listySelectedIndex != -1
przyciski opcji bez wyboru domyślnegoczy któryś ma IsChecked == true
zgoda wymaganaIsChecked == true
hasładługość, rodzaje znaków, zgodność dwóch pól (lekcja 18)

Sprawdzanie e-maila „na sto procent” jest bardzo trudne — na egzaminie i w większości programów wystarczą proste reguły z tabeli. Ostatecznym sprawdzeniem jest i tak wysłanie wiadomości z linkiem potwierdzającym.

TEORIA

Data i wiek

Datę wpisaną w polu zamienisz tak jak liczbę: DateTime.TryParse. Przy polskich ustawieniach Windows rozumie zapis 12.05.2012, a odrzuca np. 31.02.2012. Wiek liczysz z różnicy lat, poprawionej o jeden, jeśli urodziny w tym roku jeszcze nie minęły:

MainWindow.xaml.cs
if (!DateTime.TryParse(Data.Text, out DateTime urodziny) || urodziny > DateTime.Today)
{
    // zła data albo data z przyszłości
}
else
{
    int wiek = DateTime.Today.Year - urodziny.Year;
    if (urodziny.AddYears(wiek) > DateTime.Today)
    {
        wiek--;          // urodziny w tym roku jeszcze przed nami
    }
}

Przykład: dziś 08.10.2026, urodziny 20.11.2010. Różnica lat to 16, ale 20.11.2026 jest po dzisiejszej dacie, więc wiek to 15. Wygodny wybór daty z kalendarza poznasz w lekcji 32.

TEORIA

Wyróżnianie błędów

Każda kontrolka formularza dziedziczy po klasie Control, więc ma BorderBrush (kolor ramki), Foreground i ToolTip (lekcja 05). Metoda pomocnicza z parametrem typu Control obsłuży więc pole tekstowe, listę rozwijaną i pole hasła:

MainWindow.xaml.cs
private void Oznacz(Label opis, Control pole, string komunikat)
{
    opis.Foreground = Brushes.Red;     // czerwony opis pola
    pole.BorderBrush = Brushes.Red;    // czerwona ramka
    pole.ToolTip = komunikat;          // dymek po najechaniu myszą
}

private void OznaczOk(Label opis, Control pole)
{
    opis.Foreground = Brushes.Black;
    pole.ClearValue(Control.BorderBrushProperty);   // przywraca domyślną ramkę
    pole.ToolTip = null;
}

Dlaczego ClearValue, a nie BorderBrush = Brushes.Gray? Domyślna ramka pola ma określony odcień i zmienia kolor po najechaniu myszą i po kliknięciu. Wpisanie koloru na stałe wyłączyłoby to zachowanie. ClearValue usuwa nasze ustawienie i kontrolka wraca do wyglądu domyślnego. Nazwa Control.BorderBrushProperty oznacza „właściwość BorderBrush” — więcej o takich właściwościach w bloku o wyglądzie.

TEORIA

Szkielet metody Sprawdz

MainWindow.xaml.cs
private string bledy = "";
private Control? pierwszyBlad = null;

private bool Sprawdz()
{
    bledy = "";
    pierwszyBlad = null;

    if (/* pole 1 złe */) Oznacz(Opis1, Pole1, "…"); else OznaczOk(Opis1, Pole1);
    if (/* pole 2 złe */) Oznacz(Opis2, Pole2, "…"); else OznaczOk(Opis2, Pole2);
    // … każde pole: albo Oznacz, albo OznaczOk — nigdy return w środku

    return bledy == "";
}

private void Zapisz_Click(object sender, RoutedEventArgs e)
{
    if (!Sprawdz())
    {
        pierwszyBlad?.Focus();      // ?. — wywołaj tylko, gdy nie jest null
        return;
    }
    // dane są poprawne — zapis
}

Najważniejsze: w Sprawdz nie ma return po pierwszym błędzie. Każde pole dostaje swoją ocenę, a Oznacz dopisuje komunikat do wspólnej listy i zapamiętuje pierwsze błędne pole. Operator ?. wywołuje metodę tylko wtedy, gdy zmienna nie jest null.

PRZYKŁAD

Zapisy na obóz żeglarski

Formularz zgłoszenia na obóz dla osób w wieku 10–19 lat. Po kliknięciu Zapisz program sprawdza wszystkie pola, zaznacza błędne na czerwono, wypisuje listę błędów nad formularzem i ustawia kursor w pierwszym złym polu. Od tej chwili formularz sprawdza się na żywo — błędy znikają w trakcie poprawiania. Zgoda rodzica jest wymagana tylko dla niepełnoletnich.

Zapisy na obóz żeglarski–□✕
Popraw formularz:
• Numer telefonu musi mieć 9 cyfr.
• Brak zgody rodzica na wyjazd.
Imię i nazwisko:
Kuba Lis
Data urodzenia:
14.03.2011
dd.mm.rrrr
E-mail rodzica:
anna.lis@poczta.pl
Telefon rodzica:
601 22 33
Turnus:
II turnus: 10.07–19.07▾
Rodzic wyraża zgodę na wyjazd (wymagane dla niepełnoletnich)
Akceptuję regulamin obozu
Wyczyść
Zapisz

po kliknięciu „Zapisz” — dwa błędy, kursor w polu telefonu

MainWindow.xaml — pełny plik
<Window x:Class="Lekcja22.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Zapisy na obóz żeglarski" SizeToContent="Height" Width="520"
        ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen">

    <StackPanel Margin="15">
        <!-- podsumowanie błędów: ukryte, dopóki formularz jest poprawny -->
        <Border x:Name="Podsumowanie" Background="#FFFDECEA" BorderBrush="IndianRed"
                BorderThickness="1" CornerRadius="4" Padding="10" Margin="0,0,0,10"
                Visibility="Collapsed">
            <TextBlock x:Name="ListaBledow" Foreground="DarkRed" TextWrapping="Wrap"/>
        </Border>

        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>

            <Label x:Name="OpisImie" Grid.Row="0" Content="_Imię i nazwisko:"
                   Target="{Binding ElementName=Imie}"/>
            <TextBox x:Name="Imie" Grid.Row="0" Grid.Column="1" Margin="0,4"
                     MaxLength="50" TextChanged="Pole_Zmiana"/>

            <Label x:Name="OpisData" Grid.Row="1" Content="_Data urodzenia:"
                   Target="{Binding ElementName=Data}"/>
            <StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal">
                <TextBox x:Name="Data" Width="100" Margin="0,4" MaxLength="10"
                         TextChanged="Pole_Zmiana"/>
                <TextBlock Text="dd.mm.rrrr" Foreground="Gray" Margin="8,0,0,0"
                           VerticalAlignment="Center"/>
            </StackPanel>

            <Label x:Name="OpisEmail" Grid.Row="2" Content="_E-mail rodzica:"
                   Target="{Binding ElementName=Email}"/>
            <TextBox x:Name="Email" Grid.Row="2" Grid.Column="1" Margin="0,4"
                     MaxLength="60" TextChanged="Pole_Zmiana"/>

            <Label x:Name="OpisTelefon" Grid.Row="3" Content="_Telefon rodzica:"
                   Target="{Binding ElementName=Telefon}"/>
            <TextBox x:Name="Telefon" Grid.Row="3" Grid.Column="1" Margin="0,4"
                     Width="140" HorizontalAlignment="Left" MaxLength="15"
                     TextChanged="Pole_Zmiana"/>

            <Label x:Name="OpisTurnus" Grid.Row="4" Content="T_urnus:"
                   Target="{Binding ElementName=Turnus}"/>
            <ComboBox x:Name="Turnus" Grid.Row="4" Grid.Column="1" Margin="0,4"
                      SelectionChanged="Turnus_SelectionChanged">
                <ComboBoxItem Content="I turnus: 29.06–08.07"/>
                <ComboBoxItem Content="II turnus: 10.07–19.07"/>
                <ComboBoxItem Content="III turnus: 03.08–12.08"/>
            </ComboBox>

            <StackPanel Grid.Row="5" Grid.Column="1" Margin="0,6,0,0">
                <CheckBox x:Name="ZgodaRodzica" Margin="0,3"
                          Content="Rodzic wyraża zgodę na wyjazd (wymagane dla niepełnoletnich)"
                          Checked="Zgoda_Zmiana" Unchecked="Zgoda_Zmiana"/>
                <CheckBox x:Name="Regulamin" Content="Akceptuję regulamin obozu" Margin="0,3"
                          Checked="Zgoda_Zmiana" Unchecked="Zgoda_Zmiana"/>
            </StackPanel>
        </Grid>

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
            <Button Content="Wy_czyść" Width="90" Height="30" Margin="0,0,8,0" Click="Wyczysc_Click"/>
            <Button Content="Zapisz" Width="90" Height="30" IsDefault="True" Click="Zapisz_Click"/>
        </StackPanel>
    </StackPanel>
</Window>
MainWindow.xaml.cs — pełny plik
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Lekcja22;

public partial class MainWindow : Window
{
    private bool probowanoZapisac = false;   // po pierwszej próbie sprawdzamy na żywo
    private string bledy = "";               // lista komunikatów, linia po linii
    private Control? pierwszyBlad = null;    // tu wróci kursor

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Zapisz_Click(object sender, RoutedEventArgs e)
    {
        probowanoZapisac = true;

        if (!Sprawdz())
        {
            pierwszyBlad?.Focus();
            return;
        }

        MessageBox.Show($"Zapisano: {Imie.Text.Trim()}\n{Turnus.Text}\n" +
                        $"Potwierdzenie wyślemy na adres {Email.Text.Trim()}.",
                        "Zapisy", MessageBoxButton.OK, MessageBoxImage.Information);
        Wyczysc();
    }

    private void Wyczysc_Click(object sender, RoutedEventArgs e)
    {
        Wyczysc();
    }

    // Zmiany w polach: sprawdzamy na żywo dopiero po pierwszej próbie zapisu
    private void Pole_Zmiana(object sender, TextChangedEventArgs e)
    {
        if (probowanoZapisac) Sprawdz();
    }

    private void Turnus_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (probowanoZapisac) Sprawdz();
    }

    private void Zgoda_Zmiana(object sender, RoutedEventArgs e)
    {
        if (probowanoZapisac) Sprawdz();
    }

    // Sprawdza WSZYSTKIE pola i zaznacza błędne. Zwraca true, gdy formularz jest poprawny.
    private bool Sprawdz()
    {
        bledy = "";
        pierwszyBlad = null;

        // 1. Imię i nazwisko: co najmniej dwa słowa
        string imie = Imie.Text.Trim();
        if (imie.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length < 2)
            Oznacz(OpisImie, Imie, "Podaj imię i nazwisko uczestnika.");
        else
            OznaczOk(OpisImie, Imie);

        // 2. Data urodzenia: poprawna data, wiek 10–19 lat w dniu dzisiejszym
        int wiek = -1;
        if (!DateTime.TryParse(Data.Text, out DateTime urodziny) || urodziny > DateTime.Today)
        {
            Oznacz(OpisData, Data, "Wpisz poprawną datę urodzenia (dd.mm.rrrr).");
        }
        else
        {
            wiek = DateTime.Today.Year - urodziny.Year;
            if (urodziny.AddYears(wiek) > DateTime.Today)
            {
                wiek--;                              // urodziny w tym roku jeszcze nie minęły
            }

            if (wiek < 10 || wiek > 19)
                Oznacz(OpisData, Data, $"Obóz jest dla osób w wieku 10–19 lat (uczestnik ma {wiek}).");
            else
                OznaczOk(OpisData, Data);
        }

        // 3. E-mail: jeden znak @, przed nim coś, po nim kropka
        string email = Email.Text.Trim();
        int malpa = email.IndexOf('@');
        bool emailOk = malpa > 0 && malpa == email.LastIndexOf('@')
                       && email.IndexOf('.', malpa) > malpa + 1 && !email.EndsWith('.');
        if (!emailOk)
            Oznacz(OpisEmail, Email, "Adres e-mail jest niepoprawny.");
        else
            OznaczOk(OpisEmail, Email);

        // 4. Telefon: 9 cyfr, spacje i myślniki dozwolone
        string cyfry = Telefon.Text.Replace(" ", "").Replace("-", "");
        bool telefonOk = cyfry.Length == 9;
        foreach (char z in cyfry)
        {
            if (!char.IsDigit(z)) telefonOk = false;
        }
        if (!telefonOk)
            Oznacz(OpisTelefon, Telefon, "Numer telefonu musi mieć 9 cyfr.");
        else
            OznaczOk(OpisTelefon, Telefon);

        // 5. Turnus
        if (Turnus.SelectedIndex == -1)
            Oznacz(OpisTurnus, Turnus, "Wybierz turnus.");
        else
            OznaczOk(OpisTurnus, Turnus);

        // 6. Zgody — zgoda rodzica tylko dla niepełnoletnich
        if (wiek >= 0 && wiek < 18 && ZgodaRodzica.IsChecked != true)
            OznaczPole(ZgodaRodzica, "Brak zgody rodzica na wyjazd.");
        else
            ZgodaRodzica.Foreground = Brushes.Black;

        if (Regulamin.IsChecked != true)
            OznaczPole(Regulamin, "Zaakceptuj regulamin obozu.");
        else
            Regulamin.Foreground = Brushes.Black;

        // podsumowanie nad formularzem
        ListaBledow.Text = "Popraw formularz:\n" + bledy.TrimEnd();
        Podsumowanie.Visibility = bledy == "" ? Visibility.Collapsed : Visibility.Visible;

        return bledy == "";
    }

    // Zaznacza błędne pole: czerwony opis, czerwona ramka, dymek z komunikatem
    private void Oznacz(Label opis, Control pole, string komunikat)
    {
        opis.Foreground = Brushes.Red;
        pole.BorderBrush = Brushes.Red;
        pole.ToolTip = komunikat;
        DodajBlad(pole, komunikat);
    }

    private void OznaczPole(CheckBox pole, string komunikat)
    {
        pole.Foreground = Brushes.Red;
        DodajBlad(pole, komunikat);
    }

    private void DodajBlad(Control pole, string komunikat)
    {
        bledy += "• " + komunikat + "\n";
        if (pierwszyBlad == null)
        {
            pierwszyBlad = pole;
        }
    }

    // Przywraca wygląd poprawnego pola
    private void OznaczOk(Label opis, Control pole)
    {
        opis.Foreground = Brushes.Black;
        pole.ClearValue(Control.BorderBrushProperty);   // domyślna ramka
        pole.ToolTip = null;
    }

    private void Wyczysc()
    {
        probowanoZapisac = false;     // czyszczenie pól nie uruchomi sprawdzania

        Imie.Clear();
        Data.Clear();
        Email.Clear();
        Telefon.Clear();
        Turnus.SelectedIndex = -1;
        ZgodaRodzica.IsChecked = false;
        Regulamin.IsChecked = false;

        OznaczOk(OpisImie, Imie);
        OznaczOk(OpisData, Data);
        OznaczOk(OpisEmail, Email);
        OznaczOk(OpisTelefon, Telefon);
        OznaczOk(OpisTurnus, Turnus);
        ZgodaRodzica.Foreground = Brushes.Black;
        Regulamin.Foreground = Brushes.Black;
        Podsumowanie.Visibility = Visibility.Collapsed;

        Imie.Focus();
    }
}

Co dzieje się po kolei

  • Ramka Podsumowanie nad formularzem jest ukryta (Collapsed), dopóki są błędy do pokazania. Okno ma SizeToContent="Height", więc rośnie, gdy ramka się pojawia.
  • Etykiety mają nazwy (OpisImie, OpisData…) — kod zmienia ich kolor.
  • Pole probowanoZapisac decyduje, czy sprawdzać na żywo. Zdarzenia zmian (TextChanged, SelectionChanged, Checked) wywołują Sprawdz dopiero po pierwszej próbie zapisu.
  • Sprawdz ocenia każde pole i wywołuje Oznacz albo OznaczOk. Oznacz dopisuje komunikat do bledy i zapamiętuje pierwsze błędne pole.
  • Data urodzenia daje wiek — a od wieku zależy, czy wymagana jest zgoda rodzica. Gdy data jest zła, wiek zostaje −1 i warunek zgody nie jest sprawdzany (błąd daty i tak jest na liście).
  • Telefon może zawierać spacje i myślniki — Replace usuwa je przed liczeniem cyfr.
  • Pola wyboru nie mają etykiety, więc OznaczPole zmienia kolor ich własnego opisu.
  • Wyczysc najpierw wyłącza sprawdzanie na żywo, a dopiero potem czyści pola — inaczej każde czyszczenie zapalałoby czerwone ramki.

Sprawdź trzy rzeczy

  1. Kliknij Zapisz przy pustym formularzu, a potem wypełniaj pola po kolei. Komunikaty znikają z listy jeden po drugim.
  2. Wpisz datę urodzenia osoby 18-letniej. Błąd „Brak zgody rodzica” zniknie, choć pole zgody jest puste.
  3. Zamień w OznaczOk linię z ClearValue na pole.BorderBrush = Brushes.Gray;. Najedź myszą na poprawione pole — ramka przestała się podświetlać.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementTypCo robi
Control.BorderBrushpędzelKolor ramki pola, listy, pola hasła.
ToolTipobject?Dymek po najechaniu myszą; null usuwa dymek.
ClearValue(Control.BorderBrushProperty)metodaPrzywraca domyślną wartość właściwości.
DateTime.TryParse(s, out DateTime d)boolTekst → data bez ryzyka wyjątku.
DateTime.TodayDateTimeDzisiejsza data (bez godziny).
AddYears(n)DateTimeData przesunięta o n lat.
Replace(" ", "")stringUsuwa wszystkie spacje.
EndsWith('.')boolCzy napis kończy się znakiem.
obiekt?.Metoda()operatorWywołaj, gdy obiekt nie jest null.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
return po pierwszym błędzie w SprawdzPozostałe pola nie są oceniane — stare czerwone ramki zostają albo nowe się nie pojawiają.
tylko Oznacz, bez OznaczOkPoprawione pole zostaje czerwone.
sprawdzanie na żywo od startuPusty formularz od razu świeci na czerwono.
Wyczysc czyści pola przy włączonym sprawdzaniu na żywoKażde czyszczenie zapala błędy.
DateTime.Parse(Data.Text)FormatException przy złej dacie. Użyj TryParse.
wiek = rok bieżący − rok urodzeniaPrzez część roku wynik jest o 1 za duży.
komunikat „Błąd w formularzu”Użytkownik nie wie, co poprawić. Nazwij pole i regułę.
ZADANIA

Zadania

Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Stosuj zasady z lekcji: wszystkie błędy naraz, czerwone wyróżnienie z dymkiem, kursor w pierwszym błędnym polu, sprawdzanie na żywo po pierwszej próbie. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.

ZAD 1Formularz kontaktowy★☆☆

Formularz „Napisz do sekretariatu”: imię, e-mail, temat (lista: rekrutacja, legitymacja, zaświadczenie, inne), treść (wielowierszowa, 20–500 znaków, licznik pod spodem). Wyślij sprawdza wszystkie pola i pokazuje podsumowanie błędów w czerwonej ramce, a przy poprawnych danych — zieloną ramkę „Wiadomość wysłana” i czyści formularz.

pokaż wskazówkę
  • Skopiuj z przykładu metody Oznacz, OznaczOk i szkielet Sprawdz.
  • Jedna ramka może być zielona albo czerwona — zmieniasz jej Background, BorderBrush i tekst.
ZAD 2Adres z podpowiedzią na żywo★☆☆

Pola: ulica, numer domu, kod pocztowy, miejscowość. Kod pocztowy sprawdza się przy każdym znaku: niepełny kod ma zwykłą ramkę, poprawny NN-NNN — zieloną, a błędny (litera, zła pozycja myślnika, za długi) — czerwoną z dymkiem. Gdy użytkownik wpisze 5 cyfr bez myślnika („84300”), program sam wstawia myślnik. Przycisk Zapisz sprawdza wszystkie pola.

pokaż wskazówkę
  • Po wstawieniu myślnika z kodu ustaw kursor na końcu: CaretIndex = Text.Length. Zmiana Text wywoła TextChanged drugi raz — upewnij się, że nie wpadniesz w pętlę.
ZAD 3Ewidencja sprzętu w pracowni★★☆

Formularz dodawania sprzętu: numer inwentarzowy w formacie ZSMI/2026/0123 (rok 2000–bieżący, numer czterocyfrowy), nazwa, kategoria (lista), data zakupu (nie z przyszłości, a jej rok musi zgadzać się z rokiem w numerze inwentarzowym), cena (0,01–50 000 zł, z groszami), sala (lista). Poprawny sprzęt trafia do listy pod formularzem jako „ZSMI/2026/0123 — Monitor Dell (s. 204)”, a numer inwentarzowy nie może się powtórzyć.

pokaż wskazówkę
  • Numer podziel Split('/'): muszą wyjść 3 części, pierwsza to „ZSMI”, druga i trzecia — liczby o właściwej długości.
  • Reguła łącząca dwa pola (rok w numerze i rok zakupu) oznacza na czerwono oba pola.
ZAD 4Wyjazd grupowy★★☆

Zamówienie przejazdu autokarem: nazwa grupy, liczba uczestników (1–50), liczba opiekunów, data wyjazdu (od jutra do roku naprzód), godzina wyjazdu (GG:MM, 5:00–22:00), pole wyboru Wyjazd zagraniczny. Reguły zależne: na każdych rozpoczętych 15 uczestników co najmniej jeden opiekun; przy wyjeździe zagranicznym wymagane pole Numer polisy (pojawia się dopiero po zaznaczeniu opcji). Błędy reguł zależnych pokaż przy obu polach, których dotyczą.

pokaż wskazówkę
  • Godzinę sprawdzisz przez TimeSpan.TryParse i porównanie z new TimeSpan(5, 0, 0).
  • Ukryte pole polisy nie powinno blokować zapisu, gdy wyjazd jest krajowy.
ZAD 5Sprawdzanie numeru ISBN★★☆

Biblioteka szkolna przyjmuje książki z darów. Formularz: tytuł, autor, ISBN-13 (z myślnikami lub bez), rok wydania. Numer ISBN-13 ma 13 cyfr, a ostatnia jest cyfrą kontrolną: cyfry na pozycjach nieparzystych mnożymy przez 1, na parzystych przez 3, sumujemy pierwsze 12 iloczynów, a cyfra kontrolna to (10 − suma % 10) % 10. Przy złej cyfrze kontrolnej komunikat podaje, jaka powinna być. Przykład poprawnego numeru: 978-83-246-7768-9.

pokaż wskazówkę
  • Cyfrę ze znaku zamienisz przez znak - '0' — znaki cyfr leżą w tablicy kodów po kolei.
  • Pozycja pierwsza to indeks 0, więc mnożnik 1 dla indeksów parzystych.
ZAD 6Rekrutacja do szkoły★★★

Formularz rekrutacyjny kandydata: imię, nazwisko, PESEL, data urodzenia, płeć (przyciski opcji), kierunek pierwszego i drugiego wyboru (dwie listy — nie mogą być takie same), e-mail i telefon rodzica, zgoda na przetwarzanie danych. PESEL sprawdź tak jak urzędy: 11 cyfr, poprawna cyfra kontrolna (wagi 1, 3, 7, 9, 1, 3, 7, 9, 1, 3), data urodzenia zapisana w PESEL zgodna z datą w formularzu (dla urodzonych w latach 2000–2099 do miesiąca dodaje się 20) i płeć zgodna z przedostatnią cyfrą (parzysta — kobieta). Pracuj na wymyślonych numerach PESEL, nie na prawdziwych danych.

pokaż wskazówkę
  • Cyfra kontrolna: (10 − suma % 10) % 10, gdzie suma to iloczyny pierwszych 10 cyfr przez wagi.
  • Rok z PESEL: dwie pierwsze cyfry; miesiąc 21–32 oznacza lata 2000. i trzeba odjąć 20.
  • Reguła PESEL ↔ data ↔ płeć dotyczy trzech pól — oznacz wszystkie, których dotyczy niezgodność.
PODSUMOWANIE

Co trzeba zapamiętać

  • Sprawdzaj wszystkie pola naraz — w metodzie Sprawdz nie ma return po pierwszym błędzie.
  • Każde pole dostaje albo Oznacz (czerwony opis, ramka, dymek), albo OznaczOk (ClearValue).
  • Podsumowanie błędów nad formularzem + kursor w pierwszym błędnym polu.
  • Na żywo sprawdzaj dopiero po pierwszej próbie zapisu.
  • DateTime.TryParse do dat; wiek liczony z poprawką na urodziny w bieżącym roku.
  • Komunikat mówi, co poprawić, a nie tylko, że jest błąd.

Dokumentacja: Microsoft Learn — DateTime.TryParse.