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

ComboBox — lista rozwijana

Gdy opcji jest kilkanaście albo więcej, przyciski opcji zajęłyby pół okna. Lista rozwijana pokazuje tylko wybraną pozycję i rozwija się po kliknięciu. To najczęstsza kontrolka wyboru w formularzach egzaminacyjnych: klasa, województwo, waluta, rozmiar. Nauczysz się wypełniać ją w XAML i z kodu, odczytywać wybór numerem i treścią oraz budować listy zależne od siebie.

XAML C# ComboBox SelectedIndex SelectedItem SelectionChanged 90 min
CEL LEKCJI

Czego się dziś nauczysz

  • Wypełnisz listę rozwijaną pozycjami w XAML i w pętli z tablicy.
  • Odczytasz wybór numerem (SelectedIndex) i treścią (SelectedItem), a dodatkowe dane — z Tag pozycji.
  • Zareagujesz na zmianę wyboru zdarzeniem SelectionChanged i obsłużysz stan „nic nie wybrano” (−1).
  • Pozwolisz użytkownikowi wpisać własną wartość (IsEditable).
  • Zbudujesz dwie listy, z których druga zależy od wyboru w pierwszej.
TEORIA

Pozycje w XAML

MainWindow.xaml
<StackPanel Margin="15" Width="220" HorizontalAlignment="Left">
    <Label Content="_Klasa:" Target="{Binding ElementName=Klasa}"/>
    <ComboBox x:Name="Klasa" SelectedIndex="0">
        <ComboBoxItem Content="1TI"/>
        <ComboBoxItem Content="2TI"/>
        <ComboBoxItem Content="3TI"/>
        <ComboBoxItem Content="4TI" Tag="maturalna"/>
    </ComboBox>
</StackPanel>
Klasa–□✕
Klasa:
1TI▾
1TI
2TI
3TI
4TI

rozwinięta lista — pierwsza pozycja wybrana przez SelectedIndex="0"

Każda pozycja to ComboBoxItem z tekstem w Content. Jak każda kontrolka, pozycja może mieć Tag z dodatkowymi danymi. SelectedIndex="0" wybiera pierwszą pozycję; bez niego lista startuje pusta.

TEORIA

Pozycje z kodu

Gdy pozycji jest dużo albo pochodzą z danych, dodajesz je z kodu do kolekcji Items. Najprościej dodawać zwykłe napisy:

MainWindow.xaml.cs
string[] miesiace = { "styczeń", "luty", "marzec", "kwiecień", "maj", "czerwiec",
                      "lipiec", "sierpień", "wrzesień", "październik", "listopad", "grudzień" };

foreach (string m in miesiace)
{
    Miesiac.Items.Add(m);
}
Miesiac.SelectedIndex = DateTime.Now.Month - 1;   // bieżący miesiąc (numeracja od 0!)

// inne operacje na pozycjach
Miesiac.Items.Count;          // ile pozycji
Miesiac.Items.Clear();        // usuń wszystkie
Miesiac.Items.Contains("maj") // czy jest taka pozycja

Items działa podobnie jak Children panelu: Add, Insert, Remove, RemoveAt, Clear, Count. Nie mieszaj w jednej liście pozycji z XAML i z kodu — wybierz jeden sposób.

TEORIA

Odczyt wyboru

WłaściwośćTypCo zawiera
SelectedIndexintNumer wybranej pozycji od 0. −1, gdy nic nie wybrano. Idealny do tablic: kursy[Waluta.SelectedIndex].
SelectedItemobject?Wybrana pozycja: napis (gdy dodawałeś napisy) albo ComboBoxItem (gdy pozycje są w XAML). null, gdy nic nie wybrano.
TextstringTekst widoczny w polu listy — wygodny do wyświetlania.
MainWindow.xaml.cs
if (Klasa.SelectedIndex == -1)
{
    Info.Text = "Wybierz klasę.";
    return;
}

// pozycje z XAML: SelectedItem to ComboBoxItem
ComboBoxItem pozycja = (ComboBoxItem)Klasa.SelectedItem;
Info.Text = $"Klasa {pozycja.Content}";
if ($"{pozycja.Tag}" == "maturalna")
{
    Info.Text += " — w tym roku matura!";
}

