Przejdź do treści
School IT / Przestrzeń do nauki

Wiele okien

Formularz do dodawania danych nie musi zajmować miejsca w głównym oknie. Lepiej otworzyć go w osobnym, małym oknie z przyciskami OK i Anuluj — tak działa dodawanie kontaktu w telefonie czy właściwości pliku w Windows. W tej lekcji dodasz do projektu własne okna, przekażesz do nich dane, odbierzesz wynik i poznasz różnicę między oknem, które blokuje resztę programu, a takim, które działa obok.

XAML C# Window ShowDialog DialogResult Owner Show 90 min
CEL LEKCJI

Czego się dziś nauczysz

  • Dodasz do projektu nowe okno i otworzysz je z kodu.
  • Otworzysz okno dialogowe, które blokuje główne okno, i odczytasz, czy użytkownik kliknął OK.
  • Przekażesz dane do okna przez konstruktor i odbierzesz wynik przez właściwość.
  • Otworzysz okno działające obok głównego i będziesz je odświeżać.
  • Ustawisz właściciela okna i jego położenie.
TEORIA

Nowe okno w projekcie

W Visual Studio: prawy przycisk na projekcie → Dodaj → Okno (WPF)…, nazwa np. OknoUcznia. Powstają dwa pliki — OknoUcznia.xaml i OknoUcznia.xaml.cs — dokładnie jak MainWindow. Nowe okno to klasa, więc otwiera się je, tworząc obiekt:

Lekcja39/
├── App.xaml
├── MainWindow.xamlokno główne (otwierane na starcie)
├── MainWindow.xaml.cs
├── OknoUczestnika.xamlokno dialogowe
├── OknoUczestnika.xaml.cs
├── OknoStatystyk.xamlokno obok głównego
├── OknoStatystyk.xaml.cs
└── Uczestnik.csklasa danych
MainWindow.xaml.cs
OknoUcznia okno = new OknoUcznia();   // obiekt okna — jeszcze niewidoczny
okno.Owner = this;                     // właściciel: główne okno
okno.ShowDialog();                     // pokazanie

Owner łączy okna: okno podrzędne jest zawsze nad właścicielem, minimalizuje się i zamyka razem z nim, a przy WindowStartupLocation="CenterOwner" pojawia się na jego środku. ShowInTaskbar="False" sprawia, że okno nie ma osobnego przycisku na pasku zadań.

TEORIA

ShowDialog i DialogResult

ShowDialog otwiera okno modalne: dopóki jest otwarte, okna właściciela nie da się kliknąć, a kod w metodzie, która je otworzyła, czeka. Działa to tak jak MessageBox.Show z lekcji 8 i OpenFileDialog z lekcji 38. Wynik to bool?:

OknoUcznia.xaml
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
    <Button Content="OK" Width="80" IsDefault="True" Click="Ok_Click"/>
    <Button Content="Anuluj" Width="80" Margin="8,0,0,0" IsCancel="True"/>
</StackPanel>
OknoUcznia.xaml.cs
private void Ok_Click(object sender, RoutedEventArgs e)
{
    if (Nazwisko.Text.Trim() == "")
    {
        Blad.Text = "Podaj nazwisko.";
        return;                  // okno zostaje otwarte
    }
    DialogResult = true;         // zamyka okno; ShowDialog zwróci true
}
MainWindow.xaml.cs
if (okno.ShowDialog() == true)
{
    // użytkownik kliknął OK i dane były poprawne
}
// false: Anuluj, Esc albo krzyżyk

Ustawienie DialogResult w oknie modalnym od razu je zamyka. Przycisk z IsCancel="True" (lekcja 4) robi to sam — ustawia DialogResult = false po kliknięciu lub Esc — więc nie potrzebuje żadnej metody.

TEORIA

Przekazywanie danych

Do okna — przez parametr konstruktora. Klasa okna może mieć kilka konstruktorów: pusty do dodawania i z parametrem do edycji. Z okna — przez publiczną właściwość, którą okno ustawia przed zamknięciem:

OknoUcznia.xaml.cs
public partial class OknoUcznia : Window
{
    // odczyt z zewnątrz, zapis tylko w tej klasie
    public Uczen? Wynik { get; private set; }

