Wiele okien – dodawanie i wymiana danych

Nauczysz się dodawać do projektu kolejne okna, otwierać je na dwa różne sposoby oraz przekazywać dane w obie strony – z okna głównego do nowego i z powrotem. Wszystko bez MVVM, prostym stylem code-behind.

Nowe okno Show / ShowDialog Przekazywanie danych DialogResult
1

Po co wiele okien?

Do tej pory Twoje aplikacje miały jedno okno – MainWindow. W prawdziwych programach często potrzeba więcej okien: okno logowania, okno „O programie”, formularz dodawania nowego rekordu, okno ustawień, okno ze szczegółami wybranego elementu.

Analogia

Pomyśl o dowolnym programie, którego używasz na co dzień – np. edytorze zdjęć. Główne okno pokazuje zdjęcie, ale kliknięcie „Ustawienia” otwiera osobne okienko z opcjami. Kliknięcie „Eksportuj” może otworzyć kolejne okno z wyborem formatu. Każde z tych okien to osobny plik .xaml w Twoim projekcie – dokładnie tak samo jak MainWindow.xaml.

Typowe zastosowania dodatkowych okien:

OknoPrzykład
Formularz dodawania danych„Dodaj nowego ucznia”, „Nowe zamówienie”
Okno szczegółówKliknięcie na wiersz w tabeli pokazuje pełne dane
Okno ustawieńKonfiguracja motywu, języka, opcji
Okno „O programie”Informacje o autorze, wersji
Okno logowaniaEkran przed wejściem do głównej aplikacji
2

Dodawanie nowego okna do projektu

Kliknij prawym na projekt
Dodaj → Nowy element
Okno (WPF)
Nadaj nazwę
Dodaj

W Visual Studio kliknij prawym przyciskiem na nazwę projektu w Eksploratorze rozwiązań → Dodaj → Nowy element… → wybierz szablon „Okno (WPF)” → nadaj nazwę, np. OknoSzczegoly.xamlDodaj.

Visual Studio utworzy dla Ciebie parę plików – dokładnie tak samo jak przy MainWindow:

PlikDo czego służy
OknoSzczegoly.xamlWygląd nowego okna (kontrolki, layout)
OknoSzczegoly.xaml.csKod C# tego okna (logika, obsługa zdarzeń)
Nowe okno to osobna klasa

Tak jak MainWindow jest klasą dziedziczącą po Window, tak samo OknoSzczegoly też jest klasą dziedziczącą po Window. Możesz mieć w projekcie dowolnie wiele takich klas-okien. Każde jest niezależne – ma własne kontrolki, własny kod, własne zdarzenia.

OknoSzczegoly.xaml – wygenerowane automatycznie
<Window x:Class="MojaAplikacja.OknoSzczegoly"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="OknoSzczegoly"
        Height="300"
        Width="400">
    <Grid>

    </Grid>
</Window>
3

Otwieranie okna – Show()

