Aplikacje desktopowe · Dane

Modele danych i walidacja formularzy

Formularze mają zwykle wiele pól, a dane z nich trzeba uporządkować i sprawdzić, zanim ich użyjesz. Poznasz model danych (klasę grupującą informacje) i walidację, czyli sprawdzanie poprawności, tak aby program nie „wywracał się” na błędnym wpisie.

  • około 50 minut
  • WPF · model · walidator · TryParse
  • INF.04.5.3

Po tej lekcji: zbudować klasę modelu z właściwościami, napisać osobny walidator zwracający listę błędów i bezpiecznie zamienić tekst na liczbę przez TryParse.

Zacznijmy od teczki

Teczka pacjenta zamiast trzech karteczek

W aplikacji „Formularz zdrowia” użytkownik wpisuje imię, wzrost i wagę. Można trzymać te wartości w luźnych zmiennych rozsianych po kodzie, ale to bałagan. Lepiej zebrać je w model: jedną klasę, która grupuje powiązane dane, jak teczka pacjenta w przychodni.

Z kolei walidacja to sprawdzenie, czy wpisane dane mają sens, zanim ich użyjesz. Chroni przed błędami (np. dzieleniem przez zero) i od razu mówi użytkownikowi, co poprawić.

1 · Podział odpowiedzialności

Model trzyma dane, walidator je sprawdza

Modelfolder Modele

Klasa z właściwościami odpowiadającymi polom formularza i metodami bezpośrednio związanymi z tymi danymi, np. ObliczBmi(). Nie zna okna i nie pokazuje komunikatów.

Na przykład DaneFormularza

Walidatorfolder Walidacja

Osobna klasa, która przyjmuje model i zwraca listę błędów (pustą, gdy wszystko jest poprawne). Nie pokazuje ich: to robi okno.

Na przykład WalidatorFormularza.Sprawdz(dane)

Laboratorium · ćwiczenie w przeglądarce, do którego odsyłają rozgrzewki na końcu lekcji

2 · Sprawdź w działaniu

Co się stanie przy błędnych danych

Formularz startuje z błędnymi danymi (puste imię, wzrost 0, waga „abc”). Kliknij „Oblicz BMI” i obejrzyj model i listę błędów. Potem przełącz walidator na „tylko pierwszy błąd” oraz zamianę na double.Parse, i zobacz, jak program się wywraca.

Ta interakcja wymaga włączonego JavaScriptu.

Zbieraj wszystkie błędy naraz, a tekst zamieniaj na liczbę przez TryParse. Użytkownik poprawi wszystko w jednym podejściu, a program się nie wywróci.

3 · Dane

DaneFormularza.cs

Model to zwykła klasa C#: trzy właściwości i jedna metoda związana z tymi danymi. Żadnych odwołań do okna.

Modele/DaneFormularza.cscsharp
namespace FormularzZdrowia.Modele;

public class DaneFormularza
{
    public string Imie { get; set; } = "";
    public double WzrostCm { get; set; }
    public double WagaKg { get; set; }

    // Metoda związana z danymi modelu: to jest w porządku w modelu
    public double ObliczBmi()
    {
        double wzrostM = WzrostCm / 100.0;
        return WagaKg / (wzrostM * wzrostM);
    }
}
  1. { get; set; } Właściwość automatyczna: do odczytu i zapisu. Wartość domyślna "" zapobiega null.
  2. ObliczBmi() Metoda należąca do danych: BMI = waga podzielona przez wzrost w metrach podniesiony do kwadratu.
  3. WzrostCm / 100.0 Dzielimy przez 100.0, a nie 100, żeby wynik był liczbą rzeczywistą.

4 · Reguły

WalidatorFormularza.cs

Walidator zwraca listę komunikatów. Dzięki temu okno pokaże wszystkie problemy naraz, a nie tylko pierwszy.

Walidacja/WalidatorFormularza.cscsharp
using System.Collections.Generic;
using FormularzZdrowia.Modele;

namespace FormularzZdrowia.Walidacja;