    public OknoUcznia()                 // dodawanie
    {
        InitializeComponent();
    }

    public OknoUcznia(Uczen u)          // edycja: pola wypełnione danymi ucznia
    {
        InitializeComponent();          // najpierw kontrolki, potem ich wypełnianie
        Nazwisko.Text = u.Nazwisko;
    }

    private void Ok_Click(object sender, RoutedEventArgs e)
    {
        Wynik = new Uczen(Nazwisko.Text.Trim());
        DialogResult = true;
    }
}
MainWindow.xaml.cs
OknoUcznia okno = new OknoUcznia(wybrany);   // dane do okna
okno.Owner = this;
if (okno.ShowDialog() == true && okno.Wynik != null)
{
    uczniowie.Add(okno.Wynik);                // dane z okna
}

private set oznacza, że właściwość można czytać spoza klasy, ale zmieniać tylko w niej — główne okno nie zepsuje wyniku przez pomyłkę. Okno zwraca nowy obiekt zamiast zmieniać przekazany: gdy użytkownik kliknie Anuluj, oryginał zostaje nietknięty.

Zamknięte okno nadal istnieje

Po ShowDialog obiekt okno wciąż jest w pamięci — dlatego można z niego odczytać Wynik i treść pól. Nie da się go już jednak pokazać ponownie: na nowe otwarcie potrzeba nowego obiektu.

TEORIA

Show — okno obok

Show otwiera okno niemodalne: kod nie czeka, oba okna działają naraz, można klikać w każde. Nadaje się na podglądy, statystyki, przyborniki. Główne okno trzyma odwołanie w polu, żeby móc je odświeżać, i dowiaduje się o zamknięciu ze zdarzenia Closed:

MainWindow.xaml.cs
private OknoPodgladu? podglad = null;

private void Podglad_Click(object sender, RoutedEventArgs e)
{
    if (podglad != null)
    {
        podglad.Activate();               // już otwarte — tylko na wierzch
        return;
    }
    podglad = new OknoPodgladu();
    podglad.Owner = this;
    podglad.Closed += Podglad_Closed;     // jak w lekcji 25: zdarzenie podpięte w kodzie
    podglad.Show();                       // nie czeka
}

private void Podglad_Closed(object? sender, EventArgs e)
{
    podglad = null;
}
ShowDialog()Show()
okno właścicielazablokowanedziała normalnie
kod po wywołaniuczeka na zamknięciewykonuje się od razu
wynikbool? z DialogResultbrak (void)
typowe użycieformularz OK/Anuluj, pytaniepodgląd, statystyki, narzędzia

Położenie okna ustawia się przez Left i Top (piksele od lewego górnego rogu ekranu). Left + Width głównego okna to jego prawa krawędź — tam można postawić okno obok.

PRZYKŁAD

Kółko robotyki — lista uczestników

Opiekun kółka prowadzi listę uczestników. Dodaj… i Edytuj… (także dwuklik na liście) otwierają to samo okno dialogowe — puste albo wypełnione danymi. Statystyki otwierają okno obok głównego, które odświeża się przy każdej zmianie listy.

Kółko robotyki — uczestnicy–□✕
Kamińska Zuzanna (2TI) — średni
Lewandowski Filip (1TI) — początkujący
Mazur Oskar (4TI) — zaawansowany
Uczestników: 3. Dwuklik na liście — edycja.
Dodaj…
Edytuj…
Usuń
Statystyki
Edycja — Filip Lewandowski✕
Imię:
Filip
Nazwisko:
Lewandowski
Klasa:
1TI ▾
początkujący
średni
zaawansowany
OKAnuluj
Statystyki kółka✕
Uczestników: 3
początkujący: 1
średni: 1
zaawansowani: 1
Klasy: 2TI 1TI 4TI

okno edycji (modalne) zasłania główne i je blokuje; statystyki (niemodalne) działają niezależnie — po kliknięciu OK pokażą „średni: 2”

Uczestnik.cs — pełny plik
namespace Lekcja39;