Żeby otworzyć nowe okno z poziomu MainWindow, tworzysz jego obiekt (tak jak każdy inny obiekt w C#) i wywołujesz metodę Show().

MainWindow.xaml – przycisk otwierający okno
<Button Content="Pokaż szczegóły"
        Click="btnPokazSzczegoly_Click" />
MainWindow.xaml.cs – otwieranie okna
private void btnPokazSzczegoly_Click(object sender, RoutedEventArgs e)
{
    // 1. Tworzymy obiekt nowego okna (tak jak każdy inny obiekt)
    OknoSzczegoly okno = new OknoSzczegoly();

    // 2. Pokazujemy je na ekranie
    okno.Show();
}
Okno niemodalne (Show)

Show() otwiera okno niemodalnie – oba okna (główne i nowe) są aktywne jednocześnie. Użytkownik może dowolnie przełączać się między nimi, klikać w oba, przesuwać oba. To jak dwa oddzielne programy działające naraz.

4

Okno modalne – ShowDialog()

Czasem chcesz, żeby użytkownik musiał obsłużyć nowe okno, zanim wróci do reszty aplikacji – np. formularz logowania, okno z pytaniem „czy na pewno usunąć?”. Do tego służy ShowDialog().

Otwieranie okna modalnego
private void btnDodajUcznia_Click(object sender, RoutedEventArgs e)
{
    OknoDodawania okno = new OknoDodawania();
    okno.ShowDialog();

    // Ten kod wykona się DOPIERO po zamknięciu okna "okno"!
    MessageBox.Show("Okno dodawania zostało zamknięte");
}

Show() vs ShowDialog() – porównanie

CechaShow()ShowDialog()
Okno główne dostępne w tym czasie?✅ Tak❌ Nie – zablokowane
Kod po Show()/ShowDialog() czeka?❌ Nie – wykonuje się od razu✅ Tak – czeka na zamknięcie okna
Może zwrócić wynik (true/false)?❌ Nie✅ Tak (DialogResult)
Typowe zastosowaniePanel narzędzi, okno pomocnicze obok głównegoFormularz, okno z pytaniem, logowanie
Kod po ShowDialog() „czeka”

To najważniejsza różnica: linia kodu zaraz po okno.ShowDialog(); nie wykona się, dopóki użytkownik nie zamknie tego okna. Dzięki temu możesz napisać kod, który „na pewno” wykonuje się już po zakończeniu pracy z oknem podrzędnym – bardzo przydatne przy odczytywaniu danych, które użytkownik tam wprowadził (zobacz sekcję 6).

5

Przekazywanie danych DO nowego okna

Często nowe okno potrzebuje jakichś danych z okna głównego – np. okno szczegółów ucznia potrzebuje wiedzieć, którego ucznia pokazać. Są dwa sposoby.

Sposób 1 – przez konstruktor

Najczystszy sposób – dodajesz do klasy okna własny konstruktor, który przyjmuje dane jako parametry.

OknoSzczegoly.xaml.cs – własny konstruktor
public partial class OknoSzczegoly : Window
{
    // Domyślny konstruktor – WYMAGANY, zostaje bez zmian
    public OknoSzczegoly()
    {
        InitializeComponent();
    }

    // Własny, dodatkowy konstruktor – przyjmuje dane
    public OknoSzczegoly(string imie, int wiek)
    {
        InitializeComponent();

        // Od razu wypełniamy kontrolki przekazanymi danymi
        lblImie.Text = $"Imię: {imie}";
        lblWiek.Text = $"Wiek: {wiek} lat";
    }
}
MainWindow.xaml.cs – wywołanie z danymi
private void btnPokazSzczegoly_Click(object sender, RoutedEventArgs e)
{
    // Wywołujemy NASZ konstruktor, podając dane
    OknoSzczegoly okno = new OknoSzczegoly("Anna Kowalska", 17);
    okno.Show();
}
Dwa konstruktory razem – to normalne

Klasa może mieć wiele konstruktorów o różnych parametrach (to się nazywa przeciążanie). Domyślny (bez parametrów) zostaw bez zmian – jest generowany automatycznie i czasem przydatny. Dodaj obok niego własny, z parametrami, którego użyjesz do przekazania danych.

Sposób 2 – przez właściwość publiczną

Alternatywa: zamiast konstruktora, dodajesz publiczną właściwość i ustawiasz ją po utworzeniu okna, ale przed jego pokazaniem.

OknoSzczegoly.xaml.cs – z właściwością publiczną
public partial class OknoSzczegoly : Window
{
    // Publiczna właściwość – dostępna z zewnątrz klasy
    public string ImieUcznia { get; set; }
    public int    WiekUcznia { get; set; }

    public OknoSzczegoly()
    {
        InitializeComponent();
    }

    // Zdarzenie Loaded – wykonuje się TUŻ PRZED wyświetleniem okna,
       gdy właściwości są już ustawione
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        lblImie.Text = $"Imię: {ImieUcznia}";
        lblWiek.Text = $"Wiek: {WiekUcznia} lat";
    }
}
MainWindow.xaml.cs – ustawianie właściwości
private void btnPokazSzczegoly_Click(object sender, RoutedEventArgs e)
{
    OknoSzczegoly okno = new OknoSzczegoly();

    // Ustawiamy właściwości ZANIM pokażemy okno
    okno.ImieUcznia = "Anna Kowalska";
    okno.WiekUcznia = 17;

    okno.Show();
}
Który sposób wybrać?

