Układamy klocki w WPF: Kontenery Grid, StackPanel i pierwsze kontrolki

Przechodzimy do konkretów: jak sprawić, by kontrolki w oknie nie lądowały losowo w lewym górnym rogu, lecz tworzyły czytelny, estetyczny i responsywny interfejs?

Dowiesz się, dlaczego okno w WPF jest zazdrosne o swoją zawartość, czym różni się arkusz Excela od tuby chipsów Pringles oraz jak okiełznać 4 najważniejsze kontrolki każdego formularza.

1. Reguła jednego dziecka: Dlaczego okno potrzebuje kontenera?

Wyobraź sobie, że piszesz w XAML taki oto kod:

<!-- BŁĄD KOMPILACJI! -->
<Window ...>
    <Button Content="Zaloguj" />
    <Button Content="Anuluj" />
</Window>

Próbujesz uruchomić projekt i... Build Failed. Dlaczego?

Klasa Window posiada cechę zwaną regułą pojedynczej zawartości (Single Child Rule). Posiada ona właściwość Content, do której możesz przypisać tylko jeden jedyny element potomny. Jeśli spróbujesz wstawić tam dwa przyciski, kompilator zaprotestuje.

Rozwiązanie problemu:
Do okna wkładamy dokładnie jeden kontener układu (Layout Panel). Taki kontener posiada specjalną kolekcję Children, w której może pomieścić dziesiątki innych kontrolek i zorganizować je na ekranie.

2. Okno zwykłe, modalne i niemodalne.