public class WalidatorFormularza
{
    public List<string> Sprawdz(DaneFormularza dane)
    {
        var bledy = new List<string>();

        if (string.IsNullOrWhiteSpace(dane.Imie))
        {
            bledy.Add("Imię nie może być puste.");
        }
        if (dane.WzrostCm <= 0 || dane.WzrostCm > 250)
        {
            bledy.Add("Wzrost musi być liczbą z zakresu 1-250 cm.");
        }
        if (dane.WagaKg <= 0 || dane.WagaKg > 300)
        {
            bledy.Add("Waga musi być liczbą z zakresu 1-300 kg.");
        }

        return bledy;   // pusta lista oznacza, że dane są poprawne
    }
}
  1. List<string> bledy Zbiera komunikaty. Pusta lista oznacza poprawne dane.
  2. string.IsNullOrWhiteSpace Wykrywa puste pole i pole z samymi spacjami.
  3. dane.WzrostCm <= 0 || dane.WzrostCm > 250 Reguła zakresu: wzrost musi być sensowny.
  4. return bledy Walidator niczego nie wyświetla. Ta decyzja należy do okna.

5 · Cały przepływ

MainWindow.xaml.cs

Kolejność jest zawsze ta sama: zbuduj model, sprawdź go walidatorem, a dopiero przy poprawnych danych wykonaj obliczenie.

MainWindow.xaml.cscsharp
using System.Windows;
using FormularzZdrowia.Modele;
using FormularzZdrowia.Walidacja;

namespace FormularzZdrowia;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void PrzyciskOblicz_Click(object sender, RoutedEventArgs e)
    {
        var dane = new DaneFormularza
        {
            Imie = PoleImie.Text,
            WzrostCm = double.TryParse(PoleWzrost.Text, out var w) ? w : 0,
            WagaKg = double.TryParse(PoleWaga.Text, out var m) ? m : 0
        };

        var walidator = new WalidatorFormularza();
        var bledy = walidator.Sprawdz(dane);

        if (bledy.Count > 0)
        {
            EtykietaWynik.Text = "Błędy: " + string.Join(" ", bledy);
            return;
        }

        EtykietaWynik.Text = $"BMI dla {dane.Imie}: {dane.ObliczBmi():F1}";
    }
}
  1. double.TryParse(PoleWzrost.Text, out var w) ? w : 0 Próbuje zamienić tekst na liczbę. Gdy się nie uda, zwraca 0, więc program się nie wywraca, a walidator zgłosi błąd.
  2. walidator.Sprawdz(dane) Walidacja gotowego obiektu modelu.
  3. if (bledy.Count > 0) { ...; return; } Przy błędach pokazujemy je i kończymy metodę, nie licząc BMI.
  4. {dane.ObliczBmi():F1} Wynik z jednym miejscem po przecinku.

Do zadań

Zbieranie wszystkich błędów i pokazanie ich razem

W zadaniach walidator ma zgłosić wszystkie złamane reguły. Zamiast kończyć na pierwszym błędzie, dodawaj komunikaty do listy i wyświetl ją na końcu.

WalidatorFormularza.cscsharp
using System.Linq;                                   // dla Any

public static List<string> Sprawdz(string imie, string haslo, string powtorzenie, string wiekTekst)
{
    var bledy = new List<string>();

    if (imie.Trim().Length < 2)
        bledy.Add("Imię ma mieć co najmniej 2 znaki.");
    if (imie.Any(char.IsDigit))                      // czy jakikolwiek znak jest cyfrą
        bledy.Add("Imię nie może zawierać cyfr.");
    if (haslo.Length < 8)
        bledy.Add("Hasło ma mieć co najmniej 8 znaków.");
    if (haslo != powtorzenie)
        bledy.Add("Hasła muszą być identyczne.");
    if (!int.TryParse(wiekTekst, out int wiek) || wiek < 13 || wiek > 120)
        bledy.Add("Wiek ma być liczbą od 13 do 120.");

    return bledy;
}