Konstruktor – gdy dane są zawsze wymagane do sensownego działania okna (bez nich okno „nie ma sensu”). Wymusza podanie danych już przy tworzeniu obiektu – trudniej o pomyłkę.

Właściwość publiczna – gdy dane są opcjonalne albo chcesz mieć elastyczność (np. czasem otwierasz okno puste, czasem z danymi). Pamiętaj tylko, żeby ustawić ją przed wywołaniem Show().

6

Przekazywanie danych Z POWROTEM do okna głównego

To odwrotna sytuacja: użytkownik wypełnia formularz w nowym oknie (np. „Dodaj ucznia”), klika „Zapisz”, i okno główne musi odebrać te dane. Tutaj właśnie przydaje się ShowDialog() z sekcji 4.

Sposób 1 – właściwość publiczna (najprostszy)

OknoDodawania.xaml – prosty formularz
<Window x:Class="MojaAplikacja.OknoDodawania" ... Title="Dodaj ucznia" Width="300" Height="200">
    <StackPanel Margin="15">
        <TextBlock Text="Imię i nazwisko:" />
        <TextBox x:Name="txtImie" Margin="0,4,0,12" />

        <Button Content="Zapisz"
                Click="btnZapisz_Click" />
    </StackPanel>
</Window>
OknoDodawania.xaml.cs – wystawiamy wynik jako właściwość
public partial class OknoDodawania : Window
{
    // Tu "wystawiamy" wynik na zewnątrz
    public string WpisaneImie { get; private set; }

    public OknoDodawania()
    {
        InitializeComponent();
    }

    private void btnZapisz_Click(object sender, RoutedEventArgs e)
    {
        if (string.IsNullOrWhiteSpace(txtImie.Text))
        {
            MessageBox.Show("Podaj imię!");
            return;
        }

        WpisaneImie = txtImie.Text;   // zapisujemy wynik do właściwości
        Close();                     // zamykamy to okno
    }
}
MainWindow.xaml.cs – odczyt danych po zamknięciu okna
private void btnDodajUcznia_Click(object sender, RoutedEventArgs e)
{
    OknoDodawania okno = new OknoDodawania();

    // ShowDialog() CZEKA aż okno się zamknie – dopiero potem lecimy dalej
    okno.ShowDialog();

    // Tutaj okno jest już zamknięte, możemy bezpiecznie odczytać dane
    if (!string.IsNullOrEmpty(okno.WpisaneImie))
    {
        lstUczniowie.Items.Add(okno.WpisaneImie);
    }
}

Sposób 2 – DialogResult (informacja OK/Anuluj)

DialogResult to specjalna właściwość wbudowana w każde okno – mówi „czy użytkownik zatwierdził, czy anulował”. Świetnie współpracuje z właściwościami publicznymi z poprzedniego przykładu.

OknoDodawania.xaml.cs – z DialogResult
public partial class OknoDodawania : Window
{
    public string WpisaneImie { get; private set; }

    public OknoDodawania()
    {
        InitializeComponent();
    }

    private void btnZapisz_Click(object sender, RoutedEventArgs e)
    {
        if (string.IsNullOrWhiteSpace(txtImie.Text))
        {
            MessageBox.Show("Podaj imię!");
            return;
        }

        WpisaneImie = txtImie.Text;
        DialogResult = true;   // ← oznacza "zatwierdzono" i AUTOMATYCZNIE zamyka okno!
    }

    private void btnAnuluj_Click(object sender, RoutedEventArgs e)
    {
        DialogResult = false;   // ← oznacza "anulowano" i zamyka okno
    }
}
MainWindow.xaml.cs – sprawdzanie DialogResult
private void btnDodajUcznia_Click(object sender, RoutedEventArgs e)
{
    OknoDodawania okno = new OknoDodawania();

    // ShowDialog() zwraca bool? – to co ustawiliśmy w DialogResult
    bool? wynik = okno.ShowDialog();

    if (wynik == true)   // użytkownik kliknął "Zapisz", nie "Anuluj"
    {
        lstUczniowie.Items.Add(okno.WpisaneImie);
    }
    // jeśli wynik == false lub null – użytkownik anulował, nic nie robimy
}
DialogResult = true automatycznie zamyka okno!

