Przejdź do treści
School IT / Przestrzeń do nauki

GroupBox i Expander

Długi formularz z kilkunastoma polami jeden pod drugim jest trudny do ogarnięcia wzrokiem. Dwie proste kontrolki porządkują go: GroupBox otacza powiązane pola ramką z tytułem, a Expander chowa rzadziej używane opcje pod nagłówkiem, który można rozwinąć jednym kliknięciem. Arkusze INF.04 często wymagają właśnie takiego podziału — „dane osobowe”, „wybór usługi”, „podsumowanie”.

XAML C# GroupBox Expander Header IsExpanded 90 min
CEL LEKCJI

Czego się dziś nauczysz

  • Pogrupujesz pola formularza w ramki z tytułem.
  • Umieścisz w nagłówku grupy pole wyboru, które włącza i wyłącza całą grupę.
  • Schowasz rzadziej używane opcje w sekcji zwijanej.
  • Rozwiniesz i zwiniesz sekcję z kodu oraz zareagujesz na jej rozwinięcie.
  • Zbudujesz „akordeon” — kilka sekcji, z których otwarta jest tylko jedna.
TEORIA

GroupBox

GroupBox to ramka z tytułem wpisanym w górną krawędź. Tak jak Border z lekcji 11 ma jedno dziecko — zwykle panel z polami. Tytuł podaje się w Header:

MainWindow.xaml
<StackPanel Margin="12" Width="260">
    <GroupBox Header="Dane ucznia" Padding="8">
        <StackPanel>
            <TextBlock Text="Imię i nazwisko:"/>
            <TextBox x:Name="Nazwisko"/>
            <TextBlock Text="Klasa:" Margin="0,6,0,0"/>
            <TextBox x:Name="Klasa" Width="60" HorizontalAlignment="Left"/>
        </StackPanel>
    </GroupBox>

    <GroupBox Header="Wyjazd" Padding="8" Margin="0,8,0,0">
        <StackPanel>
            <RadioButton Content="Zielona szkoła" IsChecked="True"/>
            <RadioButton Content="Wycieczka jednodniowa"/>
        </StackPanel>
    </GroupBox>
</StackPanel>
Zgłoszenie–□✕
Dane ucznia
Imię i nazwisko:
Ola Nowak
Klasa:
3TI
Wyjazd
Zielona szkoła
Wycieczka jednodniowa

dwie grupy — każda ma swoją ramkę i tytuł

Padding odsuwa zawartość od ramki, Margin — ramkę od sąsiadów. Właściwości ustawione na GroupBox dziedziczą się w dół jak w lekcji 5: IsEnabled="False" wyszarza całą grupę razem z tytułem, a FontSize zmienia tytuł i wszystkie pola.

GroupBox a przyciski opcji

Ramka nie łączy przycisków opcji w grupę — robi to panel, w którym leżą (lekcja 15). Dwa panele w dwóch ramkach to dwie niezależne grupy, więc GroupName nie jest tu potrzebne.

TEORIA

Nagłówek z kontrolką

Header nie musi być tekstem. Zapisany jako element (jak Grid.RowDefinitions w lekcji 9) może zawierać dowolną kontrolkę — najczęściej pole wyboru, które włącza grupę:

MainWindow.xaml
<GroupBox Padding="8">
    <GroupBox.Header>
        <CheckBox x:Name="Faktura" Content="Chcę fakturę" Click="Faktura_Click"/>
    </GroupBox.Header>
    <StackPanel x:Name="DaneFaktury" IsEnabled="False">
        <TextBlock Text="NIP:"/>
        <TextBox x:Name="Nip"/>
    </StackPanel>
</GroupBox>
MainWindow.xaml.cs
private void Faktura_Click(object sender, RoutedEventArgs e)
{
    DaneFaktury.IsEnabled = Faktura.IsChecked == true;
}

Wyłączamy panel w środku, a nie cały GroupBox. Gdyby wyłączyć ramkę, wyłączyłoby się także pole wyboru w nagłówku i nie dałoby się już grupy włączyć z powrotem.

Zamówienie–□✕
Chcę fakturę
NIP:

pole niezaznaczone — zawartość grupy jest wyszarzona

TEORIA

Expander

