Oddzielenie logiki od UI – WPF i Windows Forms

Nauczysz się rozdzielać widok (UI) od logiki w dwóch technologiach desktopowych:

  • WPF (nowocześniejszy XAML na Windows)
  • Windows Forms (klasyczny, najprostszy do startu)

Zrobimy prosty formularz: Imię + Nazwisko → Wyślij → wynik. Logika łączenia napisów będzie w osobnej klasie.


Dlaczego to robimy?

  • Porządek: UI (przyciski, pola) nie miesza się z obliczeniami/zasadami.
  • Czytelność: łatwiej znaleźć miejsce, gdzie zmienić wygląd lub regułę działania.
  • Testowalność: logikę można sprawdzić bez uruchamiania okienka.
  • Skalowalność: rośnie projekt, a Ty dodajesz kolejne funkcje do klasy logiki.

Co oddzielamy?

  • UI (widok) – XAML (WPF) albo Designer (WinForms).
  • Klej (code-behind) – metody obsługi przycisków i wymiana danych z UI.
  • Logika – zwykła klasa C# (Formater), nie zna kontrolek.

Mechanika łączenia (ogólny schemat)

  1. UI nadaje kontrolkom nazwy (WPF: x:Name, WinForms: Name).
  2. Przycisk wywołuje zdarzenie (WPF: Click, WinForms: Click).
  3. W code-behind odczytujesz pola → wołasz metodę logiki → ustawiasz wynik na etykiecie.
  4. Logika to osobna klasa z metodami – przyjmuje dane, zwraca tekst.

CZĘŚĆ A – WPF

Struktura projektu (drzewo)

MojaAppWpf
 ┣ App.xaml
 ┣ App.xaml.cs
 ┣ MainWindow.xaml            ← UI (widok)
 ┣ MainWindow.xaml.cs         ← code-behind (zdarzenia, odczyt/zapis)
 ┣ Logika
 ┃   ┗ Formater.cs           ← czysta logika

Folder Logika ułatwia porządek (można też bez folderu).


Krok po kroku (Visual Studio – interfejs PL)

  1. Nowy projekt → „Aplikacja WPF (.NET 6/7/8)” → nazwa: MojaAppWpf.
  2. PPM na projekt → DodajNowy folderLogika.
  3. PPM na folder LogikaDodajKlasa…Formater.cs.
  4. Otwórz MainWindow.xaml i wklej XAML poniżej.
  5. Otwórz MainWindow.xaml.cs i wklej code-behind.
  6. Zbuduj i uruchom.

1) MainWindow.xaml (UI)

<Window x:Class="MojaAppWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Formularz" Height="280" Width="420">
    <StackPanel Margin="20">
        <TextBox x:Name="PoleImie"  Margin="0,0,0,8"/>
        <TextBox x:Name="PoleNazwisko" Margin="0,0,0,8"/>
        <Button Content="Wyślij" Click="PrzyciskWyslij_Click" Margin="0,0,0,8"/>
        <TextBlock x:Name="WynikLabel" FontWeight="Bold" Text="Tu pojawi się wynik"/>
    </StackPanel>
</Window>

Wyjaśnienia (linia po linii):

  • <Window …> – główne okno aplikacji WPF.
  • x:Class="MojaAppWpf.MainWindow" – wiąże ten XAML z klasą MainWindow w .xaml.cs.
  • xmlns=… i xmlns:x=… – standardowe przestrzenie nazw XAML (potrzebne do składni).
  • Title/Height/Width – tytuł i rozmiar okna.
  • <StackPanel> – układ pionowy; elementy są jeden pod drugim.
  • Margin – odstępy.
  • <TextBox x:Name="PoleImie" …/> – pole do wpisania imienia; x:Name nadaje nazwę widoczną w C#.
  • <TextBox x:Name="PoleNazwisko" …/> – pole na nazwisko.
  • <Button Content="Wyślij" Click="PrzyciskWyslij_Click"/> – przycisk; po kliknięciu wykona metodę w C#.
  • <TextBlock x:Name="WynikLabel" …/> – napis do pokazania wyniku.