// Uczestnik szkolnego kółka robotyki
public class Uczestnik
{
    public string Imie { get; set; }
    public string Nazwisko { get; set; }
    public string Klasa { get; set; }
    public string Poziom { get; set; }      // "początkujący", "średni" albo "zaawansowany"

    public Uczestnik(string imie, string nazwisko, string klasa, string poziom)
    {
        Imie = imie;
        Nazwisko = nazwisko;
        Klasa = klasa;
        Poziom = poziom;
    }

    public override string ToString()
    {
        return $"{Nazwisko} {Imie} ({Klasa}) — {Poziom}";
    }
}
OknoUczestnika.xaml — pełny plik
<Window x:Class="Lekcja39.OknoUczestnika"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Uczestnik" Width="300" SizeToContent="Height"
        ResizeMode="NoResize" WindowStartupLocation="CenterOwner" ShowInTaskbar="False">

    <StackPanel Margin="15">
        <Label Content="_Imię:" Padding="0,0,0,2" Target="{Binding ElementName=Imie}"/>
        <TextBox x:Name="Imie"/>

        <Label Content="_Nazwisko:" Padding="0,8,0,2" Target="{Binding ElementName=Nazwisko}"/>
        <TextBox x:Name="Nazwisko"/>

        <Label Content="_Klasa:" Padding="0,8,0,2" Target="{Binding ElementName=Klasa}"/>
        <ComboBox x:Name="Klasa" IsEditable="True" Width="100" HorizontalAlignment="Left">
            <ComboBoxItem Content="1TI"/>
            <ComboBoxItem Content="2TI"/>
            <ComboBoxItem Content="3TI"/>
            <ComboBoxItem Content="4TI"/>
            <ComboBoxItem Content="5TI"/>
        </ComboBox>

        <TextBlock Text="Poziom:" Margin="0,8,0,2"/>
        <StackPanel x:Name="Poziomy">
            <RadioButton Content="początkujący" IsChecked="True"/>
            <RadioButton Content="średni"/>
            <RadioButton Content="zaawansowany"/>
        </StackPanel>

        <TextBlock x:Name="Blad" Foreground="Firebrick" TextWrapping="Wrap" Margin="0,8,0,0"/>

        <!-- IsDefault: Enter, IsCancel: Esc i zamknięcie z DialogResult = false -->
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
            <Button Content="OK" Width="80" IsDefault="True" Click="Ok_Click"/>
            <Button Content="Anuluj" Width="80" Margin="8,0,0,0" IsCancel="True"/>
        </StackPanel>
    </StackPanel>
</Window>
OknoUczestnika.xaml.cs — pełny plik
using System.Windows;
using System.Windows.Controls;

namespace Lekcja39;

public partial class OknoUczestnika : Window
{
    // Wynik okna: główne okno odczyta go po zamknięciu, gdy DialogResult == true
    public Uczestnik? Wynik { get; private set; }

    // Dodawanie: okno z pustymi polami
    public OknoUczestnika()
    {
        InitializeComponent();
        Title = "Nowy uczestnik";
        Imie.Focus();
    }

    // Edycja: drugi konstruktor dostaje istniejącego uczestnika i wypełnia pola
    public OknoUczestnika(Uczestnik u)
    {
        InitializeComponent();
        Title = $"Edycja — {u.Imie} {u.Nazwisko}";

        Imie.Text = u.Imie;
        Nazwisko.Text = u.Nazwisko;
        Klasa.Text = u.Klasa;
        foreach (RadioButton r in Poziomy.Children)
        {
            r.IsChecked = $"{r.Content}" == u.Poziom;
        }
        Imie.Focus();
    }