Ustawienie DialogResult = true; (lub false) automatycznie wywołuje Close() – nie musisz pisać tego osobno. To wygodny skrót, który jednocześnie zamyka okno i „zapamiętuje” czy była to akcja pozytywna, czy negatywna. Działa tylko gdy okno zostało otwarte przez ShowDialog() – przy zwykłym Show() nie ma to sensu (nikt nie czeka na wynik).

MetodaKiedy używać
Sama właściwość publicznaProste okna, gdzie zawsze jest jeden „dobry” wynik
Właściwość + DialogResultFormularze z opcją Anuluj – wzorcowe rozwiązanie w WPF
7

Zamykanie okna z kodu

Każde okno (również MainWindow) ma metodę Close(), którą wywołujesz, żeby je zamknąć programowo – np. po kliknięciu przycisku „Anuluj” albo „Zamknij”.

Zamykanie własnego okna
private void btnZamknij_Click(object sender, RoutedEventArgs e)
{
    Close();   // zamyka OKNO, w którym jest ta metoda
}
Zamknięcie MainWindow zamyka całą aplikację!

Domyślnie WPF kończy działanie programu, gdy zamknięte zostanie główne okno (to, które ustawione jest w App.xaml jako StartupUri). Zamknięcie okna pomocniczego (np. OknoDodawania) nie kończy całej aplikacji – reszta programu działa dalej normalnie.

Zamykanie okna z zewnątrz
// Jeśli mamy referencję do otwartego okna, możemy je zamknąć z innego miejsca
OknoSzczegoly okno = new OknoSzczegoly();
okno.Show();

// ... później, gdzie indziej w kodzie:
okno.Close();
8

Zdarzenia Closing i Closed

Czasem chcesz coś zrobić tuż przed zamknięciem okna (np. zapytać „czy na pewno?”) albo tuż po jego zamknięciu. Do tego służą dwa zdarzenia.

ZdarzenieKiedy się odpalaMożna anulować zamknięcie?
ClosingTuż PRZED zamknięciem okna✅ Tak
ClosedTuż PO zamknięciu okna❌ Nie – za późno
Pytanie o potwierdzenie przy zamykaniu
<!-- XAML: <Window ... Closing="Window_Closing"> -->

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    MessageBoxResult wynik = MessageBox.Show(
        "Czy na pewno chcesz zamknąć okno? Niezapisane zmiany zostaną utracone.",
        "Potwierdzenie",
        MessageBoxButton.YesNo,
        MessageBoxImage.Warning);

    if (wynik == MessageBoxResult.No)
    {
        e.Cancel = true;   // anulujemy zamknięcie okna!
    }
}
e.Cancel = true – blokowanie zamknięcia

W zdarzeniu Closing masz dostęp do e.Cancel. Ustawienie go na true zatrzymuje proces zamykania okna – okno zostaje otwarte, jakby użytkownik nic nie kliknął. To ta sama mechanika co e.Handled przy zdarzeniach myszy w Canvas.

Zdarzenie Closed – sprzątanie po zamknięciu
private void Window_Closed(object sender, EventArgs e)
{
    // Okno jest już zamknięte - tutaj można np. zwolnić zasoby,
       zatrzymać timer, wyrejestrować się z jakiegoś zdarzenia
    _timer?.Stop();
}
9

Dostęp do okna głównego z okna podrzędnego

Czasem okno podrzędne chce bezpośrednio „dotknąć” kontrolki w oknie głównym – np. odświeżyć listę bez czekania na ShowDialog(). Można to zrobić na dwa sposoby.

Sposób 1 – Application.Current.MainWindow

WPF zawsze wie, które okno jest „głównym oknem” aplikacji (to ustawione w App.xaml). Możesz się do niego odwołać z dowolnego miejsca w programie.

Dostęp do MainWindow z dowolnego okna
private void btnOdswiez_Click(object sender, RoutedEventArgs e)
{
    // Rzutujemy Application.Current.MainWindow na nasz konkretny typ
    MainWindow glowne = (MainWindow)Application.Current.MainWindow;

    // Teraz mamy dostęp do jego publicznych metod i kontrolek
    glowne.OdswiezListe();
}
Kontrolki muszą być dostępne (nie private!)