Expander to nagłówek ze strzałką i jedno dziecko, które widać tylko po rozwinięciu. Zwinięta zawartość nie zajmuje miejsca — elementy pod spodem przesuwają się w górę, jak przy Visibility="Collapsed".

MainWindow.xaml
<StackPanel Margin="12" Width="280">
    <TextBlock Text="Nazwa pliku:"/>
    <TextBox x:Name="NazwaPliku" Text="raport"/>

    <Expander x:Name="Zaawansowane" Header="Ustawienia zaawansowane" Margin="0,10,0,0">
        <StackPanel Margin="20,6,0,0">
            <CheckBox Content="Dodaj datę do nazwy"/>
            <CheckBox Content="Nadpisuj bez pytania"/>
        </StackPanel>
    </Expander>

    <Button Content="Zapisz" Margin="0,10,0,0"/>
</StackPanel>
Eksport–□✕
Nazwa pliku:
raport
⌄ Ustawienia zaawansowane
Zapisz
Eksport–□✕
Nazwa pliku:
raport
⌃ Ustawienia zaawansowane
Dodaj datę do nazwy
Nadpisuj bez pytania
Zapisz

ta sama sekcja zwinięta i rozwinięta — przycisk przesuwa się w dół

WłaściwośćZnaczenie
HeaderTekst lub kontrolka obok strzałki. Można go zmieniać z kodu.
IsExpandedtrue — rozwinięty. Domyślnie false. Ustawienie z kodu rozwija lub zwija sekcję.
ExpandDirectionDown (domyślnie), Up, Left, Right — w którą stronę wysuwa się zawartość.

Gdy okno ma stałą wysokość, rozwinięta sekcja może się nie zmieścić. Wtedy pomaga SizeToContent="Height" (okno rośnie razem z zawartością, lekcja 10) albo ScrollViewer wokół całego formularza (lekcja 11).

TEORIA

Zdarzenia Expanded i Collapsed

Expanded zachodzi po rozwinięciu, Collapsed — po zwinięciu, niezależnie od tego, czy kliknął użytkownik, czy kod zmienił IsExpanded. Typowe zastosowanie to akordeon: kilka sekcji, z których otwarta jest najwyżej jedna. Wszystkie sekcje obsługuje ta sama metoda:

MainWindow.xaml.cs
// w XAML każdy z trzech Expanderów ma Expanded="Sekcja_Expanded"
private void Sekcja_Expanded(object sender, RoutedEventArgs e)
{
    // zwijamy wszystkie sekcje oprócz tej, która się rozwinęła
    if (sender != Krok1) Krok1.IsExpanded = false;
    if (sender != Krok2) Krok2.IsExpanded = false;
    if (sender != Krok3) Krok3.IsExpanded = false;
}

private void Krok1_Collapsed(object sender, RoutedEventArgs e)
{
    Krok1.Header = $"1. Dane ucznia — {Nazwisko.Text}";   // podsumowanie w nagłówku
}

Zwinięcie sekcji z kodu nie wywołuje ponownie Expanded, więc nie ma tu zapętlenia, przed którym chroniła flaga w lekcji 32.

PRZYKŁAD

Zamówienie sprzętu do pracowni

Szkoła zamawia komputery do pracowni. Formularz jest podzielony na trzy ramki: zestaw, liczba stanowisk i dodatki (włączane polem wyboru w nagłówku). Rzadziej zmieniane opcje — montaż i gwarancja — oraz rozpisanie ceny leżą w dwóch sekcjach zwijanych działających jak akordeon. Suma jest zawsze widoczna na dole, a nagłówek zwiniętej sekcji „Szczegóły ceny” pokazuje cenę jednego stanowiska.

Zamówienie sprzętu do pracowni–✕
Zestaw komputerowy
Biurowy — 2 499 zł
Programisty — 3 499 zł
Grafika — 4 999 zł
Liczba stanowisk
12
Dodatki do każdego stanowiska
Monitor 24″ — 599 zł
Słuchawki z mikrofonem — 149 zł
Kamera internetowa — 199 zł
⌄ Opcje dodatkowe
⌃ Szczegóły ceny — 12 stanowisk, za jedno 4 137,76 zł
Zestawy: 12 × 3 499,00 zł
Dodatki: 12 × 599,00 zł
Rabat od 10 stanowisk (5%): −2 458,80 zł
Gwarancja (+5%): 2 335,86 zł
Montaż: 600,00 zł
Razem: 49 653,06 złZamów