2) MainWindow.xaml.cs (code-behind)

using MojaAppWpf.Logika;          // pozwala użyć klasy Formater
using System.Windows;              // typy WPF (Window, RoutedEventArgs)

namespace MojaAppWpf;

public partial class MainWindow : Window
{
    private readonly Formater _formater = new(); // obiekt logiki

    public MainWindow()
    {
        InitializeComponent(); // tworzy kontrolki z XAML i łączy je z polami (PoleImie, WynikLabel)
    }

    private void PrzyciskWyslij_Click(object sender, RoutedEventArgs e)
    {
        string imie = PoleImie.Text;         // odczyt z TextBox
        string nazwisko = PoleNazwisko.Text;

        string wynik = _formater.PolaczImieNazwisko(imie, nazwisko); // logika w osobnej klasie

        WynikLabel.Text = wynik;             // wyświetlenie wyniku w UI
    }
}

Wyjaśnienia:

  • using MojaAppWpf.Logika; – import przestrzeni nazw z klasą Formater.
  • partial class MainWindow – definicja klasy jest „w dwóch plikach” (XAML + CS).
  • InitializeComponent() – kluczowa metoda, która „wczytuje” XAML.
  • PrzyciskWyslij_Click – metoda wywoływana po kliknięciu.
  • UI ↔ logika: odczyt → _formater → ustawienie wyniku.

3) Logika/Formater.cs (czysta logika)

namespace MojaAppWpf.Logika;

public class Formater
{
    public string PolaczImieNazwisko(string imie, string nazwisko)
    {
        if (string.IsNullOrWhiteSpace(imie) && string.IsNullOrWhiteSpace(nazwisko))
            return "Proszę podać imię i nazwisko.";

        if (string.IsNullOrWhiteSpace(imie))
            return $"Nazwisko: {nazwisko}";

        if (string.IsNullOrWhiteSpace(nazwisko))
            return $"Imię: {imie}";

        return $"{imie} {nazwisko}";
    }
}

Wyjaśnienia:

  • Zwykła klasa C#, bez żadnych odniesień do kontrolek.
  • Metoda przyjmuje dwa napisy i zwraca gotowy komunikat.
  • Walidacja pustych wartości – przyjazne komunikaty dla użytkownika.

CZĘŚĆ B – Windows Forms

Struktura projektu (drzewo)

MojaAppWinForms
 ┣ Program.cs                 ← punkt startowy aplikacji
 ┣ Form1.cs                   ← code-behind okna
 ┣ Form1.Designer.cs          ← plik generowany przez projektant (UI)
 ┣ Logika
 ┃   ┗ Formater.cs           ← czysta logika

W WinForms UI budujesz w Designerze przeciągając kontrolki. Kod XAML nie występuje.


Krok po kroku (Visual Studio – interfejs PL)

  1. Nowy projekt → „Aplikacja Windows Forms (.NET 6/7/8)” → MojaAppWinForms.
  2. Na formularz Form1 przeciągnij z Toolbox: TextBox (Imię), TextBox (Nazwisko), Button (Wyślij), Label (wynik). Ustaw Name: txtImie, txtNazwisko, btnWyslij, lblWynik.
  3. Kliknij dwa razy przycisk „Wyślij”, aby Visual Studio wygenerowało zdarzenie btnWyslij_Click w Form1.cs.
  4. PPM na projekt → DodajNowy folderLogika. W nim DodajKlasa…Formater.cs.
  5. Wklej kody poniżej.

1) Form1.cs (code-behind formularza)

Ten plik ma już sekcję partial class Form1 : Form. Poniżej pokazuję fragmenty, które są istotne.

using MojaAppWinForms.Logika; // pozwala odwołać się do Formater

namespace MojaAppWinForms;

public partial class Form1 : Form
{
    private readonly Formater _formater = new(); // obiekt logiki