    private void Ok_Click(object sender, RoutedEventArgs e)
    {
        string imie = Imie.Text.Trim();
        string nazwisko = Nazwisko.Text.Trim();
        string klasa = Klasa.Text.Trim().ToUpper();

        if (imie == "" || nazwisko == "")
        {
            Blad.Text = "Podaj imię i nazwisko.";
            return;
        }
        if (klasa.Length < 2 || !char.IsDigit(klasa[0]))
        {
            Blad.Text = "Klasa musi zaczynać się cyfrą, np. 2TI.";
            return;
        }

        string poziom = "";
        foreach (RadioButton r in Poziomy.Children)
        {
            if (r.IsChecked == true)
            {
                poziom = $"{r.Content}";
            }
        }

        Wynik = new Uczestnik(imie, nazwisko, klasa, poziom);
        DialogResult = true;        // zamyka okno; ShowDialog zwróci true
    }
}
OknoStatystyk.xaml — pełny plik
<Window x:Class="Lekcja39.OknoStatystyk"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Statystyki kółka" Width="280" SizeToContent="Height"
        ResizeMode="NoResize" ShowInTaskbar="False">

    <StackPanel Margin="15">
        <TextBlock x:Name="Razem" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>

        <TextBlock x:Name="OpisPoczatkujacy"/>
        <ProgressBar x:Name="PasekPoczatkujacy" Height="12" Margin="0,2,0,8"/>
        <TextBlock x:Name="OpisSredni"/>
        <ProgressBar x:Name="PasekSredni" Height="12" Margin="0,2,0,8"/>
        <TextBlock x:Name="OpisZaawansowany"/>
        <ProgressBar x:Name="PasekZaawansowany" Height="12" Margin="0,2,0,8"/>

        <TextBlock x:Name="Klasy" TextWrapping="Wrap" Foreground="Gray" Margin="0,4,0,0"/>
    </StackPanel>
</Window>
OknoStatystyk.xaml.cs — pełny plik
using System.Windows;
using System.Windows.Controls;

namespace Lekcja39;

// Okno niemodalne: działa obok głównego, główne okno odświeża je po każdej zmianie
public partial class OknoStatystyk : Window
{
    public OknoStatystyk()
    {
        InitializeComponent();
    }

    public void Odswiez(List<Uczestnik> uczestnicy)
    {
        int poczatkujacy = 0;
        int sredni = 0;
        int zaawansowani = 0;
        string klasy = "";

        foreach (Uczestnik u in uczestnicy)
        {
            if (u.Poziom == "początkujący")
            {
                poczatkujacy++;
            }
            else if (u.Poziom == "średni")
            {
                sredni++;
            }
            else
            {
                zaawansowani++;
            }

            if (!klasy.Contains(u.Klasa))
            {
                klasy += u.Klasa + " ";
            }
        }

        int razem = uczestnicy.Count;
        Razem.Text = $"Uczestników: {razem}";
        Ustaw(OpisPoczatkujacy, PasekPoczatkujacy, "początkujący", poczatkujacy, razem);
        Ustaw(OpisSredni, PasekSredni, "średni", sredni, razem);
        Ustaw(OpisZaawansowany, PasekZaawansowany, "zaawansowani", zaawansowani, razem);
        Klasy.Text = $"Klasy: {klasy}";
    }

    private void Ustaw(TextBlock opis, ProgressBar pasek, string nazwa, int ile, int razem)
    {
        opis.Text = $"{nazwa}: {ile}";
        pasek.Maximum = razem == 0 ? 1 : razem;
        pasek.Value = ile;
    }
}
MainWindow.xaml — pełny plik
<Window x:Class="Lekcja39.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Kółko robotyki — uczestnicy" Width="520" Height="380" MinWidth="420"
        WindowStartupLocation="CenterScreen">

    <DockPanel Margin="10">
        <StackPanel DockPanel.Dock="Right" Width="120" Margin="10,0,0,0">
            <Button Content="Dodaj…" Height="30" Click="Dodaj_Click"/>
            <Button x:Name="Edytuj" Content="Edytuj…" Height="30" Margin="0,6,0,0"
                    Click="Edytuj_Click"/>
            <Button x:Name="Usun" Content="Usuń" Height="30" Margin="0,6,0,0"
                    Click="Usun_Click"/>
            <Button x:Name="Statystyki" Content="Statystyki" Height="30" Margin="0,24,0,0"
                    Click="Statystyki_Click"/>
        </StackPanel>

        <TextBlock x:Name="Info" DockPanel.Dock="Bottom" Foreground="Gray" Margin="0,6,0,0"/>

        <ListBox x:Name="Lista" SelectionChanged="Lista_SelectionChanged"
                 MouseDoubleClick="Lista_MouseDoubleClick"/>
    </DockPanel>