rozwinięta sekcja „Szczegóły ceny”; „Opcje dodatkowe” zwinęły się same (montaż i gwarancja 3 lata są w nich zaznaczone)

MainWindow.xaml — pełny plik
<Window x:Class="Lekcja33.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Zamówienie sprzętu do pracowni" Width="440" SizeToContent="Height"
        ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen">

    <StackPanel Margin="12">

        <!-- 1. zestaw: grupa przycisków opcji -->
        <GroupBox Header="Zestaw komputerowy" Padding="8">
            <StackPanel x:Name="Zestawy">
                <RadioButton Content="Biurowy — 2 499 zł" Tag="2499" IsChecked="True" Click="Zmiana"/>
                <RadioButton Content="Programisty — 3 499 zł" Tag="3499" Margin="0,4,0,0" Click="Zmiana"/>
                <RadioButton Content="Grafika — 4 999 zł" Tag="4999" Margin="0,4,0,0" Click="Zmiana"/>
            </StackPanel>
        </GroupBox>

        <!-- 2. liczba stanowisk -->
        <GroupBox Header="Liczba stanowisk" Padding="8" Margin="0,8,0,0">
            <DockPanel>
                <TextBlock x:Name="IleOpis" DockPanel.Dock="Right" Width="40" TextAlignment="Right"/>
                <Slider x:Name="Ile" Minimum="1" Maximum="16" Value="8"
                        IsSnapToTickEnabled="True" TickFrequency="1"
                        ValueChanged="Ile_ValueChanged"/>
            </DockPanel>
        </GroupBox>

        <!-- 3. nagłówek grupy to pole wyboru, które włącza zawartość -->
        <GroupBox Padding="8" Margin="0,8,0,0">
            <GroupBox.Header>
                <CheckBox x:Name="ZDodatkami" Content="Dodatki do każdego stanowiska"
                          FontWeight="Bold" Click="Zmiana"/>
            </GroupBox.Header>
            <StackPanel x:Name="Dodatki" IsEnabled="False">
                <CheckBox Content="Monitor 24″ — 599 zł" Tag="599" Click="Zmiana"/>
                <CheckBox Content="Słuchawki z mikrofonem — 149 zł" Tag="149" Margin="0,4,0,0" Click="Zmiana"/>
                <CheckBox Content="Kamera internetowa — 199 zł" Tag="199" Margin="0,4,0,0" Click="Zmiana"/>
            </StackPanel>
        </GroupBox>

        <!-- 4. sekcje zwijane: otwarta może być tylko jedna -->
        <Expander x:Name="Opcje" Header="Opcje dodatkowe" Margin="0,10,0,0"
                  Expanded="Expander_Expanded">
            <StackPanel Margin="20,6,0,0">
                <CheckBox x:Name="Montaz" Content="Montaż w pracowni — 50 zł za stanowisko"
                          Click="Zmiana"/>
                <TextBlock Text="Gwarancja:" Margin="0,8,0,2"/>
                <ComboBox x:Name="Gwarancja" Width="200" HorizontalAlignment="Left"
                          SelectedIndex="0" SelectionChanged="Gwarancja_SelectionChanged">
                    <ComboBoxItem Content="2 lata (w cenie)" Tag="0"/>
                    <ComboBoxItem Content="3 lata (+5%)" Tag="5"/>
                    <ComboBoxItem Content="5 lat (+12%)" Tag="12"/>
                </ComboBox>
            </StackPanel>
        </Expander>

        <Expander x:Name="Szczegoly" Header="Szczegóły ceny" Margin="0,6,0,0"
                  Expanded="Expander_Expanded">
            <TextBlock x:Name="SzczegolyTekst" Margin="20,6,0,0" LineHeight="20"/>
        </Expander>

        <!-- podsumowanie zawsze widoczne -->
        <Border Background="#FFEFF6FF" CornerRadius="6" Padding="10" Margin="0,12,0,0">
            <DockPanel>
                <Button x:Name="Zamow" DockPanel.Dock="Right" Content="Zamów" Width="90"
                        Click="Zamow_Click"/>
                <TextBlock x:Name="Razem" FontSize="16" FontWeight="Bold"
                           VerticalAlignment="Center"/>
            </DockPanel>
        </Border>
    </StackPanel>