    public Form1()
    {
        InitializeComponent(); // tworzy kontrolki z Designera i łączy je z polami (txtImie, lblWynik)
    }

    private void btnWyslij_Click(object sender, EventArgs e)
    {
        string imie = txtImie.Text;           // odczyt z TextBox
        string nazwisko = txtNazwisko.Text;

        string wynik = _formater.PolaczImieNazwisko(imie, nazwisko); // logika w osobnej klasie

        lblWynik.Text = wynik;                // wyświetlenie wyniku w UI
    }
}

Wyjaśnienia:

  • InitializeComponent() – generowany kod, który tworzy i rozmieszcza kontrolki (w Form1.Designer.cs).
  • btnWyslij_Click – metoda wywoływana po kliknięciu przycisku (Designer sam podłączy zdarzenie).

2) Form1.Designer.cs (UI – generowany)

Visual Studio wygeneruje to za Ciebie, gdy dodasz kontrolki w Designerze. Najważniejsze, żeby ustawić Name kontrolek tak, jak używasz w kodzie (txtImie, txtNazwisko, btnWyslij, lblWynik).


3) Logika/Formater.cs (wspólna z WPF)

namespace MojaAppWinForms.Logika;

public class Formater
{
    public string PolaczImieNazwisko(string imie, string nazwisko)
    {
        if (string.IsNullOrWhiteSpace(imie) && string.IsNullOrWhiteSpace(nazwisko))
            return "Proszę podać imię i nazwisko.";

        if (string.IsNullOrWhiteSpace(imie))
            return $"Nazwisko: {nazwisko}";

        if (string.IsNullOrWhiteSpace(nazwisko))
            return $"Imię: {imie}";

        return $"{imie} {nazwisko}";
    }
}

Wyjaśnienia: identyczne jak w WPF – i o to chodzi. Logika jest uniwersalna.


Porównanie: WPF vs Windows Forms (w skrócie)

ElementWPFWinForms
UIXAMLDesigner (kod generowany w .Designer.cs)
EtykietaTextBlock/LabelLabel
Pole tekstoweTextBoxTextBox
Zdarzenie przyciskuClickClick
Łączenie UI↔kodx:Name, InitializeComponent()Name, InitializeComponent()
Logikaosobna klasaosobna klasa

Najczęstsze pytania (FAQ)

Czy muszę mieć folder Logika?
Nie. To dobra praktyka. Możesz dać Formater.cs obok okna.

Czy mogę użyć tej samej klasy logiki w obu projektach?
Tak – najlepiej wynieść ją do biblioteki klas (projekt .csproj) i odwołać się z WPF/WinForms.

Co jeśli chcę więcej akcji (np. „Nazwisko, Imię”)?
Dodaj kolejną metodę w Formater (np. FormatujNazwiskoImie) i drugi przycisk, który ją wywoła.


Ćwiczenia:

  1. Dodaj drugi przycisk „Wyczyść”, który czyści oba pola i etykietę.
  2. Dodaj walidację: Imię min. 2 znaki; jeśli krótsze – pokaż komunikat.
  3. Dodaj metodę FormatujNazwiskoImie (wynik: Kowalski, Jan) i podłącz do nowego przycisku.
  4. (Ambitne) Przenieś klasę Formater do osobnej biblioteki klas i podłącz do projektu WPF/WinForms.

Podsumowanie (do zapamiętania)

  • UI ≠ logika – rozdzielaj od początku.
  • WPF i WinForms różnią się sposobem tworzenia UI, ale schemat pracy jest ten sam.
  • InitializeComponent() łączy UI z kodem.
  • Zdarzenia (Click) to „most” między UI a logiką.
  • Osobna klasa z metodami = porządek, testowalność, skalowalność.

Gotowe. Materiał możesz wkleić na stronę jako lekcję. Jeśli chcesz, dorzucę jeszcze wersję PDF/DOCX z tym materiałem i obrazkami ekranu z Visual Studio (krok po kroku).