Domyślnie kontrolki z x:Name są dostępne tylko wewnątrz danej klasy okna. Żeby okno podrzędne mogło np. odświeżyć listę w oknie głównym, potrzebujesz w MainWindow publicznej metody (jak OdswiezListe() powyżej), która robi to za nie – zamiast bezpośrednio grzebać w prywatnych kontrolkach z zewnątrz.

Sposób 2 – przekazanie referencji przez konstruktor

Alternatywnie, przy tworzeniu okna podrzędnego możesz od razu przekazać mu referencję this (czyli „samego siebie”) – okno podrzędne zapamiętuje, kto je otworzył.

MainWindow.xaml.cs – otwieranie z przekazaniem „siebie”
private void btnOtworz_Click(object sender, RoutedEventArgs e)
{
    // "this" to referencja do aktualnego okna (MainWindow)
    OknoPodrzedne okno = new OknoPodrzedne(this);
    okno.Show();
}
OknoPodrzedne.xaml.cs – zapamiętanie okna głównego
public partial class OknoPodrzedne : Window
{
    private readonly MainWindow _oknoGlowne;

    public OknoPodrzedne(MainWindow oknoGlowne)
    {
        InitializeComponent();
        _oknoGlowne = oknoGlowne;
    }

    private void btnOdswiez_Click(object sender, RoutedEventArgs e)
    {
        _oknoGlowne.OdswiezListe();
    }
}
10

Kompletny przykład – lista uczniów z dodawaniem

Połączmy wszystko: okno główne z listą, przycisk otwierający formularz modalny, przekazanie danych z powrotem przez DialogResult.

MainWindow.xaml
<Window x:Class="MojaAplikacja.MainWindow" ...
        Title="Lista uczniów" Width="350" Height="400">
    <DockPanel Margin="10">

        <Button DockPanel.Dock="Top"
                Content="➕ Dodaj ucznia"
                Margin="0,0,0,10"
                Click="btnDodaj_Click" />

        <ListBox x:Name="lstUczniowie" />

    </DockPanel>
</Window>
MainWindow.xaml.cs
private void btnDodaj_Click(object sender, RoutedEventArgs e)
{
    OknoDodawania okno = new OknoDodawania();
    bool? wynik = okno.ShowDialog();

    if (wynik == true)
    {
        lstUczniowie.Items.Add(okno.WpisaneImie);
    }
}
OknoDodawania.xaml
<Window x:Class="MojaAplikacja.OknoDodawania" ...
        Title="Dodaj ucznia"
        Width="300" Height="180"
        WindowStartupLocation="CenterOwner"
        ResizeMode="NoResize">
    <StackPanel Margin="15">

        <TextBlock Text="Imię i nazwisko ucznia:" />
        <TextBox x:Name="txtImie" Margin="0,4,0,16" />

        <StackPanel Orientation="Horizontal"
                    HorizontalAlignment="Right">
            <Button Content="Anuluj"
                    Width="80" Margin="0,0,8,0"
                    IsCancel="True"
                    Click="btnAnuluj_Click" />
            <Button Content="Zapisz"
                    Width="80"
                    IsDefault="True"
                    Click="btnZapisz_Click" />
        </StackPanel>

    </StackPanel>
</Window>
OknoDodawania.xaml.cs
public partial class OknoDodawania : Window
{
    public string WpisaneImie { get; private set; }

    public OknoDodawania()
    {
        InitializeComponent();
    }

    private void btnZapisz_Click(object sender, RoutedEventArgs e)
    {
        if (string.IsNullOrWhiteSpace(txtImie.Text))
        {
            MessageBox.Show("Podaj imię i nazwisko!");
            return;
        }

        WpisaneImie = txtImie.Text;
        DialogResult = true;
    }

    private void btnAnuluj_Click(object sender, RoutedEventArgs e)
    {
        DialogResult = false;
    }
}
WindowStartupLocation=”CenterOwner”

Ta właściwość otwiera nowe okno wyśrodkowane względem okna, które je otworzyło (a nie względem całego ekranu). Wygląda to bardziej naturalnie niż okno pojawiające się w przypadkowym miejscu. Działa tylko jeśli okno zostało otwarte z poziomu innego okna (nie przy starcie aplikacji).

11