// pozycje dodane jako napisy: SelectedItem to string
string miesiac = (string)Miesiac.SelectedItem;
TEORIA

Zdarzenie SelectionChanged

Wywołuje się po każdej zmianie wyboru: kliknięciem, klawiaturą (strzałki na zamkniętej liście też zmieniają wybór!) i z kodu — także przez SelectedIndex="0" w XAML w trakcie InitializeComponent(). Parametr ma typ SelectionChangedEventArgs.

MainWindow.xaml.cs
private void Klasa_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (Info == null || Klasa.SelectedIndex == -1)
    {
        return;     // okno jeszcze się buduje albo lista została wyczyszczona
    }
    Info.Text = $"Wybrano: {Klasa.Text}";   // uwaga: Text może mieć jeszcze starą wartość
}

Text w SelectionChanged

W chwili zdarzenia SelectedIndex i SelectedItem mają już nową wartość, ale Text bywa jeszcze stary. W SelectionChanged odczytuj wybór przez SelectedIndex lub SelectedItem.

TEORIA

Lista z wpisywaniem

Z IsEditable="True" lista staje się połączeniem pola tekstowego i listy: użytkownik może wybrać pozycję albo wpisać własny tekst. Wpisaną wartość odczytujesz z Text, a SelectedIndex wynosi wtedy −1 (chyba że tekst pasuje do którejś pozycji). Podczas pisania lista podpowiada pasujące pozycje.

MainWindow.xaml
<ComboBox x:Name="Miasto" IsEditable="True" Width="200">
    <ComboBoxItem Content="Lębork"/>
    <ComboBoxItem Content="Słupsk"/>
    <ComboBoxItem Content="Gdańsk"/>
</ComboBox>
TEORIA

Listy zależne

Wybór w pierwszej liście zmienia zawartość drugiej — np. województwo i miasto. W SelectionChanged pierwszej listy czyścisz drugą i wypełniasz ją od nowa:

MainWindow.xaml.cs
private readonly string[] pomorskie = { "Gdańsk", "Lębork", "Słupsk" };
private readonly string[] zachodniopomorskie = { "Koszalin", "Szczecin" };

private void Wojewodztwo_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    Miasto.Items.Clear();                 // SelectedIndex miasta wraca do -1

    string[] miasta = Wojewodztwo.SelectedIndex == 0 ? pomorskie : zachodniopomorskie;
    foreach (string m in miasta)
    {
        Miasto.Items.Add(m);
    }

    Miasto.IsEnabled = true;
}
PRZYKŁAD

Kantor walut

Użytkownik wpisuje kwotę i wybiera walutę źródłową i docelową. Wynik i kurs przeliczają się przy każdej zmianie, a przycisk ⇄ zamienia waluty miejscami. Waluty i kursy są w trzech tablicach, a listy wypełnia pętla w konstruktorze.

Kantor szkolny–□✕
Mam:Chcę otrzymać:
250
PLN — złoty▾
⇄
58,14 EUR
EUR — euro▾
1 PLN = 0,2326 EUR
Kursy przykładowe — nie są kursami NBP.

wymiana 250 zł na euro

MainWindow.xaml — pełny plik
<Window x:Class="Lekcja16.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Kantor szkolny" Height="300" Width="520"
        ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen">

    <Grid Margin="15">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Label Grid.Row="0" Grid.Column="0" Content="_Mam:" Target="{Binding ElementName=Kwota}"/>
        <Label Grid.Row="0" Grid.Column="2" Content="_Chcę otrzymać:" Target="{Binding ElementName=WalutaNa}"/>

        <!-- lewa kolumna: kwota i waluta źródłowa -->
        <StackPanel Grid.Row="1" Grid.Column="0">
            <TextBox x:Name="Kwota" Text="100" FontSize="16" Margin="0,0,0,6"
                     TextChanged="Kwota_TextChanged"/>
            <ComboBox x:Name="WalutaZ" SelectionChanged="Waluta_SelectionChanged"/>
        </StackPanel>

        <Button Grid.Row="1" Grid.Column="1" Content="⇄" Width="40" Height="40"
                FontSize="18" Margin="10,0" VerticalAlignment="Bottom"
                ToolTip="Zamień waluty" Click="Zamien_Click"/>

        <!-- prawa kolumna: wynik i waluta docelowa -->
        <StackPanel Grid.Row="1" Grid.Column="2">
            <TextBox x:Name="Wynik" IsReadOnly="True" IsTabStop="False" FontSize="16"
                     Background="#FFF5F5F5" Margin="0,0,0,6"/>
            <ComboBox x:Name="WalutaNa" SelectionChanged="Waluta_SelectionChanged"/>
        </StackPanel>

        <TextBlock x:Name="Kurs" Grid.Row="2" Grid.ColumnSpan="3" Margin="0,15,0,0"
                   Foreground="DimGray"/>

        <TextBlock Grid.Row="3" Grid.ColumnSpan="3" VerticalAlignment="Bottom"
                   FontSize="11" Foreground="Gray"
                   Text="Kursy przykładowe — nie są kursami NBP."/>
    </Grid>
