Modele i walidacja w aplikacjach desktopowych


1. Co to są modele i po co je stosujemy?

Wyjaśnienie prostymi słowami

Wyobraź sobie, że piszesz aplikację „Formularz zdrowia” — gdzie użytkownik wpisuje imię, wzrost, wagę, płeć. Możesz trzymać te wartości w zmiennych rozsianych po różnych miejscach w kodzie — ale to będzie bałagan. Znacznie lepiej jest stworzyć model, czyli klasę, która zgromadzi wszystkie dane w jednym obiekcie.
To tak, jak mieć teczkę pacjenta w przychodni — w niej imię, wiek, wyniki badań. Nie trzy luźne kartki, ale uporządkowana teczka.

Co model powinien zawierać?

  • Właściwości (properties) odpowiadające danym z formularza
  • Jeśli to sensowne, metody związane z danymi (np. ObliczBmi() w klasie modelu)

Model nie powinien zawierać kodu, który obsługuje interfejs użytkownika (np. MessageBox.Show), ani walidacji interfejsowej — to będzie osobna część.


2. Co to jest walidacja?

Walidacja to sprawdzanie, czy dane wpisane przez użytkownika są poprawne i sensowne, zanim je użyjesz.

Przykłady:

  • Imię nie może być puste
  • Wzrost musi być liczbą większą niż zero
  • Waga musi być liczbą i mieścić się w rozsądnym zakresie
  • Płeć powinna być spośród dostępnych opcji

Dlaczego to ważne?

  • Zabezpiecza przed błędami (np. dzielenie przez zero)
  • Użytkownik od razu wie, co wpisał źle
  • Program jest bardziej odporny na „dziwne” dane

Walidację też dobrze oddzielić od interfejsu — czyli zrobić klasy/metody walidacyjne, które dostają teksty z kontrolek, a zwracają listę błędów lub gotowy model.


3. Struktura folderów i plików (propozycja)

Załóżmy, że twoja aplikacja to „FormularzZdrowia”. Oto jak możesz zorganizować:

FormularzZdrowia/
│
├── Modele/
│    └── DaneFormularza.cs
│    └── Plec.cs
│
├── Walidacja/
│    └── Walidacje.cs
│    └── WalidatorFormularza.cs
│
├── MainWindow.xaml
└── MainWindow.xaml.cs

Modele to miejsce dla klas, które trzymają dane
Walidacja to miejsce dla klas/metod sprawdzających dane
MainWindow.xaml + .cs to warstwa interfejsu użytkownika


4. Kod krok po kroku

Poniżej najprostszy przykład, który będzie wystarczający na egzamin — bez wzorców MVVM, lecz z wyraźnym oddzieleniem modelu i walidacji.

Plik: Modele/Plec.cs

namespace FormularzZdrowia.Modele
{
    public enum Plec
    {
        NiePodano,
        Kobieta,
        Mezczyzna
    }
}

(To enum — typ wyliczeniowy dla płci.)


Plik: Modele/DaneFormularza.cs

namespace FormularzZdrowia.Modele
{
    public class DaneFormularza
    {
        public string Imie { get; set; } = "";
        public double? WzrostCm { get; set; }
        public double? WagaKg { get; set; }
        public Plec Plec { get; set; } = Plec.NiePodano;

        public double? ObliczBmi()
        {
            if (WzrostCm is null || WagaKg is null) return null;
            double wzrostM = WzrostCm.Value / 100.0;
            if (wzrostM <= 0) return null;
            return Math.Round(WagaKg.Value / (wzrostM * wzrostM), 2);
        }
    }
}
  • double? oznacza typ, który może być pusty (null) — to pomaga, gdy wpis użytkownika nie jest jeszcze poprawny
  • ObliczBmi zwraca null, jeśli nie da się obliczyć

Plik: Walidacja/Walidacje.cs

Tutaj umieszczamy małe, pomocnicze metody, które przydadzą się wielokrotnie.

using System.Globalization;

namespace FormularzZdrowia.Walidacja
{
    public static class Walidacje
    {
        public static bool NiePuste(string? txt) =>
            !string.IsNullOrWhiteSpace(txt);

        public static double? SprobujDouble(string? txt)
        {
            if (string.IsNullOrWhiteSpace(txt))
                return null;
            // Parsowanie z polską kulturą (przecinek jako separator)
            if (double.TryParse(txt, NumberStyles.Float, new CultureInfo("pl-PL"), out double val))
                return val;
            return null;
        }

        public static bool WZakresie(double? wartość, double min, double max) =>
            wartość is not null && wartość >= min && wartość <= max;
    }
}

Plik: Walidacja/WalidatorFormularza.cs

To klasa, która zbiera wszystkie sprawdzenia w jednym miejscu. Weź dane (jako teksty) i sprawdź, czy są ok. Zwróć wynik + ewentualne błędy + gotowy model (jeśli wszystko OK).

using System;
using System.Collections.Generic;
using FormularzZdrowia.Modele;