</Window>
MainWindow.xaml.cs — pełny plik
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Lekcja39;

public partial class MainWindow : Window
{
    private List<Uczestnik> uczestnicy = new List<Uczestnik>();
    private OknoStatystyk? statystyki = null;     // null, gdy okno statystyk jest zamknięte

    public MainWindow()
    {
        InitializeComponent();

        uczestnicy.Add(new Uczestnik("Zuzanna", "Kamińska", "2TI", "średni"));
        uczestnicy.Add(new Uczestnik("Filip", "Lewandowski", "1TI", "początkujący"));
        uczestnicy.Add(new Uczestnik("Oskar", "Mazur", "4TI", "zaawansowany"));
        PokazListe(null);
    }

    // ---------- okno modalne: ShowDialog ----------

    private void Dodaj_Click(object sender, RoutedEventArgs e)
    {
        OknoUczestnika okno = new OknoUczestnika();
        okno.Owner = this;                // okno nad głównym, wyśrodkowane względem niego

        if (okno.ShowDialog() == true && okno.Wynik != null)
        {
            uczestnicy.Add(okno.Wynik);
            PokazListe(okno.Wynik);
        }
    }

    private void Edytuj_Click(object sender, RoutedEventArgs e)
    {
        Uczestnik? u = Lista.SelectedItem as Uczestnik;
        if (u == null)
        {
            return;
        }

        OknoUczestnika okno = new OknoUczestnika(u);   // przekazanie danych do okna
        okno.Owner = this;

        if (okno.ShowDialog() == true && okno.Wynik != null)
        {
            // zastępujemy starego uczestnika nowym obiektem w tym samym miejscu listy
            int nr = uczestnicy.IndexOf(u);
            uczestnicy[nr] = okno.Wynik;
            PokazListe(okno.Wynik);
        }
    }

    private void Lista_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
        Edytuj_Click(sender, e);
    }

    private void Usun_Click(object sender, RoutedEventArgs e)
    {
        Uczestnik? u = Lista.SelectedItem as Uczestnik;
        if (u == null)
        {
            return;
        }

        MessageBoxResult odp = MessageBox.Show($"Usunąć: {u.Imie} {u.Nazwisko}?", "Usuwanie",
            MessageBoxButton.YesNo, MessageBoxImage.Question);
        if (odp == MessageBoxResult.Yes)
        {
            uczestnicy.Remove(u);
            PokazListe(null);
        }
    }

    // ---------- okno niemodalne: Show ----------

    private void Statystyki_Click(object sender, RoutedEventArgs e)
    {
        if (statystyki != null)
        {
            statystyki.Activate();       // już otwarte — tylko wysuwamy je na wierzch
            return;
        }

        statystyki = new OknoStatystyk();
        statystyki.Owner = this;
        statystyki.Left = Left + Width + 10;    // obok głównego okna
        statystyki.Top = Top;
        statystyki.Closed += Statystyki_Closed;
        statystyki.Odswiez(uczestnicy);
        statystyki.Show();                       // nie czeka — oba okna działają naraz
    }

    private void Statystyki_Closed(object? sender, EventArgs e)
    {
        statystyki = null;       // zamkniętego okna nie da się pokazać ponownie
    }

    // ---------- wyświetlanie ----------

    private void Lista_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        UstawPrzyciski();
    }

    private void UstawPrzyciski()
    {
        bool wybrany = Lista.SelectedItem != null;
        Edytuj.IsEnabled = wybrany;
        Usun.IsEnabled = wybrany;
    }

    private void PokazListe(Uczestnik? zaznacz)
    {
        Lista.Items.Clear();
        foreach (Uczestnik u in uczestnicy)
        {
            Lista.Items.Add(u);
        }
        Lista.SelectedItem = zaznacz;
        UstawPrzyciski();

        Info.Text = $"Uczestników: {uczestnicy.Count}. Dwuklik na liście — edycja.";

        // otwarte statystyki odświeżają się razem z listą
        if (statystyki != null)
        {
            statystyki.Odswiez(uczestnicy);
        }
    }
}