// w oknie: każdy błąd w nowej linii
var bledy = WalidatorFormularza.Sprawdz(...);
EtykietaBledy.Text = string.Join(Environment.NewLine, bledy);
  1. imie.Any(char.IsDigit) Przechodzi po wszystkich znakach i sprawdza, czy któryś jest cyfrą.
  2. string.Join(Environment.NewLine, bledy) Skleja listę w jeden napis, wstawiając koniec linii między elementami.
  3. Pusta lista Brak błędów (bledy.Count == 0) oznacza, że dane są poprawne.

6 · Wygląd

MainWindow.xaml

Trzy pola, przycisk i etykieta na wynik lub błędy. Zwykły widok bez logiki.

MainWindow.xamlxaml
<Window x:Class="FormularzZdrowia.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Formularz zdrowia" Height="320" Width="360">
    <StackPanel Margin="20">
        <TextBlock Text="Imię:" />
        <TextBox x:Name="PoleImie" Margin="0,2,0,8" />
        <TextBlock Text="Wzrost (cm):" />
        <TextBox x:Name="PoleWzrost" Margin="0,2,0,8" />
        <TextBlock Text="Waga (kg):" />
        <TextBox x:Name="PoleWaga" Margin="0,2,0,12" />
        <Button Content="Oblicz BMI" Click="PrzyciskOblicz_Click" />
        <TextBlock x:Name="EtykietaWynik" TextWrapping="Wrap" Margin="0,12,0,0" />
    </StackPanel>
</Window>
  1. TextWrapping="Wrap" Długa lista błędów zawija się do kolejnych linii zamiast wychodzić poza okno.

7 · Czego unikać

Trzy błędy z walidacją

`MessageBox` w walidatorze
Tak nie
public bool Sprawdz(DaneFormularza dane)
{
    if (dane.WzrostCm <= 0)
    {
        MessageBox.Show("Zły wzrost");
        return false;
    }
    return true;
}

Walidatora nie da się przetestować ani użyć poza oknem, a komunikat jest „na sztywno”.

Tak
public List<string> Sprawdz(DaneFormularza dane)
{
    var bledy = new List<string>();
    if (dane.WzrostCm <= 0) bledy.Add("Zły wzrost");
    return bledy;
}

Walidator zwraca informację, a okno decyduje, jak ją pokazać.

Tylko pierwszy błąd
Tak nie
if (string.IsNullOrWhiteSpace(dane.Imie)) return "Brak imienia";
if (dane.WzrostCm <= 0) return "Zły wzrost";

Użytkownik poprawia jedno pole i dopiero wtedy widzi kolejny błąd.

Tak
var bledy = new List<string>();
if (string.IsNullOrWhiteSpace(dane.Imie)) bledy.Add("Brak imienia");
if (dane.WzrostCm <= 0) bledy.Add("Zły wzrost");
return bledy;

Lista pozwala pokazać wszystkie problemy za jednym razem.

`double.Parse` bez zabezpieczenia
Tak nie
double wzrost = double.Parse(PoleWzrost.Text);

Wpisanie „abc” kończy się wyjątkiem FormatException i awarią programu.

Tak
double wzrost = double.TryParse(PoleWzrost.Text, out var w) ? w : 0;

TryParse zwraca false zamiast rzucać wyjątek, więc możesz zareagować.

8 · Sprawdź się

Gdzie to napiszesz?

Cztery sytuacje z formularzem.

Ta interakcja wymaga włączonego JavaScriptu.

Podsumowanie

Co warto zapamiętać

  1. Model to klasa grupująca powiązane dane, z metodami związanymi z tymi danymi, bez wiedzy o oknie.
  2. Walidator to osobna klasa zwracająca listę błędów. Pusta lista oznacza poprawne dane.
  3. Walidator nie pokazuje komunikatów: robi to okno.
  4. Zbieraj wszystkie błędy naraz, a nie tylko pierwszy.
  5. Tekst na liczbę zamieniaj przez TryParse, a nie Parse, żeby program się nie wywrócił.

Dalej na swoim koncie

Rozgrzewki, quiz i zadania są w biurku ucznia.

Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.

Zaloguj się →
Źródła i dalsza nauka