WrapPanel i UniformGrid
Dwa panele do układania wielu podobnych elementów. WrapPanel ustawia je jeden za drugim i przenosi do nowej linii, gdy zabraknie miejsca — jak słowa w akapicie. UniformGrid dzieli obszar na równe komórki i wypełnia je po kolei, bez numerowania wierszy i kolumn. Dzięki nim plansza, klawiatura czy sala kinowa to kilka linijek pętli.
Czego się dziś nauczysz
- Ułożysz elementy w
WrapPanel, który przenosi je do nowej linii, i ustawisz im wspólny rozmiar. - Zbudujesz siatkę równych komórek w
UniformGridzRowsiColumns. - Przesuniesz pierwszy element
UniformGrido kilka komórek przezFirstColumn. - Wypełnisz oba panele w pętli i odczytasz stan elementów, przechodząc po
Children. - Dobierzesz panel do zadania:
StackPanel,WrapPanel,UniformGridczyGrid.
WrapPanel — z zawijaniem
<WrapPanel Margin="10">
<Button Content="matematyka" Margin="0,0,6,6" Padding="8,2"/>
<Button Content="polski" Margin="0,0,6,6" Padding="8,2"/>
<Button Content="programowanie" Margin="0,0,6,6" Padding="8,2"/>
<Button Content="sieci" Margin="0,0,6,6" Padding="8,2"/>
<Button Content="WF" Margin="0,0,6,6" Padding="8,2"/>
<Button Content="angielski" Margin="0,0,6,6" Padding="8,2"/>
</WrapPanel>
zwęź okno — przyciski przejdą do kolejnych linii
Każdy element ma swój naturalny rozmiar; panel ustawia je w linii od lewej do prawej, a gdy następny się nie mieści — zaczyna nową linię. Dodatkowe ustawienia:
| Właściwość | Co robi |
|---|---|
Orientation="Vertical" | Układa elementy w kolumnie z góry na dół, a gdy zabraknie wysokości — zaczyna nową kolumnę. Domyślnie Horizontal. |
ItemWidth, ItemHeight | Każdy element dostaje taki sam „slot”, np. ItemWidth="80". Elementy tworzą wtedy równe kolumny, choć nadal się zawijają. |
UniformGrid — równe komórki
UniformGrid to uproszczony Grid: podajesz tylko liczbę wierszy i kolumn, a wszystkie komórki są równe. Elementy nie mają Grid.Row ani Grid.Column — trafiają do komórek po kolei, wierszami od lewej do prawej:
<UniformGrid Rows="2" Columns="3" Margin="10">
<Button Content="1" Margin="3"/>
<Button Content="2" Margin="3"/>
<Button Content="3" Margin="3"/>
<Button Content="4" Margin="3"/>
<Button Content="5" Margin="3"/>
</UniformGrid>
5 przycisków w siatce 2 × 3 — ostatnia komórka zostaje pusta
| Ustawienie | Efekt |
|---|---|
tylko Columns="3" | Wierszy jest tyle, ile potrzeba: 7 elementów → 3 wiersze. |
tylko Rows="2" | Kolumn jest tyle, ile potrzeba. |
| bez obu | Panel sam tworzy siatkę „prawie kwadratową”: 9 elementów → 3 × 3. |
FirstColumn="2" | Pierwszy element trafia do kolumny 2 pierwszego wiersza, dwie pierwsze komórki zostają puste. Przydaje się w kalendarzu, gdy miesiąc nie zaczyna się w poniedziałek. |
Komórki dzielą całe dostępne miejsce po równo i rosną razem z oknem. Jeśli elementów jest więcej niż komórek, nadmiarowe nie zostaną pokazane.
Wypełnianie z kodu
Oba panele świetnie współpracują z pętlą — dodajesz elementy przez Children.Add, a panel sam je rozmieszcza. Stan elementów sprawdzasz, przechodząc po Children w foreach:
// w XAML: <UniformGrid x:Name="Plansza" Rows="4" Columns="4"/>
for (int i = 1; i <= 16; i++)
{
Button pole = new Button { Content = i, Margin = new Thickness(2), Tag = "wolne" };
pole.Click += Pole_Click;
Plansza.Children.Add(pole);
}
// ile pól jest zaznaczonych?
int zaznaczone = 0;
foreach (Button pole in Plansza.Children)
{
if ((string)pole.Tag == "zaznaczone")
{
zaznaczone++;
}
}
Zapis foreach (Button pole in Plansza.Children) zakłada, że w panelu są same przyciski — każdy element jest rzutowany na Button. Gdyby był tam też napis, program zakończy się wyjątkiem InvalidCastException.
Do zapamiętania stanu elementu dobrze nadaje się Tag z krótkim napisem („wolne”, „zaznaczone”). Odczyt wymaga rzutowania: (string)pole.Tag.
Który panel wybrać
| Panel | Układa | Typowe użycie |
|---|---|---|
StackPanel | w jednej linii, bez zawijania | pionowa lista, rząd przycisków |
WrapPanel | w linii z zawijaniem, elementy różnej wielkości | tagi, żetony, galeria miniatur, klawiatura ekranowa |
UniformGrid | w równych komórkach, po kolei | plansza gry, klawiatura numeryczna, kalendarz, sala |
Grid | w komórkach o różnych rozmiarach, z łączeniem | układ całego okna, formularz |
Wybór miejsc w kinie
Sala ma 5 rzędów po 8 miejsc. Przyciski miejsc tworzy pętla w konstruktorze i wkłada do UniformGrid. Kliknięcie wolnego miejsca je zaznacza (najwyżej 6), ponowne — odznacza. Wybrane miejsca pojawiają się pod salą jako zielone żetony w WrapPanel, a na dole widać cenę. Po rezerwacji miejsca stają się nieaktywne.
trzy miejsca wybrane, pięć wcześniej sprzedanych (szare)
<Window x:Class="Lekcja12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Kino „Sokół” — wybór miejsc" Height="480" Width="520"
MinWidth="420" MinHeight="420"
WindowStartupLocation="CenterScreen">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- ekran -->
<RowDefinition Height="*"/> <!-- sala -->
<RowDefinition Height="Auto"/> <!-- wybrane miejsca -->
<RowDefinition Height="Auto"/> <!-- podsumowanie -->
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="LightGray" CornerRadius="0,0,20,20"
Padding="4" Margin="40,0,40,10">
<TextBlock Text="EKRAN" HorizontalAlignment="Center" Foreground="DimGray"/>
</Border>
<!-- Sala: 5 rzędów po 8 miejsc, przyciski dodaje konstruktor -->
<UniformGrid x:Name="Sala" Grid.Row="1" Rows="5" Columns="8"/>
<!-- Wybrane miejsca jako „żetony”, zawijane do nowej linii -->
<StackPanel Grid.Row="2" Margin="0,10,0,0">
<TextBlock Text="Wybrane miejsca:" FontWeight="Bold"/>
<WrapPanel x:Name="Wybrane" Margin="0,4,0,0"/>
</StackPanel>
<Grid Grid.Row="3" Margin="0,10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="Podsumowanie" VerticalAlignment="Center" FontSize="14"/>
<Button x:Name="Rezerwuj" Grid.Column="1" Content="Rezerwuj"
Width="100" Height="30" Click="Rezerwuj_Click"/>
</Grid>
</Grid>
</Window>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Lekcja12;
public partial class MainWindow : Window
{
private const int Rzedy = 5;
private const int MiejscWRzedzie = 8;
private const int Limit = 6;
private const decimal Cena = 25.00m;
// miejsca sprzedane wcześniej
private readonly string[] sprzedane = { "A4", "A5", "C1", "C2", "E7" };
public MainWindow()
{
InitializeComponent();
for (int r = 0; r < Rzedy; r++)
{
char litera = (char)('A' + r); // 0 → 'A', 1 → 'B', …
for (int m = 1; m <= MiejscWRzedzie; m++)
{
string nazwa = $"{litera}{m}";
Button miejsce = new Button
{
Content = nazwa,
Margin = new Thickness(3),
Tag = "wolne",
Background = Brushes.WhiteSmoke
};
miejsce.Click += Miejsce_Click;
if (sprzedane.Contains(nazwa))
{
OznaczJakoZajete(miejsce);
}
Sala.Children.Add(miejsce); // UniformGrid sam wybiera komórkę
}
}
Odswiez();
}
private void Miejsce_Click(object sender, RoutedEventArgs e)
{
Button miejsce = (Button)sender;
if ((string)miejsce.Tag == "wybrane")
{
miejsce.Tag = "wolne";
miejsce.Background = Brushes.WhiteSmoke;
}
else if (LiczbaWybranych() < Limit)
{
miejsce.Tag = "wybrane";
miejsce.Background = Brushes.LightGreen;
}
else
{
MessageBox.Show($"Jedna rezerwacja to najwyżej {Limit} miejsc.", "Limit miejsc",
MessageBoxButton.OK, MessageBoxImage.Information);
}
Odswiez();
}
private void Rezerwuj_Click(object sender, RoutedEventArgs e)
{
int ile = LiczbaWybranych();
MessageBoxResult odp = MessageBox.Show($"Zarezerwować {ile} miejsc za {ile * Cena:F2} zł?",
"Rezerwacja", MessageBoxButton.YesNo,
MessageBoxImage.Question);
if (odp != MessageBoxResult.Yes)
{
return;
}
foreach (Button miejsce in Sala.Children)
{
if ((string)miejsce.Tag == "wybrane")
{
OznaczJakoZajete(miejsce);
}
}
Odswiez();
}
// Przebudowuje żetony i podsumowanie na podstawie stanu przycisków
private void Odswiez()
{
Wybrane.Children.Clear();
foreach (Button miejsce in Sala.Children)
{
if ((string)miejsce.Tag == "wybrane")
{
Border zeton = new Border
{
Background = Brushes.LightGreen,
CornerRadius = new CornerRadius(10),
Padding = new Thickness(10, 2, 10, 2),
Margin = new Thickness(0, 0, 6, 6),
Child = new TextBlock { Text = (string)miejsce.Content }
};
Wybrane.Children.Add(zeton);
}
}
int ile = Wybrane.Children.Count;
Podsumowanie.Text = ile == 0
? "Kliknij wolne miejsce, aby je wybrać."
: $"Miejsc: {ile} × {Cena:F2} zł = {ile * Cena:F2} zł";
Rezerwuj.IsEnabled = ile > 0;
}
private int LiczbaWybranych()
{
int ile = 0;
foreach (Button miejsce in Sala.Children)
{
if ((string)miejsce.Tag == "wybrane")
{
ile++;
}
}
return ile;
}
private void OznaczJakoZajete(Button miejsce)
{
miejsce.Tag = "zajete";
miejsce.IsEnabled = false; // nieaktywny przycisk WPF rysuje na szaro
}
}
Co dzieje się po kolei
- W XAML
UniformGridsali jest pusty — ma tylkoRows="5"iColumns="8". Stoi w wierszu*, więc przyciski rosną razem z oknem. - Konstruktor tworzy 40 przycisków w dwóch pętlach.
(char)('A' + r)zamienia numer rzędu na literę: znaki to liczby, więc 'A’ + 1 to 'B’. - Przyciski są dodawane po kolei, a
UniformGridsam wypełnia nimi wiersze — nie ma żadnegoGrid.SetRow. sprzedane.Contains(nazwa)sprawdza, czy tablica zawiera nazwę miejsca. Sprzedane miejsca od razu stają się nieaktywne — WPF rysuje je na szaro i nie wywołuje dla nichClick.- Stan miejsca jest w
Tag: „wolne”, „wybrane” albo „zajete”. Kolor tła tylko ten stan pokazuje. Miejsce_Clickodznacza wybrane miejsce, a wolne zaznacza tylko wtedy, gdy nie przekroczono limitu.Odswiezbuduje żetony od nowa: czyściWrapPaneli dodaje zaokrąglonyBorderdla każdego wybranego miejsca. Liczba żetonów to liczba wybranych miejsc.- Przycisk Rezerwuj jest aktywny tylko wtedy, gdy coś wybrano. Po potwierdzeniu wszystkie wybrane miejsca dostają stan „zajete”.
Sprawdź trzy rzeczy
- Wybierz 6 miejsc i zwęź okno. Żetony w
WrapPanelprzejdą do drugiej linii. - Usuń z XAML
Rows="5". Nic się nie zmieni — przy 8 kolumnach i 40 przyciskach panel sam policzy 5 wierszy. - Zmień
Columns="8"naColumns="10". Rzędy „rozjadą się”: A9 i A10 to w rzeczywistości B1 i B2, bo panel wypełnia komórki po kolei.
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
WrapPanel.Orientation | Horizontal, Vertical | Kierunek układania; zawija do nowej linii / kolumny. |
ItemWidth, ItemHeight | double | Jednakowy slot dla każdego elementu WrapPanel. |
UniformGrid.Rows, Columns | int | Liczba wierszy / kolumn; jedną z nich panel może policzyć sam. |
FirstColumn | int | Ile komórek pierwszego wiersza zostaje pustych. |
foreach (Button b in Panel.Children) | pętla | Przejście po wszystkich elementach panelu z rzutowaniem na Button. |
tablica.Contains(x) | bool | Czy tablica zawiera wartość x. |
(char)('A' + n) | char | Litera przesunięta o n od 'A’. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
Grid.Row="1" w elemencie UniformGrid | Ignorowane — kolejność wyznacza tylko pozycja w pliku / w Children. |
więcej elementów niż Rows × Columns | Nadmiarowe elementy nie są widoczne. Zwiększ liczbę wierszy albo usuń Rows. |
WrapPanel w poziomym StackPanel | Nie zawija — dostaje nieskończenie dużo szerokości. Postaw go w Grid albo w pionowym StackPanel. |
foreach (Button b in Panel.Children), gdy w panelu jest też TextBlock | InvalidCastException przy pierwszym napisie. |
if (pole.Tag == "wolne") | Ostrzeżenie CS0252 i porównanie referencji. Rzutuj: (string)pole.Tag == "wolne". |
Background nieaktywnego przycisku | Nie jest widoczny — WPF rysuje nieaktywny przycisk na szaro. |
Zadania
Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Elementy, których jest dużo, twórz w pętli. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
Kiosk w bibliotece nie ma fizycznej klawiatury. U góry pole tylko do odczytu, pod nim WrapPanel z przyciskami wszystkich liter polskiego alfabetu (32 litery, przyciski 40 × 40) utworzonymi w pętli z napisu "AĄBCĆDEĘFGHIJKLŁMNŃOÓPRSŚTUWYZŹŻ". Pod literami przyciski Spacja, ⌫ i Szukaj. Kliknięcie litery dopisuje ją do pola, Szukaj pokazuje „Szukam: …”.
pokaż wskazówkę
foreach (char litera in napis)przejdzie po wszystkich znakach.- Równe przyciski dostaniesz przez
ItemWidth/ItemHeightpanelu alboWidth/Heightprzycisku.
UniformGrid 4 × 4 z szesnastoma kolorowymi przyciskami bez napisów (wybierz kolory z klasy Brushes). Kliknięcie koloru zmienia tło dużego prostokąta podglądu (Border z zaokrąglonymi rogami) obok palety, a pod podglądem pokazuje kod koloru, np. „#FF4682B4”. Wybrany przycisk palety ma grubszą, czarną ramkę.
pokaż wskazówkę
- Pędzle zapisz w tablicy
Brush[]i twórz przyciski w pętli; kliknięty kolor odczytasz zBackgroundprzycisku. - Kod koloru:
Background.ToString(). Ramkę przycisku ustawisz przezBorderBrushiBorderThickness— przed zaznaczeniem nowego przywróć zwykłą ramkę wszystkim.
Kalendarz bieżącego miesiąca: u góry nazwa miesiąca i rok („październik 2026”) oraz przyciski ◀ i ▶ do zmiany miesiąca. Pod spodem wiersz z nazwami dni (Pn–Nd) i UniformGrid z 7 kolumnami, w którym są numery dni. Pierwszy dzień miesiąca stoi pod właściwym dniem tygodnia, soboty i niedziele są czerwone, a dzisiejszy dzień ma żółte tło.
pokaż wskazówkę
- Pierwszy dzień:
new DateTime(rok, miesiac, 1), liczba dni:DateTime.DaysInMonth(rok, miesiac). DayOfWeekliczy od niedzieli (0). Przesunięcie dla tygodnia od poniedziałku:((int)dzien.DayOfWeek + 6) % 7— to wartość dlaFirstColumn.- Nazwę miesiąca da
ToString("MMMM yyyy"). Przy zmianie miesiąca wyczyść panel i zbuduj go od nowa;AddMonths(1)sam przejdzie przez koniec roku.
Pole „Nowy tag” i przycisk Dodaj (Enter). Każdy tag pojawia się jako żeton „#wpf ✕” w WrapPanel. Kliknięcie żetonu usuwa go. Tag zapisuje się małymi literami, bez spacji (spacje zamieniają się na myślniki), a znak # dodaje program. Nie można dodać tagu pustego ani takiego, który już jest. Najwyżej 10 tagów — potem pole i przycisk się blokują, a po usunięciu tagu odblokowują.
pokaż wskazówkę
- Najprościej, gdy żeton jest zwykłym przyciskiem z napisem „#wpf ✕” i
Padding. Kliknięty przycisk usuniesz przezChildren.Remove((Button)sender). Zaokrąglony przycisk zrobisz w lekcji o szablonach. Replace(' ', '-')iToLower()przygotują tekst tagu.
Egzaminator wpisuje liczbę stanowisk w rzędzie (2–8) i liczbę rzędów (2–8), a program rysuje plan sali w UniformGrid. Na egzaminie zdający nie mogą siedzieć obok siebie, więc w każdym rzędzie co drugie stanowisko jest wolne, a w kolejnych rzędach wzór jest przesunięty („szachownica”). Zajęte stanowiska to jasnoniebieskie ramki z numerem zdającego (1, 2, 3…), wolne — szare ramki z napisem „—”. Pod planem: „Maksymalnie zdających: 18”.
pokaż wskazówkę
- Ustaw
RowsiColumnspanelu z kodu, potem wyczyść go i wypełnij w dwóch pętlach. - Szachownica: stanowisko (w, k) jest zajęte, gdy
(w + k) % 2 == 0.
Plansza 5 × 5 przycisków-lampek: zapalona jest żółta, zgaszona — ciemnoszara. Kliknięcie lampki przełącza ją i jej sąsiadów z góry, z dołu, z lewej i z prawej (lampki na brzegu mają mniej sąsiadów). Celem jest zgaszenie wszystkich. Na start zapal lampki według wzoru wpisanego w kodzie, np. środkowy krzyż. Pod planszą: „Ruchy: 7”. Po zgaszeniu wszystkich pokaż „Wygrana w 7 ruchach!” i zablokuj planszę. Przycisk Od nowa przywraca wzór startowy.
pokaż wskazówkę
- W
Tagkażdej lampki zapisz jej numer 0–24. Wiersz tonumer / 5, kolumna —numer % 5. - Stan lampek trzymaj w tablicy
bool[25], a kolory ustawiaj w osobnej metodzie na podstawie tablicy. Lampkę o numerzenznajdziesz jako(Button)Plansza.Children[n]. - Metoda „przełącz (wiersz, kolumna)” niech nic nie robi, gdy współrzędne wychodzą poza planszę — wtedy brzegi obsłużą się same.
Co trzeba zapamiętać
WrapPanelukłada elementy w linii i zawija je, gdy zabraknie miejsca.ItemWidth/ItemHeightdają równe sloty.UniformGriddzieli obszar na równe komórki i wypełnia je po kolei — bezGrid.RowiGrid.Column.- Wystarczy podać
RowsalboColumns;FirstColumnzostawia puste komórki na początku. - Oba panele najlepiej wypełniać w pętli, a stan elementów odczytywać w
foreachpoChildren. - Stan elementu (wolne, wybrane…) zapisuj w
Tag, a kolor niech go tylko pokazuje.
Dokumentacja: Microsoft Learn — WrapPanel, UniformGrid.