Co dzieje się po kolei

  • OknoUczestnika ma dwa konstruktory: pusty (dodawanie) i z parametrem Uczestnik (edycja — wypełnia pola i zaznacza poziom).
  • Ok_Click sprawdza dane. Przy błędzie pokazuje komunikat i okno zostaje otwarte; przy poprawnych danych tworzy nowy obiekt w Wynik i ustawia DialogResult = true.
  • Dodaj_Click i Edytuj_Click czekają na ShowDialog. Dopiero gdy wynik to true, zmieniają listę — przy edycji nowy obiekt zastępuje stary pod tym samym indeksem.
  • Lista_MouseDoubleClick po prostu wywołuje Edytuj_Click — dwuklik działa jak przycisk.
  • Statystyki_Click tworzy okno statystyk tylko raz; kolejne kliknięcie wysuwa je na wierzch. Pole statystyki zeruje się w Statystyki_Closed.
  • PokazListe po każdej zmianie wywołuje publiczną metodę Odswiez okna statystyk, jeśli jest otwarte — tak jedno okno wpływa na drugie.
  • Oba okna podrzędne mają Owner = this, więc zamykają się razem z głównym.

Sprawdź trzy rzeczy

  1. Otwórz edycję i spróbuj kliknąć listę w głównym oknie. Nie da się — okno jest modalne. Statystyki można klikać przy otwartym głównym oknie.
  2. Usuń z Statystyki_Click linię statystyki.Owner = this;. Otwórz statystyki i zamknij główne okno: statystyki zostaną, a program nie zakończy się.
  3. Zamień w Dodaj_Click ShowDialog() na Show(). Program się nie skompiluje: Show niczego nie zwraca, więc nie ma czego porównać z true.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementTypCo robi
new NazwaOkna()obiektTworzy okno (jeszcze niewidoczne).
ShowDialog()bool?Okno modalne; czeka na zamknięcie.
Show()metodaOkno niemodalne; nie czeka.
DialogResultbool?Wynik okna modalnego; ustawienie zamyka okno.
IsDefault, IsCancelboolPrzycisk dla Enter; przycisk dla Esc z DialogResult = false.
OwnerWindowWłaściciel: okno nad nim, zamykane razem z nim.
WindowStartupLocation="CenterOwner"wyliczenieOkno na środku właściciela.
ShowInTaskbarboolCzy okno ma przycisk na pasku zadań.
Activate()metodaWysuwa okno na wierzch.
ClosedzdarzenieOkno zostało zamknięte.
Left, TopdoublePołożenie okna na ekranie.
{ get; private set; }właściwośćOdczyt z zewnątrz, zapis tylko w klasie.
MouseDoubleClickzdarzenieDwukrotne kliknięcie kontrolki.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
DialogResult = true; w oknie otwartym przez Show()InvalidOperationException — DialogResult działa tylko po ShowDialog.
if (okno.ShowDialog())CS0266 — wynik to bool?; porównaj z true.
ponowne Show() zamkniętego oknaInvalidOperationException. Utwórz nowy obiekt.
wypełnianie pól w konstruktorze przed InitializeComponent()NullReferenceException — kontrolki jeszcze nie istnieją.
zmiana przekazanego obiektu w oknie edycjiPo Anuluj zmiany i tak zostają. Zwracaj nowy obiekt.
okno niemodalne bez OwnerZostaje po zamknięciu głównego, a program działa dalej w tle.
wiele kliknięć „Statystyki”Bez pola i sprawdzenia null otwiera się kilka takich samych okien.
ZADANIA

Zadania

Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.

ZAD 1Okno logowania przed programem★☆☆

Po uruchomieniu programu najpierw pojawia się modalne okno logowania (login, hasło w PasswordBox, przyciski Zaloguj i Anuluj). Trzy nieudane próby lub Anuluj zamykają program. Po poprawnym logowaniu główne okno wita użytkownika („Witaj, admin!”) i pokazuje godzinę logowania przekazaną z okna logowania.

pokaż wskazówkę
  • Okno logowania otwórz w konstruktorze głównego okna, a gdy wynik nie jest true, wywołaj Close() głównego okna.
ZAD 2Wybór koloru motywu★☆☆