Zanim ułożymy przyciski, musimy zrozumieć, w jakich oknach je wyświetlamy. W systemach Windows rozróżniamy dwa kluczowe typy okien dialogowych:

  1. Okno / Dialog modalny (w C#: okno.ShowDialog()):
    • Blokuje okno nadrzędne. Użytkownik nie może kliknąć w okno główne, dopóki nie zamknie okna modalnego lub nie kliknie w nim „OK” / „Anuluj”.
    • Przykłady: Komunikat ostrzegawczy: "Czy na pewno chcesz usunąć bazę danych?", okno wyboru pliku (OpenFileDialog).
  2. Okno / Dialog niemodalny (w C#: okno.Show()):
    • Działa niezależnie. Użytkownik może swobodnie klikać w okno główne oraz w nowo otwarte okno naprzemiennie.
    • Przykłady: Okienko wyszukiwania i zamiany tekstu (Ctrl+F) w Notatniku lub Visual Studio.

      GIMP nie wykorzystuje w tym wypadku dialogu niemodalnego, ale przykład ma na celu pokazać że w dialogach niemodalnych możemy wrócić do rodzica i wykonywać inne akcje.

3. Pojedynek kontenerów: Grid vs StackPanel

WPF oferuje kilka rodzajów kontenerów, ale 90% Twojej pracy na zajęciach oprze się na dwóch bohaterach.

      [ StackPanel ]                       [ Grid ]
  ┌──────────────────┐               ┌────────┬────────┐
  │ Element 1        │               │ [0, 0] │ [0, 1] │
  ├──────────────────┤               ├────────┼────────┤
  │ Element 2        │               │ [1, 0] │ [1, 1] │
  ├──────────────────┤               └────────┴────────┘
  │ Element 3        │                 Wiersze i kolumny
  └──────────────────┘                 jak w Excelu!
   Stos: jeden pod drugim

Opcja A: StackPanel - Tuba chipsów Pringles

Działa jak stos książek lub tuba chipsów - wrzucasz do niego kontrolki, a one same układają się jedna za drugą w jednej linii.

  • Domyślnie układa w pionie: Orientation="Vertical" (kontrolki lądują jedna pod drugą i rozciągają się na pełną szerokość).
  • Może układać w poziomie: Orientation="Horizontal" (kontrolki stają w jednym rzędzie obok siebie).
  • Kiedy używać? Do prostych list, menu z przyciskami, rzędu przycisków „Zapisz” / „Anuluj”.

Opcja B: Grid - Arkusz kalkulacyjny Excel

Najpotężniejszy kontener w WPF. Dzieli przestrzeń okna na dwuwymiarową tabelę złożoną z wierszy (RowDefinitions) i kolumn (ColumnDefinitions).

  • Elementy nie układają się same - musisz podać im adres za pomocą właściwości dołączonych: Grid.Row="numer" oraz Grid.Column="numer" (uwaga: liczymy od zera!).
  • Jak ustalać wymiary w Gridzie?
    • 100 - stała liczba pikseli (sztywny rozmiar, rzadko polecany).
    • Auto - kolumna lub wiersz dostosowuje swój rozmiar dokładnie do tego, ile miejsca potrzebuje kontrolka w środku.
    • * (Gwiazdka / Rozmiar proporcjonalny) - zajmuje całe wolne, pozostałe miejsce. Zapis 2* oznacza „dwa razy więcej miejsca niż 1*”.

4. Wielka Czwórka: Podstawowe kontrolki w WPF

Oto 4 elementy, bez których nie powstanie żaden formularz logowania czy ankieta:

Kontrolka Do czego służy? Kluczowe atrybuty (właściwości)
TextBlock Wyświetlanie tekstu (tylko do odczytu, jak akapit na stronie). Text="Twój napis", FontSize="14"
TextBox Wprowadzanie danych (pole edycyjne, w które użytkownik wpisuje tekst). Text="Wpisz login...", MaxLength="20"
Button Przycisk akcji (wywołuje kod C# po kliknięciu). Content="Kliknij mnie", Click="NazwaMetody"
CheckBox Przełącznik tak/nie (kwadracik do zaznaczania). Content="Zgoda na regulamin", IsChecked="True"

🧠 Ważny detal:
Zauważ, że TextBlock i TextBox mają właściwość Text, ale Button i CheckBox mają Content! Dlaczego? Bo do Content przycisku możesz włożyć nie tylko napis, ale też obrazek, ikonę, a nawet cały inny panel z kontrolkami!

5. Anatomia odstępów: Margin vs Padding

Interfejs, w którym kontrolki dotykają się krawędziami, wygląda nieprofesjonalnie. Do zarządzania przestrzenią służą dwa atrybuty:

  • Margin (Margines zewnętrzny): Odpycha kontrolkę od jej sąsiadów lub krawędzi okna (dystans na zewnątrz).
  • Padding (Margines wewnętrzny): Odsuwa zawartość kontrolki (np. napis) od jej własnych brzegów (dystans do środka).

Sposób zapisu 4 wartości (wskazówki zegara):

Margin="Lewy, Górny, Prawy, Dolny"
Przykład: Margin="10,20,10,0" da 10 px z lewej, 20 px od góry, 10 px z prawej i 0 od dołu.

6. Zobaczmy to w akcji: Prosty panel w StackPanel

Oto gotowy, czysty przykład kodu XAML. Zobacz, jak estetycznie można zorganizować wybór użytkownika:

<Window x:Class="LekcjaDruga.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Wybór Profilu" Height="220" Width="320">
    
    <!-- Kontener nadrzędny układający elementy w pionie z marginesem od krawędzi okna -->
    <StackPanel Orientation="Vertical" Margin="20">
        
        <TextBlock Text="Wybierz typ konta:" 
                   FontSize="16" 
                   FontWeight="Bold" 
                   Margin="0,0,0,15" />
        
        <Button Content="Uczeń / Praktykant" 
                Height="35" 
                Background="LightSkyBlue" 
                Margin="0,0,0,10" />
        
        <Button Content="Nauczyciel / Egzaminator" 
                Height="35" 
                Background="LightGray" 
                Margin="0,0,0,10" />

        <CheckBox Content="Zapamiętaj mój wybór" 
                  IsChecked="True" />
                  
    </StackPanel>
    
</Window>

Ten sam formularz zrealizowany w kontenerze Grid

<Window x:Class="LekcjaDruga.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Wybór Profilu (wersja Grid)" Height="250" Width="320">
    
    <!-- Kontener nadrzędny Grid z marginesem od brzegu okna -->
    <Grid Margin="20">
        
        <!-- KROK 1: Definiujemy siatkę wierszy (Height="Auto" dopasowuje wysokość do zawartości) -->
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" /> <!-- Wiersz 0: Nagłówek TextBlock -->
            <RowDefinition Height="Auto" /> <!-- Wiersz 1: Pierwszy Button -->
            <RowDefinition Height="Auto" /> <!-- Wiersz 2: Drugi Button -->
            <RowDefinition Height="Auto" /> <!-- Wiersz 3: CheckBox -->
        </Grid.RowDefinitions>

        <!-- KROK 2: Rozmieszczamy kontrolki, przypisując im numer wiersza (Grid.Row) -->
        
        <!-- Wiersz 0 -->
        <TextBlock Grid.Row="0"
                   Text="Wybierz typ konta:" 
                   FontSize="16" 
                   FontWeight="Bold" 
                   Margin="0,0,0,15" />
        
        <!-- Wiersz 1 -->
        <Button Grid.Row="1"
                Content="Uczeń / Praktykant" 
                Height="35" 
                Background="LightSkyBlue" 
                Margin="0,0,0,10" />
        
        <!-- Wiersz 2 -->
        <Button Grid.Row="2"
                Content="Nauczyciel / Egzaminator" 
                Height="35" 
                Background="LightGray" 
                Margin="0,0,0,10" />

        <!-- Wiersz 3 -->
        <CheckBox Grid.Row="3"
                  Content="Zapamiętaj mój wybór" 
                  IsChecked="True" />
                  
    </Grid>
    
</Window>