</Window>
MainWindow.xaml.cs — pełny plik
using System.Windows;
using System.Windows.Controls;

namespace Lekcja16;

public partial class MainWindow : Window
{
    // trzy tablice tej samej długości: element i opisuje tę samą walutę
    private readonly string[] kody = { "PLN", "EUR", "USD", "GBP", "CHF", "CZK" };
    private readonly string[] nazwy = { "złoty", "euro", "dolar amerykański", "funt brytyjski", "frank szwajcarski", "korona czeska" };
    private readonly decimal[] kursy = { 1m, 4.30m, 3.95m, 5.10m, 4.55m, 0.17m };   // ile złotych za 1 jednostkę

    public MainWindow()
    {
        InitializeComponent();

        for (int i = 0; i < kody.Length; i++)
        {
            WalutaZ.Items.Add($"{kody[i]} — {nazwy[i]}");
            WalutaNa.Items.Add($"{kody[i]} — {nazwy[i]}");
        }

        WalutaZ.SelectedIndex = 0;    // PLN
        WalutaNa.SelectedIndex = 1;   // EUR
    }

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

    private void Kwota_TextChanged(object sender, TextChangedEventArgs e)
    {
        Przelicz();
    }

    private void Zamien_Click(object sender, RoutedEventArgs e)
    {
        int z = WalutaZ.SelectedIndex;
        WalutaZ.SelectedIndex = WalutaNa.SelectedIndex;
        WalutaNa.SelectedIndex = z;
    }

    private void Przelicz()
    {
        // zdarzenia przychodzą też w trakcie InitializeComponent i wypełniania list
        if (Wynik == null || Kurs == null || WalutaZ.SelectedIndex < 0 || WalutaNa.SelectedIndex < 0)
        {
            return;
        }

        int z = WalutaZ.SelectedIndex;
        int na = WalutaNa.SelectedIndex;

        if (!decimal.TryParse(Kwota.Text, out decimal kwota) || kwota < 0)
        {
            Wynik.Text = "—";
            Kurs.Text = "Wpisz kwotę (liczba nieujemna).";
            return;
        }

        decimal wynik = kwota * kursy[z] / kursy[na];
        Wynik.Text = $"{wynik:F2} {kody[na]}";
        Kurs.Text = $"1 {kody[z]} = {kursy[z] / kursy[na]:F4} {kody[na]}";
    }
}

Co dzieje się po kolei

  • Trzy tablice — kody, nazwy, kursy — mają tę samą długość. Element o numerze i w każdej z nich opisuje tę samą walutę.
  • Konstruktor dodaje do obu list napisy „EUR — euro”. Kolejność pozycji jest taka sama jak w tablicach, więc SelectedIndex listy jest jednocześnie numerem w tablicach.
  • Kurs to „ile złotych za 1 jednostkę”. Kwota w złotych to kwota × kursy[z]; podzielona przez kursy[na] daje kwotę w walucie docelowej. Dzięki temu da się wymieniać dowolną parę walut, także EUR na USD.
  • Przelicz wychodzi od razu, gdy okno jeszcze się buduje (Wynik == null) albo któraś lista nie ma wyboru (−1). Pole Kwota ma w XAML Text="100", więc jego TextChanged przychodzi wcześnie.
  • Obie listy mają tę samą metodę Waluta_SelectionChanged — i tak przeliczamy wszystko.
  • Zamien_Click zamienia numery wybranych pozycji przez zmienną pomocniczą, jak przy zamianie dwóch liczb. Każda zmiana SelectedIndex wywołuje przeliczenie.
  • Kurs w napisie ma 4 miejsca po przecinku (F4), bo dla walut o małej wartości (CZK) dwa miejsca to za mało.