</Window>
MainWindow.xaml.cs — pełny plik
using System.Windows;
using System.Windows.Controls;

namespace Lekcja33;

public partial class MainWindow : Window
{
    const decimal CenaMontazu = 50;
    const int ProgRabatu = 10;          // od tylu stanowisk rabat 5%

    public MainWindow()
    {
        InitializeComponent();
        Przelicz();
    }

    // Wspólna obsługa przycisków opcji i pól wyboru
    private void Zmiana(object sender, RoutedEventArgs e)
    {
        Przelicz();
    }

    private void Ile_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        Przelicz();
    }

    private void Gwarancja_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        Przelicz();
    }

    // Akordeon: rozwinięcie jednej sekcji zwija drugą
    private void Expander_Expanded(object sender, RoutedEventArgs e)
    {
        if (sender == Opcje)
        {
            Szczegoly.IsExpanded = false;
        }
        else
        {
            Opcje.IsExpanded = false;
        }
    }

    private void Przelicz()
    {
        // zdarzenia przychodzą już podczas wczytywania XAML
        if (Razem == null)
        {
            return;
        }

        int ile = (int)Ile.Value;
        IleOpis.Text = $"{ile}";

        // zestaw
        RadioButton? zestaw = Zaznaczony();
        decimal cenaZestawu = 0;
        if (zestaw != null)
        {
            cenaZestawu = decimal.Parse($"{zestaw.Tag}");
        }

        // dodatki liczą się tylko wtedy, gdy grupa jest włączona
        bool zDodatkami = ZDodatkami.IsChecked == true;
        Dodatki.IsEnabled = zDodatkami;
        decimal cenaDodatkow = 0;
        if (zDodatkami)
        {
            foreach (CheckBox dodatek in Dodatki.Children)
            {
                if (dodatek.IsChecked == true)
                {
                    cenaDodatkow += decimal.Parse($"{dodatek.Tag}");
                }
            }
        }

        decimal sprzet = (cenaZestawu + cenaDodatkow) * ile;

        decimal rabat = 0;
        if (ile >= ProgRabatu)
        {
            rabat = sprzet * 5 / 100;
        }

        decimal montaz = 0;
        if (Montaz.IsChecked == true)
        {
            montaz = CenaMontazu * ile;
        }

        decimal procentGwarancji = 0;
        ComboBoxItem? gwarancja = Gwarancja.SelectedItem as ComboBoxItem;
        if (gwarancja != null)
        {
            procentGwarancji = decimal.Parse($"{gwarancja.Tag}");
        }
        decimal dodatekGwarancji = (sprzet - rabat) * procentGwarancji / 100;

        decimal razem = sprzet - rabat + dodatekGwarancji + montaz;

        SzczegolyTekst.Text =
            $"Zestawy: {ile} × {cenaZestawu:N2} zł\n" +
            $"Dodatki: {ile} × {cenaDodatkow:N2} zł\n" +
            $"Rabat od {ProgRabatu} stanowisk (5%): −{rabat:N2} zł\n" +
            $"Gwarancja (+{procentGwarancji}%): {dodatekGwarancji:N2} zł\n" +
            $"Montaż: {montaz:N2} zł";

        Razem.Text = $"Razem: {razem:N2} zł";

        // nagłówek zwiniętej sekcji też może coś mówić
        Szczegoly.Header = $"Szczegóły ceny — {ile} stanowisk, za jedno {razem / ile:N2} zł";
    }

    private RadioButton? Zaznaczony()
    {
        foreach (RadioButton opcja in Zestawy.Children)
        {
            if (opcja.IsChecked == true)
            {
                return opcja;
            }
        }
        return null;
    }

    private void Zamow_Click(object sender, RoutedEventArgs e)
    {
        MessageBoxResult odp = MessageBox.Show(
            $"{Razem.Text}\n\nCzy wysłać zamówienie do działu zakupów?",
            "Potwierdzenie", MessageBoxButton.YesNo, MessageBoxImage.Question);

        if (odp == MessageBoxResult.Yes)
        {
            MessageBox.Show("Zamówienie zostało wysłane.", "Gotowe",
                MessageBoxButton.OK, MessageBoxImage.Information);
        }
    }
}

