Grid — wiersze i kolumny
Do tej pory układałeś kontrolki jedna pod drugą w StackPanel. Większość prawdziwych okien wygląda jednak jak tabela: napisy po lewej, pola po prawej, przyciski w rzędzie, a główna część rośnie razem z oknem. Do tego służy Grid — najważniejszy panel w WPF i ten sam, który od lekcji 01 czeka pusty w każdym nowym projekcie.
Czego się dziś nauczysz
- Podzielisz okno na wiersze i kolumny za pomocą
RowDefinitionsiColumnDefinitions. - Umieścisz kontrolkę w wybranej komórce właściwościami
Grid.RowiGrid.Column. - Dobierzesz rozmiar wiersza lub kolumny: stały,
Autoalbo proporcjonalny*— i policzysz, ile miejsca dostanie kolumna2*. - Połączysz kilka komórek w jedną przez
Grid.ColumnSpaniGrid.RowSpan. - Zbudujesz okno, w którym główna część rośnie razem z oknem, a nagłówek i przyciski zostają na swoim miejscu.
- Zagnieździsz jeden
Gridw drugim i dodasz kontrolkę do komórki z kodu C#.
Wiersze, kolumny i numery komórek
Pusty <Grid> ma jedną komórkę na całe okno. Żeby go podzielić, najpierw opisujesz, ile ma wierszy i kolumn, a dopiero potem wstawiasz kontrolki:
<Grid>
<Grid.RowDefinitions> <!-- 2 wiersze -->
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions> <!-- 3 kolumny -->
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Content="A"/>
<Button Grid.Row="0" Grid.Column="2" Content="B"/>
<Button Grid.Row="1" Grid.Column="1" Content="C"/>
</Grid>
Jeden znacznik <RowDefinition/> to jeden wiersz, jeden <ColumnDefinition/> — jedna kolumna. Miejsce kontrolki podajesz w samej kontrolce: Grid.Row to numer wiersza, Grid.Column — numer kolumny. Numeracja zaczyna się od 0:
A
B
C
siatka 2 × 3 — przycisk bez Width i Height wypełnia całą komórkę
Dlaczego Grid.Row, a nie samo Row?
Przycisk nie ma własnej właściwości „wiersz” — przecież może stać w StackPanel, gdzie wierszy nie ma. To Grid „dokleja” kontrolce swoją informację. Taką właściwość nazywamy dołączoną (ang. attached property) i zapisujemy z nazwą panelu przed kropką.
Kontrolka bez Grid.Row i Grid.Column trafia do komórki (0, 0), bo 0 to wartość domyślna. Dwie kontrolki w tej samej komórce nakładają się — ta, która jest niżej w pliku, rysuje się na wierzchu. To najczęstsza przyczyna „zniknięcia” przycisku.
Linie pomocnicze
Dopisz <Grid ShowGridLines="True">, a po uruchomieniu zobaczysz przerywane granice komórek. Świetne do szukania błędów w układzie — w gotowym programie usuń ten atrybut.
Rozmiary: liczba, Auto i gwiazdka
Wysokość wiersza ustawiasz atrybutem Height, szerokość kolumny — atrybutem Width:
| Zapis | Nazwa | Co oznacza |
|---|---|---|
Height="60" | rozmiar stały | Dokładnie 60 jednostek, niezależnie od okna i zawartości. |
Height="Auto" | według zawartości | Wiersz jest tak wysoki, jak jego najwyższa kontrolka. Dobre dla napisów, pól jednowierszowych i przycisków. |
Height="*" | proporcja | Wiersz dostaje część pozostałego miejsca. 2* to dwie części, 3* — trzy. To wartość domyślna: <RowDefinition/> znaczy to samo co Height="*". |
Jak WPF dzieli szerokość — krok po kroku
Siatka ma 600 jednostek szerokości i cztery kolumny:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/> <!-- stała -->
<ColumnDefinition Width="Auto"/> <!-- przycisk w tej kolumnie ma 80 szerokości -->
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
| Krok | Co robi WPF | Wynik |
|---|---|---|
| 1 | Przydziela kolumny o stałej szerokości | kolumna 0 → 100 |
| 2 | Mierzy zawartość kolumn Auto | kolumna 1 → 80 |
| 3 | Liczy, ile zostało | 600 − 100 − 80 = 420 |
| 4 | Sumuje gwiazdki: * + 2* | 3 części, jedna część = 420 / 3 = 140 |
| 5 | Rozdaje części | kolumna 2 → 140, kolumna 3 → 280 |
80
140
280
po poszerzeniu okna rosną tylko kolumny z gwiazdką — w proporcji 1 : 2
Łączenie komórek: ColumnSpan i RowSpan
Nagłówek na całą szerokość okna albo duży przycisk na dwa wiersze: podajesz komórkę startową oraz liczbę zajmowanych kolumn (Grid.ColumnSpan) lub wierszy (Grid.RowSpan). Kontrolka rozciąga się zawsze w prawo i w dół.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- wiersz 0, od kolumny 0, zajmuje 2 kolumny -->
<TextBlock Grid.Row="0" Grid.ColumnSpan="2" Text="Panel gracza" FontSize="20"/>
<!-- kolumna 0, od wiersza 1, zajmuje 2 wiersze -->
<Button Grid.Row="1" Grid.RowSpan="2" Content="START"/>
<Button Grid.Row="1" Grid.Column="1" Content="Opcje"/>
<Button Grid.Row="2" Grid.Column="1" Content="Wyjście"/>
</Grid>
START
Opcje
Wyjście
ColumnSpan łączy w poziomie, RowSpan w pionie
Co rośnie razem z oknem
W lekcji 07 pole wielowierszowe w StackPanel musiało dostać stałą wysokość. W Grid wystarczy postawić je w wierszu z gwiazdką — wtedy wypełnia całe wolne miejsce i rośnie, gdy użytkownik powiększa okno:
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- tytuł: tyle, ile tekst -->
<RowDefinition Height="*"/> <!-- notatka: cała reszta -->
<RowDefinition Height="Auto"/> <!-- przycisk: tyle, ile przycisk -->
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Notatka" FontSize="18" Margin="0,0,0,5"/>
<TextBox Grid.Row="1" AcceptsReturn="True" TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"/>
<Button Grid.Row="2" Content="Zapisz" Width="100" Margin="0,10,0,0"
HorizontalAlignment="Right"/>
</Grid>
powiększ okno — rośnie tylko pole notatki
Zasada praktyczna
Wiersze z napisami, polami jednowierszowymi i przyciskami — Auto. Główna część okna — *. Liczby stałe stosuj rzadko: przy większej czcionce tekst może się w nich nie zmieścić.
Grid w Gridzie i Grid z kodu
W komórce może stać dowolna kontrolka — także StackPanel albo kolejny Grid z własnymi wierszami i kolumnami. Panel wewnętrzny numeruje swoje komórki od nowa, od 0. Dzięki temu dużą siatkę dzielisz na kawałki, z których każdy ma prosty układ (tak jest zbudowany przykład tej lekcji).
Kontrolkę możesz też dodać do siatki z kodu — tak jak w lekcji 03 dodawałeś napisy do StackPanel. Właściwości dołączone ustawiasz wtedy metodami Grid.SetRow i Grid.SetColumn:
// w XAML: <Grid x:Name="Plansza"> z 3 wierszami i 3 kolumnami
for (int w = 0; w < 3; w++)
{
for (int k = 0; k < 3; k++)
{
Button pole = new Button { Content = $"{w},{k}", Margin = new Thickness(2) };
Grid.SetRow(pole, w); // to samo co Grid.Row="w" w XAML
Grid.SetColumn(pole, k); // to samo co Grid.Column="k"
Plansza.Children.Add(pole);
}
}
Odczyt działa w drugą stronę: Grid.GetRow(pole) zwraca numer wiersza kontrolki.
Zamek szyfrowy
Okno z klawiaturą numeryczną. Użytkownik wpisuje 4 cyfry, OK sprawdza kod, C czyści wyświetlacz. Po trzech błędnych próbach zamek się blokuje. Okno to siatka z trzema wierszami: wyświetlacz, klawiatura i komunikat. Klawiatura jest osobnym Grid 4 × 3 w środkowym wierszu — dzięki temu można ją zablokować jednym poleceniem.
po pierwszej błędnej próbie i wpisaniu dwóch nowych cyfr
<Window x:Class="Lekcja09.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Zamek szyfrowy" Height="460" Width="320"
WindowStartupLocation="CenterScreen">
<!-- Siatka zewnętrzna: wyświetlacz, klawiatura, komunikat -->
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="Wyswietlacz" Grid.Row="0"
Margin="4" Padding="10"
Background="Black" Foreground="Lime"
FontSize="32" FontFamily="Consolas" TextAlignment="Right"/>
<!-- Siatka wewnętrzna: klawiatura 4 × 3 w wierszu 1 -->
<Grid x:Name="Klawiatura" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Content="1" Margin="4" FontSize="22" Click="Cyfra_Click"/>
<Button Grid.Row="0" Grid.Column="1" Content="2" Margin="4" FontSize="22" Click="Cyfra_Click"/>
<Button Grid.Row="0" Grid.Column="2" Content="3" Margin="4" FontSize="22" Click="Cyfra_Click"/>
<Button Grid.Row="1" Grid.Column="0" Content="4" Margin="4" FontSize="22" Click="Cyfra_Click"/>
<Button Grid.Row="1" Grid.Column="1" Content="5" Margin="4" FontSize="22" Click="Cyfra_Click"/>
<Button Grid.Row="1" Grid.Column="2" Content="6" Margin="4" FontSize="22" Click="Cyfra_Click"/>
<Button Grid.Row="2" Grid.Column="0" Content="7" Margin="4" FontSize="22" Click="Cyfra_Click"/>
<Button Grid.Row="2" Grid.Column="1" Content="8" Margin="4" FontSize="22" Click="Cyfra_Click"/>
<Button Grid.Row="2" Grid.Column="2" Content="9" Margin="4" FontSize="22" Click="Cyfra_Click"/>
<Button Grid.Row="3" Grid.Column="0" Content="C" Margin="4" FontSize="22" Click="Wyczysc_Click"/>
<Button Grid.Row="3" Grid.Column="1" Content="0" Margin="4" FontSize="22" Click="Cyfra_Click"/>
<Button Grid.Row="3" Grid.Column="2" Content="OK" Margin="4" FontSize="22" Click="Ok_Click"/>
</Grid>
<TextBlock x:Name="Komunikat" Grid.Row="2"
Margin="4,10,4,0" FontSize="16" TextAlignment="Center"
Text="Wpisz 4-cyfrowy kod"/>
</Grid>
</Window>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Lekcja09;
public partial class MainWindow : Window
{
private const string Kod = "2468";
private const int LimitProb = 3;
private int bledneProby = 0;
public MainWindow()
{
InitializeComponent();
}
// Jedna metoda obsługuje wszystkie dziesięć cyfr
private void Cyfra_Click(object sender, RoutedEventArgs e)
{
Button przycisk = (Button)sender;
if (Wyswietlacz.Text.Length < 4)
{
Wyswietlacz.Text += przycisk.Content;
}
}
private void Wyczysc_Click(object sender, RoutedEventArgs e)
{
Wyswietlacz.Text = "";
}
private void Ok_Click(object sender, RoutedEventArgs e)
{
if (Wyswietlacz.Text.Length < 4)
{
Komunikat.Text = "Kod musi mieć 4 cyfry";
Komunikat.Foreground = Brushes.DarkOrange;
return;
}
if (Wyswietlacz.Text == Kod)
{
Komunikat.Text = "Zamek otwarty!";
Komunikat.Foreground = Brushes.Green;
Klawiatura.IsEnabled = false;
return;
}
bledneProby++;
Wyswietlacz.Text = "";
if (bledneProby >= LimitProb)
{
Komunikat.Text = "Zamek zablokowany";
Komunikat.Foreground = Brushes.Red;
Klawiatura.IsEnabled = false; // cała siatka z przyciskami naraz
}
else
{
Komunikat.Text = $"Błędny kod. Pozostało prób: {LimitProb - bledneProby}";
Komunikat.Foreground = Brushes.Red;
}
}
}
Co dzieje się po kolei
- Zewnętrzny
Gridma trzy wiersze:Autodla wyświetlacza,*dla klawiatury iAutodla komunikatu. Po powiększeniu okna rosną tylko przyciski. - Klawiatura to drugi
Gridw wierszu 1. Ma własne 4 wiersze i 3 kolumny, numerowane od 0 — przycisk „1” maGrid.Row="0", choć cała klawiatura stoi w wierszu 1 okna. - Wszystkie wiersze i kolumny klawiatury mają
*, więc przyciski są równe. Grid nie robi odstępów między komórkami — szczelinę 8 jednostek tworzą marginesy sąsiednich przycisków (4 + 4). - Dziesięć cyfr ma jedną metodę
Cyfra_Click. Który przycisk kliknięto, mówi(Button)sender, a jegoContentto cyfra do dopisania. Ok_Clicksprawdza najpierw, czy wpisano 4 cyfry. Krótszy kod nie zużywa próby.- Poprawny kod lub trzecia błędna próba ustawia
Klawiatura.IsEnabled = false. Właściwość z lekcji 05 działa na panel i na wszystko, co w nim jest — dwanaście przycisków blokuje się naraz. - Stałe
KodiLimitProbsą w jednym miejscu na górze klasy — zmiana kodu to zmiana jednej linii.
Sprawdź trzy rzeczy
- Dopisz
ShowGridLines="True"do obu siatek i porównaj, gdzie przebiegają linie zewnętrznej, a gdzie wewnętrznej. - Usuń
Grid.Row="1"przy przycisku „5”. Przycisk trafi do wiersza 0 klawiatury i nałoży się na „2”, a w środku zostanie dziura. - Zmień wysokość środkowego wiersza zewnętrznej siatki na
Auto. Klawiatura skurczy się do wysokości przycisków, a po powiększeniu okna na dole zostanie puste miejsce.
Zestawienie elementów
| Element | Wartości | Co robi |
|---|---|---|
Grid.RowDefinitions, Grid.ColumnDefinitions | lista definicji | Ile znaczników RowDefinition / ColumnDefinition w środku — tyle wierszy / kolumn. |
RowDefinition.Height, ColumnDefinition.Width | liczba, Auto, *, 2* | Rozmiar wiersza / kolumny. Domyślnie *. |
Grid.Row, Grid.Column | int, od 0 | Właściwości dołączone — wpisujesz je w kontrolce. Domyślnie 0. |
Grid.RowSpan, Grid.ColumnSpan | int, od 1 | Ile wierszy / kolumn zajmuje kontrolka od komórki startowej. |
ShowGridLines | True / False | Przerywane linie komórek — tylko do szukania błędów. |
Grid.SetRow(k, n), Grid.SetColumn(k, n) | metody | Ustawiają wiersz / kolumnę kontrolki z kodu. |
Grid.GetRow(k), Grid.GetColumn(k) | int | Odczytują wiersz / kolumnę kontrolki. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
<Button Grid.Column="3"/> przy 3 kolumnach | WPF nie zgłosi błędu — po cichu wstawi przycisk do ostatniej kolumny (2), gdzie nałoży się na inny. Kolumny mają numery 0, 1, 2. |
<Button Row="1"/> | Błąd XAML: The property 'Row’ was not found. Potrzebny przedrostek: Grid.Row. |
<RowDefinition Grid.Row="1"/> | Definicja tylko opisuje rozmiar. Numer wiersza wpisujesz w kontrolce. |
napis i pole bez Grid.Row/Grid.Column | Oba w komórce (0, 0) — pole zasłania napis. |
Height="*2" | Błąd XAML. Liczba stoi przed gwiazdką: 2*. |
Grid.ColumnSpan="0" | Błąd przy uruchomieniu — kontrolka zajmuje co najmniej jedną kolumnę. |
wszystkie wiersze Auto | Nic nie rośnie z oknem. Po powiększeniu na dole zostaje puste miejsce. |
przycisk z Width w komórce * | Nie wypełni komórki, bo szerokość podałeś sam. Do wypełniania usuń Width. |
Zadania
Każde zadanie wykonaj w nowym projekcie Aplikacja WPF, chyba że treść mówi inaczej. Główny układ okna buduj na Grid. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
Panel windy w ośmiopiętrowym budynku: u góry wyświetlacz z bieżącym piętrem (na start „0”), pod nim przyciski pięter 0–7 w siatce 4 wiersze × 2 kolumny, a na dole napis z komunikatem. Po kliknięciu piętra komunikat mówi „Jadę w górę na piętro 5” albo „Jadę w dół na piętro 2”, a wyświetlacz pokazuje nowe piętro. Kliknięcie piętra, na którym winda już jest, daje „Jesteś na piętrze 3”. Wszystkie piętra obsługuje jedna metoda.
pokaż wskazówkę
- Bieżące piętro trzymaj w polu klasy typu
int. - Numer klikniętego piętra odczytasz z
Contentprzycisku — toobject, więc zamień go przezint.Parse(przycisk.Content.ToString()). Tu dane wpisałeś sam w XAML, więcParsejest bezpieczne.
Okno do notatek z lektury: u góry w jednym wierszu napis „Tytuł:” i pole na tytuł (pole zajmuje całą resztę szerokości). Pod spodem duże pole wielowierszowe na notatkę, które rośnie razem z oknem w obu kierunkach. Na dole po lewej napis „Znaków: 0” aktualizowany przy każdej zmianie notatki, a po prawej przycisk Wyczyść, który czyści oba pola.
pokaż wskazówkę
- Zastanów się, które wiersze mają być
Auto, a który*. W pierwszym wierszu napis i pole stoją w dwóch kolumnach — jakie szerokości? - Notatka i dolny wiersz muszą zająć wszystkie kolumny.
Klasyczny układ aplikacji: nagłówek „Informator ZSMI” na całą szerokość (wysokość według tekstu, ciemne tło), po lewej kolumna menu z przyciskami Godziny dzwonków, Sekretariat, Biblioteka, Stołówka, po prawej obszar treści 3 razy szerszy od menu, a na dole stopka o stałej wysokości 30 z napisem „Wybrano: Biblioteka”. Kliknięcie przycisku menu wpisuje do obszaru treści informacje o wybranym miejscu (wymyśl je) i zmienia stopkę.
pon.–pt. 8:00–15:00
sala 104, I piętro
pokaż wskazówkę
- Siatka 3 × 2. Proporcja „3 razy szersza” to zapis z gwiazdkami. Nagłówek i stopka zajmują obie kolumny.
- Przyciski menu ułóż w
StackPanelpostawionym w jednej komórce. Treść dla każdego przycisku możesz trzymać w jegoTag.
Rozbuduj przykład. Wyświetlacz ma pokazywać gwiazdki zamiast cyfr (po 3 cyfrach: ***), a sprawdzanie kodu ma działać jak wcześniej. Dodaj pod klawiaturą przycisk ← Usuń cyfrę na całą szerokość okna, który usuwa ostatnią wpisaną cyfrę (przy pustym kodzie nic nie robi). Po otwarciu zamka pokaż przycisk Zamknij ponownie, który zeruje licznik prób i odblokowuje klawiaturę.
pokaż wskazówkę
- Wyświetlacz nie może już przechowywać kodu — cyfry trzymaj w polu klasy typu
string. Gwiazdki:new string('*', liczba). - Nowy wiersz siatki zmienia numer wiersza komunikatu. Usunięcie ostatniego znaku:
Substring(0, długość - 1). - Przycisk „Zamknij ponownie” może od początku stać w oknie z
Visibility="Collapsed".
Okno z pustym Grid o 11 wierszach i 11 kolumnach (wszystkie *). Po otwarciu okna program wypełnia go z kodu: w wierszu 0 i kolumnie 0 liczby 1–10 na szarym tle (nagłówki), w pozostałych komórkach iloczyny. Kwadraty liczb (na przekątnej) mają żółte tło. Komórki to TextBlock z tekstem wyśrodkowanym. Okno ma rozmiar 500 × 500, a po jego powiększeniu komórki rosną równo.
pokaż wskazówkę
- Dwie zagnieżdżone pętle od 0 do 10 i
Grid.SetRow/Grid.SetColumndla każdego napisu. Komórkę (0, 0) zostaw pustą. - Iloczyn w komórce (w, k) to
w * k. Kwadrat — gdyw == k. - Wiersze i kolumny możesz zapisać w XAML — kodem dodajesz tylko napisy.
Plansza 3 × 3 z dziewięciu pustych przycisków (czcionka 40), pod nią napis „Ruch: X” i przycisk Nowa gra. Kliknięcie wolnego pola wstawia na zmianę X i O, kliknięcie zajętego nic nie zmienia. Po każdym ruchu program sprawdza 8 linii (3 wiersze, 3 kolumny, 2 przekątne). Przy wygranej pokazuje „Wygrywa X!” i blokuje planszę, przy zapełnionej planszy bez zwycięzcy — „Remis”. Prowadź też wynik meczu: „X: 2 O: 1 remisy: 1”. Wszystkie pola obsługuje jedna metoda.
pokaż wskazówkę
- Planszę połóż w osobnym
Gridz nazwą — łatwo ją wtedy zablokować i przejść po polach przezChildren. - Pola nazwij
P0–P8i w konstruktorze zbierz je w tablicęButton[]. Linie zapisz jako tablicę trójek indeksów, np.{ 0, 4, 8 }. - Wolne pole ma
Contentrównenull.
Co trzeba zapamiętać
- Wiersze i kolumny opisujesz w
Grid.RowDefinitionsiGrid.ColumnDefinitions. - Miejsce ustawiasz w kontrolce:
Grid.Row,Grid.Column. Numeracja od 0, domyślnie 0. - Liczba = rozmiar stały,
Auto= według zawartości,*= część pozostałego miejsca.*i2*dzielą resztę w proporcji 1 : 2. Grid.ColumnSpaniGrid.RowSpanrozciągają kontrolkę w prawo i w dół.- Napisy i przyciski w wierszach
Auto, główna część w wierszu*— wtedy okno dobrze się skaluje. - Siatki można zagnieżdżać; z kodu komórkę ustawiasz przez
Grid.SetRowiGrid.SetColumn.
Dokumentacja: Microsoft Learn — Grid.