Sprawdź trzy rzeczy

  1. Kliknij listę walut (bez rozwijania) i naciskaj strzałki ↑ ↓. Wybór i wynik zmieniają się bez rozwijania listy.
  2. Usuń z Przelicz warunek WalutaZ.SelectedIndex < 0. Przy starcie kursy[-1] zakończy program wyjątkiem IndexOutOfRangeException.
  3. Dodaj na końcu tablic walutę „NOK”, „korona norweska”, 0.37. Pojawi się w obu listach bez zmian w XAML.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementTypCo robi
ComboBoxItemkontrolkaPozycja listy w XAML; tekst w Content, dane w Tag.
ItemskolekcjaAdd, Insert, Remove, RemoveAt, Clear, Count, Contains.
SelectedIndexintNumer wybranej pozycji; −1 = brak wyboru.
SelectedItemobject?Wybrana pozycja (napis albo ComboBoxItem).
TextstringWidoczny tekst; przy IsEditable — tekst wpisany.
IsEditableboolPozwala wpisać własny tekst.
SelectionChangedzdarzenieZmiana wyboru; parametr SelectionChangedEventArgs.
IsDropDownOpenboolCzy lista jest rozwinięta; można ją rozwinąć z kodu.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
tablica[Lista.SelectedIndex] bez sprawdzeniaPrzy braku wyboru indeks −1 → IndexOutOfRangeException.
(string)Klasa.SelectedItem dla pozycji z XAMLInvalidCastException — to ComboBoxItem, nie napis.
Klasa.SelectedItem.ToString() dla pozycji z XAMLDaje „System.Windows.Controls.ComboBoxItem: 3TI”. Użyj Content.
Klasa.Text w SelectionChangedMoże mieć jeszcze poprzednią wartość.
pozycje w XAML i Items.Add w kodzie w jednej liścieMieszanka napisów i ComboBoxItem — trudne do odczytu.
SelectedIndex="0" + SelectionChanged zmieniające kontrolkę niżej w plikuNullReferenceException przy starcie — sprawdź null.
Items.Clear() w drugiej liście i od razu (string)Miasto.SelectedItemSelectedItem to null — lista po wyczyszczeniu nie ma wyboru.
ZADANIA

Zadania

Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Zawsze obsłuż sytuację „nic nie wybrano”. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.

ZAD 1Plan lekcji na dzień★☆☆

Lista rozwijana z dniami od poniedziałku do piątku. Po wyborze dnia pod listą pojawia się plan lekcji tego dnia: „1. matematyka”, „2. j. polski”… (wymyśl plan klasy). Po uruchomieniu wybrany jest dzisiejszy dzień, a w weekend — poniedziałek z dopiskiem „Najbliższy dzień nauki”.

pokaż wskazówkę
  • Plan zapisz jako tablicę napisów, po jednym na dzień, z lekcjami rozdzielonymi przecinkiem — Split(',') zrobi z nich listę.
  • DayOfWeek: poniedziałek = 1 … piątek = 5, sobota = 6, niedziela = 0.
ZAD 2Szkolna kawiarenka★☆☆

Trzy listy rozwijane: Napój (espresso 6 zł, latte 9 zł, kakao 8 zł, herbata 5 zł), Rozmiar (mały ×1, średni ×1,3, duży ×1,6), Mleko (krowie +0 zł, owsiane +2 zł, bez mleka +0 zł). Herbata i espresso nie mają mleka — po ich wyborze lista mleka jest nieaktywna i ustawiona na „bez mleka”. Cena przelicza się na żywo i jest zaokrąglana do 10 groszy w górę.

pokaż wskazówkę
  • Ceny i mnożniki trzymaj w tablicach równoległych do pozycji list — jak kursy w przykładzie.
  • Zaokrąglenie do 10 groszy w górę: Math.Ceiling(cena * 10) / 10.
ZAD 3Województwo, powiat, szkoła★★☆