Główne okno z przyciskiem Zmień kolory…. Okno dialogowe ma trzy suwaki R, G, B, podgląd koloru i przyciski OK/Anuluj. Okno dostaje aktualny kolor tła głównego okna (suwaki startują z jego wartości) i zwraca nowy kolor przez właściwość. Anuluj nie może zmienić koloru.

pokaż wskazówkę
  • Przekazuj i zwracaj Color, a nie pędzel — z koloru odczytasz R, G, B do suwaków.
ZAD 3Zamówienia w pizzerii★★☆

Główne okno: lista zamówień (numer, pizza, rozmiar, dodatki, cena) i suma dnia. Nowe zamówienie… otwiera okno dialogowe z wyborem pizzy (przyciski opcji), rozmiaru (lista) i dodatków (pola wyboru) z ceną liczoną na bieżąco. Szczegóły otwiera okno niemodalne z pełnym opisem zaznaczonego zamówienia, które zmienia się przy zmianie zaznaczenia na liście.

pokaż wskazówkę
  • Klasa Zamowienie z metodą liczącą cenę. Okno szczegółów ma publiczną metodę Pokaz(Zamowienie?), wywoływaną z SelectionChanged listy.
ZAD 4Kreator ustawień gry★★☆

Gra z lekcji 31 dostaje okno ustawień otwierane przed startem i z menu: poziom trudności, liczba żyć, prędkość, imię gracza. Ustawienia przekazywane są obiektem klasy Ustawienia — okno dostaje kopię bieżących i zwraca nowe. Po grze okno modalne „Koniec gry” pokazuje wynik i pyta „Zagrać jeszcze raz?” (przyciski Tak/Nie), a główne okno działa zależnie od odpowiedzi.

pokaż wskazówkę
  • Na czas otwartego okna zatrzymaj DispatcherTimer gry i wznów go po zamknięciu.
ZAD 5Tablica wyników na drugi monitor★★☆

Połącz z lekcją 37: panel sędziego otwiera niemodalne okno „Tablica” z samą tablicą wyników w Viewbox (do wyświetlenia na rzutniku), bez ramki okna (WindowStyle="None"), zamykane klawiszem Esc. Każdy punkt w panelu sędziego od razu zmienia tablicę. W panelu przycisk Tablica zmienia napis na „Zamknij tablicę”, gdy tablica jest otwarta.

pokaż wskazówkę
  • Okno tablicy ma metodę Pokaz(Mecz). Zamknięcie z Esc: KeyDown okna tablicy i Close(); panel dowie się przez Closed.
ZAD 6Wypożyczalnia sprzętu narciarskiego★★★

Główne okno: magazyn (lista sprzętu z ceną za dzień i stanem: wolny/wypożyczony) i lista aktywnych wypożyczeń. Nowe wypożyczenie… otwiera kreator w oknie modalnym z zakładkami (lekcja 34): klient, sprzęt (tylko wolny, wiele pozycji), daty (lekcja 32) z ceną. Zwrot… otwiera okno z wyliczeniem dopłaty za spóźnienie. Okno niemodalne „Kasa dnia” pokazuje sumę wpłat i aktualizuje się na bieżąco. Dane zapisywane są w plikach CSV (lekcja 38) przy zamknięciu programu i wczytywane przy starcie.

pokaż wskazówkę
  • Okno kreatora dostaje listę wolnego sprzętu jako parametr konstruktora i zwraca obiekt Wypozyczenie — nie zmienia magazynu samo. Stan sprzętu zmienia dopiero główne okno po DialogResult == true.
PODSUMOWANIE

Co trzeba zapamiętać

  • Nowe okno to klasa: new OknoX(), potem ShowDialog() albo Show().
  • ShowDialog blokuje właściciela i czeka; wynik true daje DialogResult = true w oknie.
  • Dane do okna — konstruktorem; z okna — publiczną właściwością odczytaną po zamknięciu.
  • Show otwiera okno obok; trzymaj je w polu, zeruj w Closed, odświeżaj jego publiczną metodą.
  • Zawsze ustawiaj Owner — okna podrzędne zamkną się razem z głównym.

Dokumentacja: Microsoft Learn — okna dialogowe, Okna w WPF.

Postęp zapisuje się w tej przeglądarce.