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.
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.
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:
├── 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
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ń.
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?:
<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>
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
}
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.
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:
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;
}
}
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.
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:
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ściciela | zablokowane | działa normalnie |
| kod po wywołaniu | czeka na zamknięcie | wykonuje się od razu |
| wynik | bool? z DialogResult | brak (void) |
| typowe użycie | formularz OK/Anuluj, pytanie | podglą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.
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.
okno edycji (modalne) zasłania główne i je blokuje; statystyki (niemodalne) działają niezależnie — po kliknięciu OK pokażą „średni: 2”
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}";
}
}
<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>
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
}
}
<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>
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;
}
}
<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>
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
OknoUczestnikama dwa konstruktory: pusty (dodawanie) i z parametremUczestnik(edycja — wypełnia pola i zaznacza poziom).Ok_Clicksprawdza dane. Przy błędzie pokazuje komunikat i okno zostaje otwarte; przy poprawnych danych tworzy nowy obiekt wWyniki ustawiaDialogResult = true.Dodaj_ClickiEdytuj_Clickczekają naShowDialog. Dopiero gdy wynik totrue, zmieniają listę — przy edycji nowy obiekt zastępuje stary pod tym samym indeksem.Lista_MouseDoubleClickpo prostu wywołujeEdytuj_Click— dwuklik działa jak przycisk.Statystyki_Clicktworzy okno statystyk tylko raz; kolejne kliknięcie wysuwa je na wierzch. Polestatystykizeruje się wStatystyki_Closed.PokazListepo każdej zmianie wywołuje publiczną metodęOdswiezokna 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
- 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.
- Usuń z
Statystyki_Clicklinięstatystyki.Owner = this;. Otwórz statystyki i zamknij główne okno: statystyki zostaną, a program nie zakończy się. - Zamień w
Dodaj_ClickShowDialog()naShow(). Program się nie skompiluje:Showniczego nie zwraca, więc nie ma czego porównać ztrue.
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
new NazwaOkna() | obiekt | Tworzy okno (jeszcze niewidoczne). |
ShowDialog() | bool? | Okno modalne; czeka na zamknięcie. |
Show() | metoda | Okno niemodalne; nie czeka. |
DialogResult | bool? | Wynik okna modalnego; ustawienie zamyka okno. |
IsDefault, IsCancel | bool | Przycisk dla Enter; przycisk dla Esc z DialogResult = false. |
Owner | Window | Właściciel: okno nad nim, zamykane razem z nim. |
WindowStartupLocation="CenterOwner" | wyliczenie | Okno na środku właściciela. |
ShowInTaskbar | bool | Czy okno ma przycisk na pasku zadań. |
Activate() | metoda | Wysuwa okno na wierzch. |
Closed | zdarzenie | Okno zostało zamknięte. |
Left, Top | double | Położenie okna na ekranie. |
{ get; private set; } | właściwość | Odczyt z zewnątrz, zapis tylko w klasie. |
MouseDoubleClick | zdarzenie | Dwukrotne kliknięcie kontrolki. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
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 okna | InvalidOperationException. Utwórz nowy obiekt. |
wypełnianie pól w konstruktorze przed InitializeComponent() | NullReferenceException — kontrolki jeszcze nie istnieją. |
| zmiana przekazanego obiektu w oknie edycji | Po Anuluj zmiany i tak zostają. Zwracaj nowy obiekt. |
okno niemodalne bez Owner | Zostaje 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
Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
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łajClose()głównego okna.
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 odczytaszR,G,Bdo suwaków.
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
Zamowieniez metodą liczącą cenę. Okno szczegółów ma publiczną metodęPokaz(Zamowienie?), wywoływaną zSelectionChangedlisty.
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
DispatcherTimergry i wznów go po zamknięciu.
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:KeyDownokna tablicy iClose(); panel dowie się przezClosed.
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 poDialogResult == true.
Co trzeba zapamiętać
- Nowe okno to klasa:
new OknoX(), potemShowDialog()alboShow(). ShowDialogblokuje właściciela i czeka; wyniktruedajeDialogResult = truew oknie.- Dane do okna — konstruktorem; z okna — publiczną właściwością odczytaną po zamknięciu.
Showotwiera okno obok; trzymaj je w polu, zeruj wClosed, 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.