namespace FormularzZdrowia.Walidacja
{
    public static class WalidatorFormularza
    {
        public static (bool ok, List<string> bledy, DaneFormularza? model)
            SprawdzIZbuduj(string? imieTxt, string? wzrostTxt, string? wagaTxt, string? plecTxt)
        {
            List<string> bledy = new List<string>();

            // Imię
            if (!Walidacje.NiePuste(imieTxt))
                bledy.Add("Imię nie może być puste.");
            else if (imieTxt!.Trim().Length < 2)
                bledy.Add("Imię musi mieć co najmniej 2 znaki.");

            // Wzrost
            var wzrost = Walidacje.SprobujDouble(wzrostTxt);
            if (wzrost is null)
                bledy.Add("Wzrost musi być liczbą.");
            else if (!Walidacje.WZakresie(wzrost, 50, 300))
                bledy.Add("Wzrost musi być między 50 a 300 cm.");

            // Waga
            var waga = Walidacje.SprobujDouble(wagaTxt);
            if (waga is null)
                bledy.Add("Waga musi być liczbą.");
            else if (!Walidacje.WZakresie(waga, 10, 400))
                bledy.Add("Waga musi być między 10 a 400 kg.");

            // Płeć
            Plec plec = Plec.NiePodano;
            if (!string.IsNullOrWhiteSpace(plecTxt))
            {
                if (Enum.TryParse<Plec>(plecTxt, ignoreCase: true, out Plec wynik))
                    plec = wynik;
                else
                    bledy.Add("Niepoprawna wartość płci.");
            }

            if (bledy.Count > 0)
                return (false, bledy, null);

            // Wszystko OK — twórz model
            DaneFormularza model = new DaneFormularza
            {
                Imie = imieTxt!.Trim(),
                WzrostCm = wzrost,
                WagaKg = waga,
                Plec = plec
            };

            return (true, bledy, model);
        }
    }
}

Plik: MainWindow.xaml

To nasz interfejs graficzny — okno z polami do wpisywania i przyciskiem „Wyślij”.

<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="300" Width="350">
    <StackPanel Margin="20">
        <TextBlock Text="Imię:" />
        <TextBox x:Name="txtImie" Margin="0,5"/>
        <TextBlock Text="Wzrost (cm):" />
        <TextBox x:Name="txtWzrost" Margin="0,5"/>
        <TextBlock Text="Waga (kg):" />
        <TextBox x:Name="txtWaga" Margin="0,5"/>
        <TextBlock Text="Płeć:" />
        <TextBox x:Name="txtPlec" Margin="0,5" PlaceholderText="NiePodano / Kobieta / Mezczyzna"/>
        <Button Content="Wyślij" Margin="0,10" Click="Wyślij_Click"/>
        <TextBlock x:Name="txtBledy" Foreground="Red" TextWrapping="Wrap" />
    </StackPanel>
</Window>

Plik: MainWindow.xaml.cs

To kod, który reaguje na kliknięcie, używa walidatora i pokazuje wynik albo błędy.

using System.Windows;
using FormularzZdrowia.Modele;
using FormularzZdrowia.Walidacja;

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

        private void Wyślij_Click(object sender, RoutedEventArgs e)
        {
            // 1. Zbierz teksty z kontrolek
            string imieTxt = txtImie.Text;
            string wzrostTxt = txtWzrost.Text;
            string wagaTxt = txtWaga.Text;
            string plecTxt = txtPlec.Text;

            // 2. Walidacja i zbudowanie modelu
            var (ok, bledy, model) = WalidatorFormularza.SprawdzIZbuduj(imieTxt, wzrostTxt, wagaTxt, plecTxt);

            if (!ok)
            {
                // Wyświetl błędy — jedno pod drugim
                txtBledy.Text = "Błędy:\n" + string.Join("\n", bledy);
                return;
            }

            // 3. Jeśli dane poprawne, pokaz podsumowanie
            // Możemy użyć MessageBox lub otworzyć nowe okno — tu dla uproszczenia:
            double? bmi = model!.ObliczBmi();
            string wynik = bmi is null ? "Nie można obliczyć BMI" : bmi.Value.ToString("0.00");

            MessageBox.Show(
                $"Imię: {model.Imie}\n" +
                $"Wzrost: {model.WzrostCm} cm\n" +
                $"Waga: {model.WagaKg} kg\n" +
                $"Płeć: {model.Plec}\n" +
                $"BMI: {wynik}",
                "Podsumowanie");
        }
    }
}

5. Zadania dla uczniów (do samodzielnego zrobienia)

  1. Rozszerz aplikację o pole Wiek (int).
    • Dodaj do DaneFormularza właściwość Wiek (int?).
    • W walidatorze sprawdź, że wiek jest liczbą całkowitą i w rozsądnym zakresie (np. 0–120).
    • W podsumowaniu pokaż „Wiek: X lat”.
  2. Zamiast pola txtPlec jako zwykłego TextBox, zmień je na ComboBox z opcjami: „NiePodano”, „Kobieta”, „Mezczyzna”.
    • W MainWindow.xaml użyj <ComboBox> i odczytaj SelectedItem lub SelectedValue w Wyślij_Click.
    • Sprawdź, czy wybrana wartość prawidłowo przechodzi walidację i trafia do modelu.
  3. Utwórz nowe okno Window (np. PodsumowanieWindow) i wyświetl dane z model tam zamiast MessageBox.
    • W konstruktorze okna przekaż DaneFormularza model.
    • W XAML tego nowego okna umieść TextBlock-y, aby pokazać Imię, Wzrost, Waga, Płeć, BMI.