Co dzieje się po kolei

  • Każda ramka zawiera jeden panel. Przyciski opcji zestawu leżą w panelu Zestawy, więc tworzą jedną grupę; ceny są zapisane w Tag, jak w lekcjach 14 i 15.
  • Nagłówek trzeciej ramki to pole wyboru ZDodatkami. Przelicz włącza albo wyłącza panel Dodatki i liczy dodatki tylko wtedy, gdy grupa jest włączona — zaznaczenia w wyłączonej grupie zostają, ale się nie liczą.
  • Wszystkie pola wywołują Przelicz, a osłona Razem == null chroni przed zdarzeniami z czasu wczytywania XAML (suwak z Value="8", lista z SelectedIndex="0").
  • Oba Expandery mają tę samą metodę Expander_Expanded: rozwinięcie jednego zwija drugi.
  • Nagłówek sekcji „Szczegóły ceny” jest ustawiany z kodu przy każdym przeliczeniu — nawet zwinięta sekcja coś mówi.
  • Okno ma stałą szerokość i SizeToContent="Height", więc rośnie i maleje razem z rozwijanymi sekcjami.

Sprawdź trzy rzeczy

  1. Zaznacz monitor, a potem odznacz pole w nagłówku ramki dodatków. Monitor zostaje zaznaczony, ale cena spada.
  2. Usuń SizeToContent="Height" i ustaw Height="480". Po rozwinięciu sekcji przycisk Zamów zniknie za dolną krawędzią.
  3. Nadaj trzeciej ramce nazwę RamkaDodatkow i w Przelicz zamień Dodatki.IsEnabled na RamkaDodatkow.IsEnabled. Pole w nagłówku od razu jest wyszarzone i nie da się go zaznaczyć.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementTypCo robi
GroupBoxkontrolkaRamka z tytułem wokół jednego dziecka.
ExpanderkontrolkaNagłówek ze strzałką i zwijana zawartość.
HeaderobjectTytuł: tekst albo kontrolka (<GroupBox.Header>).
IsExpandedboolCzy sekcja jest rozwinięta; można ustawiać z kodu.
ExpandDirectionwyliczenieDown, Up, Left, Right.
Expanded, CollapsedzdarzeniaPo rozwinięciu i po zwinięciu; parametr RoutedEventArgs.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
dwa pola bezpośrednio w GroupBoxBłąd XAML — „Właściwość Content jest ustawiona więcej niż raz”. Włóż pola do panelu.
IsEnabled="False" na GroupBox z polem w nagłówkuWyłącza też nagłówek — grupy nie da się włączyć. Wyłączaj panel w środku.
okno o stałej wysokości z kilkoma ExpanderamiRozwinięta zawartość jest ucięta. Użyj SizeToContent albo ScrollViewer.
odczyt pól ze zwiniętej sekcji jako „pustych”Zwinięta zawartość istnieje i zachowuje wartości — jest tylko niewidoczna.
Header zmieniany w Expanded na tekst z polami, które jeszcze są nullNullReferenceException przy IsExpanded="True" w XAML — dodaj osłonę jak w Przelicz.
ZADANIA

Zadania

Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.

ZAD 1Karta zgłoszenia na zawody★☆☆

Formularz zgłoszenia na szkolne zawody pływackie w trzech ramkach: „Zawodnik” (imię, nazwisko, klasa), „Konkurencje” (pola wyboru: 50 m kraulem, 50 m grzbietowym, 100 m dowolnym, sztafeta) i „Opiekun” (nazwisko i telefon). Przycisk Zgłoś sprawdza, czy pola są wypełnione i czy wybrano 1–2 konkurencje (regulamin), i pokazuje podsumowanie w oknie komunikatu.

pokaż wskazówkę
  • Konkurencje policzysz pętlą po Children panelu z ramki, jak w lekcji 14.
ZAD 2Adres do korespondencji★☆☆

Ramka „Adres zamieszkania” (ulica, kod, miasto) i druga ramka, w której nagłówku jest pole wyboru „Inny adres do korespondencji”. Dopóki pole nie jest zaznaczone, zawartość drugiej ramki jest wyszarzona i pokazuje na bieżąco kopię adresu zamieszkania. Po zaznaczeniu pola można wpisać inny adres. Kod pocztowy w obu ramkach jest sprawdzany (format 00-000) przyciskiem Zapisz.

