Label i formularz w Grid
Formularz to najczęstsze okno na egzaminie: kolumna opisów po lewej, kolumna pól po prawej i przyciski na dole. W tej lekcji budujesz go porządnie — z kontrolką Label, która przenosi kursor do pola po naciśnięciu Alt + litera, z równymi kolumnami, sensowną kolejnością klawisza Tab i oknem, którego nie da się zmniejszyć tak, że formularz się rozsypie.
Czego się dziś nauczysz
- Wyjaśnisz różnicę między
LabelaTextBlocki dobierzesz właściwą kontrolkę. - Dodasz klawisz dostępu (Alt + litera) do etykiety i do przycisku.
- Zbudujesz formularz w
Gridz kolumnamiAutoi*, w którym krótkie pola nie rozciągają się na całą szerokość. - Ustawisz kolejność przechodzenia klawiszem Tab i wyłączysz z niej pola tylko do odczytu.
- Dopasujesz rozmiar okna do formularza (
SizeToContent,MinWidth,MinHeight).
Label czy TextBlock?
Obie kontrolki wyświetlają tekst. Label jest jednak przeznaczona do opisywania pól formularza:
Label | TextBlock | |
|---|---|---|
| Treść | Content | Text |
| Wewnętrzny odstęp | Padding 5 — od razu wyrównuje się z polem tekstowym | brak |
| Klawisz dostępu | tak: _ przed literą i Target | nie |
| Zawijanie tekstu | nie | tak: TextWrapping |
| Do czego | opis pola: „Imię:”, „Klasa:” | dłuższy tekst, wyniki, komunikaty |
<StackPanel Margin="10">
<Label Content="Imię:"/>
<TextBox x:Name="Imie"/>
<TextBlock x:Name="Wynik" TextWrapping="Wrap" Margin="0,10,0,0"/>
</StackPanel>
Z kodu tekst etykiety zmieniasz przez Content, a nie Text: Opis.Content = "Nazwisko:";.
Klawisze dostępu
Znak podkreślenia przed literą robi z niej klawisz dostępu. Litera nie wyświetla się z podkreślnikiem — gdy użytkownik przytrzyma Alt, pojawi się pod nią kreska. Alt + ta litera działa tak:
<!-- Alt+N przenosi kursor do pola Nazwisko -->
<Label Content="_Nazwisko:" Target="{Binding ElementName=Nazwisko}"/>
<TextBox x:Name="Nazwisko"/>
<!-- Alt+Z „klika” przycisk -->
<Button Content="_Zapisz" Click="Zapisz_Click"/>
| Gdzie | Co robi Alt + litera |
|---|---|
Label z Target | Przenosi kursor do kontrolki wskazanej w Target. |
Button | Wywołuje Click, tak jakby przycisk kliknięto myszą. |
| prawdziwy podkreślnik w tekście | Piszesz dwa: Content="numer__karty" wyświetli „numer_karty”. |
Zapis {Binding ElementName=…}
Na razie traktuj Target="{Binding ElementName=Nazwisko}" jako stały wzór: w miejsce Nazwisko wpisujesz x:Name pola. Co dokładnie znaczą nawiasy klamrowe i słowo Binding, dowiesz się w lekcji 41.
Litery w jednym oknie nie powinny się powtarzać. Przy dwóch etykietach z tą samą literą Alt + litera przechodzi kolejno między nimi, co myli użytkownika.
Układ formularza w Grid
W lekcji 07 wiersze formularza robiłeś z poziomych StackPanel, a etykiety dostawały stałą szerokość 180. Grid załatwia to sam: kolumna etykiet Auto ma szerokość najdłuższej etykiety, a kolumna pól * — resztę okna.
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="_Imię:" Target="{Binding ElementName=Imie}"/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="Imie" Margin="0,4"/>
<Label Grid.Row="1" Content="_Klasa:" Target="{Binding ElementName=Klasa}"/>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="Klasa" Margin="0,4"
Width="60" HorizontalAlignment="Left"/>
<Label Grid.Row="2" Content="Numer w _dzienniku:" Target="{Binding ElementName=Numer}"/>
<TextBox Grid.Row="2" Grid.Column="1" x:Name="Numer" Margin="0,4"
Width="40" HorizontalAlignment="Left"/>
</Grid>
krótkie pola mają własną szerokość i HorizontalAlignment="Left"
Trzy zasady dobrego formularza:
- Wiersze z polami jednowierszowymi —
Auto. Wiersz z polem wielowierszowym —*. - Pole na krótką wartość (klasa, wiek, kod) dostaje
WidthiHorizontalAlignment="Left"— szerokość pola podpowiada, ile trzeba wpisać. - Przyciski na dole stoją w poziomym
StackPanelzHorizontalAlignment="Right"; najważniejszy jest ostatni i maIsDefault="True".
Kolejność klawisza Tab
Tab przenosi kursor do następnej kontrolki, Shift + Tab — do poprzedniej. Domyślna kolejność to kolejność w pliku XAML, więc zwykle wystarczy pisać kontrolki od góry do dołu. Gdy trzeba ją zmienić, ustawiasz TabIndex (mniejsza liczba = wcześniej). Kontrolkę, do której Tab ma nie wchodzić, oznaczasz IsTabStop="False":
<TextBox x:Name="Kwota" TabIndex="0"/>
<TextBox x:Name="Waluta" TabIndex="1"/>
<!-- wynik: tylko do odczytu i pomijany przez Tab -->
<TextBox x:Name="Wynik" IsReadOnly="True" IsTabStop="False"/>
Label sama nigdy nie dostaje fokusu — Tab ją przeskakuje.
Rozmiar okna z formularzem
| Ustawienie okna | Efekt |
|---|---|
SizeToContent="Height" | Okno samo dobiera wysokość do zawartości. Atrybut Height jest wtedy pomijany. Inne wartości: Width, WidthAndHeight, Manual (domyślna). |
MinWidth="400" MinHeight="300" | Użytkownik nie zmniejszy okna poniżej tych wymiarów — formularz się nie rozsypie. |
ResizeMode="CanMinimize" | Okna nie da się w ogóle zmieniać (z lekcji 01) — dobre dla małych, stałych formularzy. |
SizeToContent="Height" nie pasuje do okna z wierszem *: okno bierze wysokość z zawartości, więc wiersz z gwiazdką zachowuje się jak Auto i pole wielowierszowe kurczy się do jednej linii. Używaj go w formularzach złożonych z samych wierszy Auto.
Zgłoszenie usterki w pracowni
Uczeń albo nauczyciel zgłasza usterkę komputera: wpisuje imię i nazwisko, salę, numer stanowiska i opis. Formularz sprawdza pola od góry do dołu, pokazuje podsumowanie i liczy wysłane zgłoszenia. Po wysłaniu zgłaszający i sala zostają w polach, bo zwykle zgłasza się kilka usterek z jednej sali.
formularz przed wysłaniem trzeciego zgłoszenia
<Window x:Class="Lekcja10.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Zgłoszenie usterki" Height="400" Width="480"
MinWidth="400" MinHeight="340"
WindowStartupLocation="CenterScreen">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <!-- etykiety: szerokość najdłuższej -->
<ColumnDefinition Width="*"/> <!-- pola: cała reszta -->
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <!-- opis rośnie razem z oknem -->
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="_Imię i nazwisko:"
Target="{Binding ElementName=Osoba}"/>
<TextBox x:Name="Osoba" Grid.Row="0" Grid.Column="1"
Margin="0,4" MaxLength="40"/>
<Label Grid.Row="1" Grid.Column="0" Content="_Sala:"
Target="{Binding ElementName=Sala}"/>
<TextBox x:Name="Sala" Grid.Row="1" Grid.Column="1"
Margin="0,4" Width="80" HorizontalAlignment="Left" MaxLength="5"/>
<Label Grid.Row="2" Grid.Column="0" Content="_Numer stanowiska:"
Target="{Binding ElementName=Stanowisko}"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<TextBox x:Name="Stanowisko" Margin="0,4" Width="50" MaxLength="2"/>
<TextBlock Text="(1–16)" Margin="8,0,0,0" VerticalAlignment="Center"
Foreground="Gray"/>
</StackPanel>
<Label Grid.Row="3" Grid.Column="0" Content="_Opis usterki:"
Target="{Binding ElementName=Opis}" VerticalAlignment="Top"/>
<TextBox x:Name="Opis" Grid.Row="3" Grid.Column="1" Margin="0,4"
AcceptsReturn="True" TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto" MaxLength="300"/>
<!-- dolny wiersz: licznik po lewej, przyciski po prawej -->
<TextBlock x:Name="Licznik" Grid.Row="4" Grid.Column="0"
Text="Wysłane: 0" VerticalAlignment="Center" Margin="5,10,0,0"/>
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal"
HorizontalAlignment="Right" Margin="0,10,0,0">
<Button Content="Wy_czyść" Width="90" Height="28" Margin="0,0,8,0"
Click="Wyczysc_Click"/>
<Button Content="Wyślij" Width="90" Height="28"
IsDefault="True" Click="Wyslij_Click"/>
</StackPanel>
</Grid>
</Window>
using System.Windows;
using System.Windows.Controls;
namespace Lekcja10;
public partial class MainWindow : Window
{
private int wyslane = 0;
public MainWindow()
{
InitializeComponent();
}
private void Wyslij_Click(object sender, RoutedEventArgs e)
{
// 1. Sprawdzanie pól od góry do dołu
if (string.IsNullOrWhiteSpace(Osoba.Text))
{
PokazBlad("Wpisz imię i nazwisko.", Osoba);
return;
}
if (string.IsNullOrWhiteSpace(Sala.Text))
{
PokazBlad("Wpisz numer sali.", Sala);
return;
}
if (!int.TryParse(Stanowisko.Text, out int stanowisko) || stanowisko < 1 || stanowisko > 16)
{
PokazBlad("Numer stanowiska musi być liczbą od 1 do 16.", Stanowisko);
return;
}
if (Opis.Text.Trim().Length < 10)
{
PokazBlad("Opisz usterkę dokładniej (co najmniej 10 znaków).", Opis);
return;
}
// 2. Podsumowanie zgłoszenia
MessageBox.Show($"Zgłaszający: {Osoba.Text.Trim()}\n" +
$"Miejsce: sala {Sala.Text.Trim()}, stanowisko {stanowisko}\n" +
$"Opis: {Opis.Text.Trim()}",
"Zgłoszenie wysłane", MessageBoxButton.OK, MessageBoxImage.Information);
wyslane++;
Licznik.Text = $"Wysłane: {wyslane}";
// 3. Zgłaszający i sala zostają — zwykle zgłasza się kilka usterek naraz
Stanowisko.Clear();
Opis.Clear();
Stanowisko.Focus();
}
private void Wyczysc_Click(object sender, RoutedEventArgs e)
{
Osoba.Clear();
Sala.Clear();
Stanowisko.Clear();
Opis.Clear();
Osoba.Focus();
}
private void PokazBlad(string komunikat, TextBox pole)
{
MessageBox.Show(komunikat, "Błąd w formularzu",
MessageBoxButton.OK, MessageBoxImage.Warning);
pole.Focus();
pole.SelectAll();
}
}
Co dzieje się po kolei
- Siatka ma 2 kolumny (
Autona etykiety,*na pola) i 5 wierszy. Tylko wiersz z opisem ma*, więc po powiększeniu okna rośnie wyłącznie pole opisu. - Każda etykieta ma klawisz dostępu: Alt + I, S, N, O przenosi kursor do odpowiedniego pola. Przycisk „Wyczyść” ma Alt + C, bo litera W pojawia się też w „Wyślij”.
- Etykieta opisu ma
VerticalAlignment="Top"— bez tego stałaby na środku wysokiego pola. - Pola sali i stanowiska mają własną szerokość i
HorizontalAlignment="Left". Obok pola stanowiska podpowiedź „(1–16)” — dlatego pole i podpowiedź stoją razem w poziomymStackPanelw jednej komórce. - W dolnym wierszu licznik zajmuje kolumnę etykiet, a przyciski — kolumnę pól, dosunięte do prawej.
Wyslij_Clicksprawdza pola w tej samej kolejności, w jakiej stoją w oknie. Pierwszy błąd kończy metodę, aPokazBladprzenosi kursor do złego pola.- Po wysłaniu czyścimy tylko stanowisko i opis, a kursor trafia do pola stanowiska — kolejne zgłoszenie z tej samej sali to dwa pola do wypełnienia.
MinWidthiMinHeightpilnują, by po zmniejszeniu okna przyciski nie nachodziły na pola.
Sprawdź trzy rzeczy
- Uruchom program i naciśnij Alt + O, potem Alt + I. Kursor przeskakuje między opisem i polem nazwiska. Przytrzymaj sam Alt — pod literami pojawią się kreski.
- Usuń z etykiety sali atrybut
Target. Litera S nadal jest podkreślona, ale Alt + S nic nie robi. - Zmień szerokość pierwszej kolumny z
Autona80. Etykieta „Numer stanowiska:” zostanie ucięta.
Zestawienie elementów
| Element | Typ / wartości | Co robi |
|---|---|---|
Label.Content | object | Tekst etykiety. _ przed literą tworzy klawisz dostępu, __ wyświetla podkreślnik. |
Label.Target | kontrolka | Dokąd Alt + litera przenosi kursor. Wzór: {Binding ElementName=Nazwa}. |
TabIndex | int | Kolejność przechodzenia klawiszem Tab. |
IsTabStop | bool | False — Tab pomija kontrolkę. |
Window.SizeToContent | Manual, Width, Height, WidthAndHeight | Okno dopasowuje rozmiar do zawartości. |
MinWidth, MinHeight | double | Najmniejszy dozwolony rozmiar okna (albo kontrolki). |
Zanim utkniesz
| Zapis | Problem |
|---|---|
<Label Text="Imię:"/> | Błąd XAML — Label nie ma Text, ma Content. |
Opis.Text = "…" dla etykiety | CS1061 — z kodu też Content. |
Content="_Imię:" bez Target | Litera jest podkreślana, ale Alt + litera nic nie robi. |
Target="Imie" | Błąd — potrzebny wzór {Binding ElementName=Imie}. |
| ta sama litera w dwóch etykietach | Alt + litera skacze na zmianę między dwoma polami. |
wszystkie wiersze * | Pola jednowierszowe rozciągają się na wysokość, formularz wygląda dziwnie. Wiersze z polami — Auto. |
SizeToContent="Height" z wierszem * | Wiersz z gwiazdką zachowuje się jak Auto — pole wielowierszowe ma jedną linię. |
| długa etykieta w kolumnie o stałej szerokości | Tekst jest ucinany. Kolumna etykiet — Auto. |
Zadania
W każdym formularzu opisy pól to Label z klawiszem dostępu, a układ to Grid z kolumną etykiet Auto. Błędy sygnalizuj oknem MessageBox i przenoś kursor do złego pola. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
Formularz biblioteki szkolnej: Numer karty (liczba czterocyfrowa 1000–9999), Tytuł książki, Liczba dni (1–30, domyślnie wpisane 14). Przycisk Wypożycz (pod Enter) pokazuje pod formularzem: „Karta 1234: „Lalka” — zwrot do 22.10.2026 (czwartek)”. Okno dopasowuje wysokość do zawartości i nie da się go zwęzić poniżej 360.
pokaż wskazówkę
- Datę zwrotu da
DateTime.Now.AddDays(dni), a format z nazwą dnia —ToString("dd.MM.yyyy (dddd)"). - Formularz bez wiersza
*to dobry przypadek dlaSizeToContent.
Pola: Imię, Bilety normalne, Bilety ulgowe (liczby całkowite od 0). Ceny: normalny 28 zł, ulgowy 20 zł. W jednej transakcji można kupić od 1 do 10 biletów łącznie. Przycisk Oblicz wpisuje do pola tylko do odczytu na dole: „Ola: 2 × normalny, 3 × ulgowy = 116,00 zł”. Pole wyniku jest pomijane przez Tab. Przycisk Anuluj zamyka okno i reaguje na Esc.
pokaż wskazówkę
- Ceny zapisz jako stałe
decimal. - Najpierw sprawdź każde pole osobno, a dopiero potem sumę biletów.
Kalkulator kredytu na laptop: Kwota (100–20 000 zł), Oprocentowanie roczne w % (0–25, może być z przecinkiem), Liczba rat (3–48). Wynik: miesięczna rata, łączna kwota do spłaty i koszt odsetek, każda kwota z dwoma miejscami po przecinku. Raty liczysz ze wzoru na ratę równą: rata = K · r / (1 − (1 + r)−n), gdzie r to oprocentowanie miesięczne (procent roczny / 12 / 100), a n — liczba rat. Przy oprocentowaniu 0 rata to po prostu K / n.
pokaż wskazówkę
- Potęgę policzysz przez
Math.Pow(1 + r, -n). - Przypadek
r == 0musisz obsłużyć osobno — inaczej dzielisz przez zero. - Dla kontroli: 3000 zł, 10 %, 12 rat → rata 263,75 zł.
Po lewej formularz: Imię i nazwisko, Stanowisko, Telefon, E-mail. Po prawej podgląd wizytówki, który zmienia się przy każdym naciśnięciu klawisza: imię i nazwisko dużą pogrubioną czcionką, pod nim stanowisko kursywą, niżej telefon i e-mail. Puste pole w podglądzie pokazuje szary tekst zastępczy, np. „Imię i nazwisko”. Wszystkie pola obsługuje jedna metoda TextChanged.
pokaż wskazówkę
- Trzecia kolumna siatki na podgląd, a podgląd — pionowy
StackPanelzGrid.RowSpanna wszystkie wiersze formularza. - Tekst zastępczy i jego kolor ustaw w jednej metodzie pomocniczej z parametrami: pole, napis podglądu, tekst zastępczy.
- Zdarzenie może przyjść, zanim powstanie podgląd. Na początku metody sprawdź, czy napis podglądu nie jest
null.
Formularz adresu w dwóch parach kolumn (etykieta, pole, etykieta, pole): w pierwszym wierszu Ulica i Nr domu, w drugim Kod pocztowy i Miejscowość, w trzecim Odbiorca na całą szerokość. Kod pocztowy musi mieć postać NN-NNN (cyfry i myślnik na trzecim miejscu). Przycisk Drukuj etykietę pokazuje adres w oknie MessageBox w trzech liniach, tak jak na kopercie.
pokaż wskazówkę
- Kolumny:
Auto,*,Auto,*. Pole odbiorcy zaczyna w kolumnie 1 i zajmuje 3 kolumny. - Kod sprawdzisz w pętli po znakach: długość 6, znak o indeksie 2 to
'-', pozostałe spełniająchar.IsDigit.
Wychowawca planuje wycieczkę. Pola: Liczba uczniów (10–40), Liczba dni (1–5), Autokar za dzień (zł), Nocleg za osobę i noc (zł), Bilety wstępu na osobę (zł). Zasady:
- na każdych rozpoczętych 10 uczniów jedzie jeden opiekun (np. 21 uczniów → 3 opiekunów);
- noclegów jest o jeden mniej niż dni; opiekunowie płacą za nocleg i bilety jak uczniowie;
- koszt opiekunów rozkłada się na uczniów, a kwotę na ucznia zaokrągla się w górę do pełnych złotych.
Wynik w polu tylko do odczytu: liczba opiekunów, koszt całkowity, koszt na ucznia. Przycisk Wyczyść ma klawisz dostępu, a Oblicz — Enter. Wpisz w komentarzu dwa zestawy danych, które sprawdziłeś ręcznie.
pokaż wskazówkę
- Opiekunowie:
(uczniowie + 9) / 10na liczbach całkowitych daje zaokrąglenie w górę. - Kwotę w górę do pełnych złotych da
Math.Ceiling. - Najpierw policz osobno autokar, noclegi i bilety — łatwiej znaleźć błąd.
Co trzeba zapamiętać
Labelopisuje pole: treść wContent, wbudowany odstęp 5, klawisz dostępu przez_iTarget.TextBlock— dłuższy tekst, wyniki i komunikaty.- Formularz w
Grid: kolumnyAuto+*, wiersze pólAuto, wiersz pola wielowierszowego*. - Krótkie pola: własny
WidthiHorizontalAlignment="Left". Przyciski na dole po prawej. - Kolejność Tab = kolejność w XAML; zmienisz ją
TabIndex, wyłączyszIsTabStop="False". SizeToContentdopasowuje okno do formularza,MinWidth/MinHeightchronią przed zbytnim zmniejszeniem.
Dokumentacja: Microsoft Learn — klasa Label.