Częste błędy

❌ Błąd 1: Odczyt danych przed zamknięciem okna (Show zamiast ShowDialog)

❌ Dane jeszcze nie istnieją!

OknoDodawania okno = new OknoDodawania();
okno.Show();  // NIE czeka!

// To wykona się NATYCHMIAST,
// zanim użytkownik cokolwiek wpisze!
lstUczniowie.Items.Add(okno.WpisaneImie);

✅ ShowDialog czeka na zamknięcie

OknoDodawania okno = new OknoDodawania();
bool? wynik = okno.ShowDialog();  // czeka!

if (wynik == true)
    lstUczniowie.Items.Add(okno.WpisaneImie);

❌ Błąd 2: Ustawienie właściwości po Show()/ShowDialog()

❌ Za późno – okno już się otworzyło puste

OknoSzczegoly okno = new OknoSzczegoly();
okno.Show();
okno.ImieUcznia = "Anna"; // za późno!
// Window_Loaded już się wykonał

✅ Ustaw PRZED pokazaniem okna

OknoSzczegoly okno = new OknoSzczegoly();
okno.ImieUcznia = "Anna"; // najpierw dane
okno.Show(); // potem pokazujemy

❌ Błąd 3: Zapomniany domyślny konstruktor

❌ Błąd kompilacji XAML

public partial class OknoSzczegoly : Window
{
    // Tylko własny konstruktor –
    // brak konstruktora bez parametrów!
    public OknoSzczegoly(string imie)
    {
        InitializeComponent();
    }
}
// Visual Studio / XAML designer
// oczekuje domyślnego konstruktora!

✅ Zostaw oba konstruktory

public partial class OknoSzczegoly : Window
{
    public OknoSzczegoly() // domyślny – zostaw!
    {
        InitializeComponent();
    }

    public OknoSzczegoly(string imie) : this()
    {
        lblImie.Text = imie;
    }
}

❌ Błąd 4: DialogResult ustawione bez ShowDialog()

❌ Wyjątek w czasie działania

// W MainWindow:
OknoDodawania okno = new OknoDodawania();
okno.Show(); // Show(), nie ShowDialog()!

// W OknoDodawania, przy kliknięciu Zapisz:
DialogResult = true;
// Wyjątek! DialogResult działa TYLKO
// z ShowDialog()

✅ DialogResult tylko z ShowDialog

// Jeśli chcesz używać DialogResult,
// zawsze otwieraj okno przez:
okno.ShowDialog();
12

Zadania do wykonania

Zadanie 1 łatwe

Dodaj do projektu drugie okno o nazwie OknoInfo zawierające TextBlock z tekstem „Aplikacja stworzona przez [Twoje imię]” i przycisk „Zamknij”, który zamyka to okno. W MainWindow dodaj przycisk „O programie” otwierający OknoInfo przez Show(). Sprawdź, że możesz mieć oba okna otwarte jednocześnie.

Zadanie 2 łatwe

Zmień okno z zadania 1 tak, żeby otwierało się przez ShowDialog(). Dodaj do MainWindow zdarzenie Closing, które pyta „Czy na pewno chcesz zamknąć aplikację?” i pozwala anulować zamknięcie (e.Cancel = true).

Zadanie 3 średnie

Stwórz okno OknoKalkulatora przyjmujące przez konstruktor dwie liczby (int a, int b). Okno wyświetla wynik dodawania, odejmowania, mnożenia i dzielenia tych liczb w czterech TextBlockach. W MainWindow dodaj dwa pola TextBox na liczby i przycisk „Oblicz”, który otwiera OknoKalkulatora z wpisanymi wartościami.

Zadanie 4 średnie

Zbuduj kompletną listę produktów (jak w przykładzie z sekcji 10), ale rozbuduj formularz dodawania o dwa pola: nazwę produktu (TextBox) i cenę (TextBox z walidacją – tylko liczby, PreviewTextInput). Użyj DialogResult i właściwości publicznych NazwaProduktu oraz CenaProduktu. W ListBox wyświetlaj produkty w formacie „Nazwa – 12.50 zł”. Dodaj też przycisk „Usuń zaznaczony”, który usuwa wybrany element z listy w MainWindow (bez otwierania żadnego okna).