pokaż wskazówkę
  • Kopię zrób w TextChanged pól adresu zamieszkania — tylko wtedy, gdy pole wyboru nie jest zaznaczone.
ZAD 3Często zadawane pytania★☆☆

Okno „Pomoc — e-dziennik” z sześcioma pytaniami jako Expandery (np. „Jak usprawiedliwić nieobecność?”). Odpowiedź to zawijany tekst. Na górze dwa przyciski: Rozwiń wszystkie i Zwiń wszystkie, na dole licznik „Przeczytane: 3 z 6” — pytanie liczy się jako przeczytane po pierwszym rozwinięciu.

pokaż wskazówkę
  • Umieść Expandery w jednym panelu i przechodź pętlą po jego Children. Przeczytane zapamiętaj w Tag sekcji.
ZAD 4Kreator zgłoszenia awarii krok po kroku★★☆

Akordeon z trzech kroków: „1. Gdzie?” (sala, numer stanowiska), „2. Co nie działa?” (lista rodzaju usterki, opis), „3. Kontakt” (nazwisko, e-mail). Pod każdym krokiem przycisk Dalej, który sprawdza pola tego kroku i dopiero wtedy zwija go i rozwija następny. Nagłówek zwiniętego, poprawnie wypełnionego kroku zmienia się na podsumowanie, np. „1. Sala 14, stanowisko 7 ✔”. Przycisk Wyślij jest aktywny dopiero po trzech zaliczonych krokach.

pokaż wskazówkę
  • Stan kroków trzymaj w tablicy bool[3]. Akordeon zrób jedną metodą Expanded jak w teorii.
ZAD 5Wyszukiwarka ofert praktyk★★☆

Na górze pole szukania i lista ofert praktyk zawodowych (klasa Oferta: firma, miasto, branża, liczba miejsc). Pod polem szukania Expander „Filtry” z trzema ramkami: „Miasto” (lista rozwijana z pozycją „wszystkie”), „Branża” (pola wyboru), „Tylko z wolnymi miejscami” (pole wyboru). Lista filtruje się przy każdej zmianie. Nagłówek zwiniętych filtrów pokazuje, ile filtrów jest aktywnych: „Filtry (2 aktywne)”.

pokaż wskazówkę
  • Jedna metoda Filtruj czyści listę i dodaje oferty, które przechodzą wszystkie warunki — jak w lekcji 23.
ZAD 6Konfigurator wycieczki klasowej★★★

Ramki: „Cel” (przyciski opcji z ceną za osobę), „Uczestnicy” (liczba uczniów i opiekunów — suwaki; jeden opiekun na 15 uczniów, opiekunowie jadą za darmo), „Transport” (autokar 50 miejsc / bus 20 miejsc — program liczy, ile pojazdów potrzeba, i dolicza koszt). W nagłówku ramki „Ubezpieczenie” pole wyboru włączające wybór wariantu. Dwie sekcje zwijane: „Program dnia” (lista atrakcji z cenami do zaznaczenia) i „Rozliczenie”, w którym jest koszt całkowity i na ucznia. Przy za małej liczbie opiekunów ramka „Uczestnicy” dostaje czerwony nagłówek z ostrzeżeniem, a przycisk Zatwierdź jest wyłączony.

pokaż wskazówkę
  • Liczba pojazdów: dzielenie z zaokrągleniem w górę — (osoby + miejsca - 1) / miejsca na liczbach całkowitych.
  • Czerwony nagłówek: ustaw z kodu Header na nowy tekst i Foreground ramki — zmieni się też kolor pól w środku, więc przywróć go panelowi.
PODSUMOWANIE

Co trzeba zapamiętać

  • GroupBox — ramka z tytułem; jedno dziecko, zwykle panel.
  • Header może być kontrolką — wtedy wyłączaj panel w środku, nie ramkę.
  • Expander chowa zawartość; IsExpanded rozwija i zwija ją z kodu.
  • Expanded/Collapsed pozwalają zbudować akordeon i podsumowanie w nagłówku.
  • Rozwijane sekcje potrzebują miejsca: SizeToContent albo ScrollViewer.

Dokumentacja: Microsoft Learn — GroupBox, Expander.

Postęp zapisuje się w tej przeglądarce.