Trzy listy zależne: województwo (3 pozycje), powiat (po 2–3 dla każdego województwa), szkoła (po 2 dla każdego powiatu — wymyśl nazwy). Druga lista jest nieaktywna, dopóki nie wybrano województwa, trzecia — dopóki nie wybrano powiatu. Zmiana województwa czyści i blokuje listę szkół. Pod listami pełny adres wyboru: „pomorskie › lęborski › ZSMI Lębork”.

pokaż wskazówkę
  • Powiaty możesz zapisać jako tablicę tablic: string[][] powiaty, gdzie powiaty[0] to powiaty pierwszego województwa.
  • Po Items.Clear() druga lista wywoła SelectionChanged z wyborem −1 — obsłuż to na początku metody.
ZAD 4Własne przedmioty★★☆

Lista rozwijana z możliwością wpisywania, z kilkoma przedmiotami na start. Przycisk Dodaj dopisuje wpisany przedmiot do listy (bez spacji na brzegach, z wielkiej litery), jeśli jeszcze go nie ma — bez względu na wielkość liter. Przycisk Usuń usuwa wybrany przedmiot po potwierdzeniu. Napis pod spodem: „Przedmiotów: 7”. Nowy przedmiot zostaje od razu wybrany.

pokaż wskazówkę
  • Pozycje dodawaj jako napisy (także te startowe — w konstruktorze), wtedy porównujesz zwykłe string.
  • Sprawdzenie bez względu na wielkość liter: przejdź pętlą po Items i porównaj wersje ToLower().
ZAD 5Konwerter systemów liczbowych★★☆

Pole na liczbę i dwie listy: z systemu i na system (dwójkowy, ósemkowy, dziesiętny, szesnastkowy). Wynik pojawia się na żywo. Liczby szesnastkowe wyświetlaj wielkimi literami, dwójkowe — w grupach po 4 cyfry od prawej („1010 0110”). Gdy wpis nie jest poprawną liczbą w wybranym systemie (np. cyfra 2 w systemie dwójkowym), pokaż komunikat zamiast wyniku.

pokaż wskazówkę
  • Convert.ToInt32(tekst, 2) zamienia tekst w podanym systemie na liczbę, a Convert.ToString(liczba, 16) — liczbę na tekst w systemie. Obsługiwane podstawy to 2, 8, 10 i 16 — zapisz je w tablicy.
  • Convert.ToInt32 przy złym wpisie rzuca wyjątek FormatException. Zanim zamienisz, sprawdź każdy znak, np. czy występuje w napisie "0123456789ABCDEF" obciętym do liczby znaków równej podstawie.
ZAD 6Rezerwacja pracowni★★★

Nauczyciel rezerwuje jedną z trzech pracowni (204, 205, 210) na wybrany dzień z najbliższych 7 dni (pozycje w stylu „czw 08.10”) i numer lekcji (1–8, z godzinami w treści, np. „3 — 9:50–10:35”). Pod listami napis „Wolna” na zielono albo „Zajęta przez: Kowalski” na czerwono. Pole Nazwisko i przycisk Rezerwuj (aktywny tylko dla wolnego terminu z wpisanym nazwiskiem). Na start kilka terminów jest już zajętych.

pokaż wskazówkę
  • Rezerwacje trzymaj w tablicy trójwymiarowej string[,,] rezerwacje = new string[3, 7, 8]; pusty element (null) to wolny termin.
  • Dni wygeneruj w pętli z DateTime.Today.AddDays(i) i formatu "ddd dd.MM".
  • Indeksy trzech list to od razu indeksy tablicy.
PODSUMOWANIE

Co trzeba zapamiętać

  • Pozycje listy: ComboBoxItem w XAML albo Items.Add z kodu — nie mieszaj obu sposobów.
  • SelectedIndex (od 0, −1 = brak) świetnie łączy się z tablicami równoległymi.
  • SelectedItem to napis albo ComboBoxItem — zależnie od tego, jak dodałeś pozycje.
  • SelectionChanged przychodzi także przy starcie okna i po Items.Clear() — sprawdzaj null i −1.
  • IsEditable="True" pozwala wpisać własną wartość; odczytujesz ją z Text.
  • Listy zależne: w SelectionChanged pierwszej listy wyczyść i wypełnij drugą.

Dokumentacja: Microsoft Learn — klasa ComboBox.

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