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

ListBox — lista na stałe widoczna

ListBox pokazuje wiele pozycji naraz, bez rozwijania. Użytkownik widzi całą listę, może wybrać jedną albo kilka pozycji, a program dodaje, usuwa i przestawia pozycje w trakcie działania. To podstawa okien typu „lista uczniów”, „koszyk”, „kolejka”, które często pojawiają się w zadaniach egzaminacyjnych.

XAML C# ListBox Items SelectionMode SelectedItems 90 min
CEL LEKCJI

Czego się dziś nauczysz

  • Umieścisz listę w oknie tak, żeby rosła razem z nim i przewijała się, gdy pozycji jest dużo.
  • Dodasz, wstawisz, usuniesz i przestawisz pozycję listy z kodu.
  • Włączysz wybór wielu pozycji i odczytasz je z SelectedItems.
  • Bezpiecznie usuniesz wszystkie zaznaczone pozycje.
  • Przeniesiesz pozycje między dwiema listami.
  • Dobierzesz kontrolkę: ListBox czy ComboBox.
TEORIA

ListBox w oknie

MainWindow.xaml
<Grid Margin="10">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <TextBlock Text="Uczniowie klasy 3TI" FontWeight="Bold" Margin="0,0,0,4"/>
    <ListBox x:Name="Uczniowie" Grid.Row="1" SelectionChanged="Uczniowie_SelectionChanged">
        <ListBoxItem Content="Adamczyk Ola"/>
        <ListBoxItem Content="Borowski Jan"/>
        <ListBoxItem Content="Cieślak Maja"/>
    </ListBox>
    <TextBlock x:Name="Info" Grid.Row="2" Margin="0,6,0,0"/>
</Grid>
Klasa–□✕
Uczniowie klasy 3TI
Adamczyk Ola
Borowski Jan
Cieślak Maja
Wybrano: Borowski Jan

lista w wierszu * — gdy pozycji przybędzie, pojawi się pasek przewijania

Wszystko, co znasz z ComboBox, działa tu tak samo: pozycje ListBoxItem w XAML albo Items.Add z kodu, SelectedIndex (−1 = brak), SelectedItem i zdarzenie SelectionChanged. ListBox ma wbudowany ScrollViewer, więc przewija się sam — pod warunkiem, że ma ograniczoną wysokość, np. stoi w wierszu * (lekcja 11).

TEORIA

Dodawanie, usuwanie, przestawianie

MainWindow.xaml.cs
Lista.Items.Add("chleb");              // na koniec
Lista.Items.Insert(0, "masło");        // na początek (pozycja 0)
Lista.Items.Remove("chleb");           // usuń pozycję o takiej treści
Lista.Items.RemoveAt(2);               // usuń trzecią pozycję
object trzecia = Lista.Items[2];       // odczyt pozycji o numerze 2
Lista.Items[2] = "ser żółty";          // zamiana treści pozycji

Lista.SelectedItem = "masło";          // zaznacz z kodu
Lista.ScrollIntoView("masło");         // przewiń, żeby była widoczna

Przesunięcie pozycji w górę to trzy kroki: zapamiętaj pozycję, usuń ją i wstaw z powrotem o jedno miejsce wyżej. Po RemoveAt zaznaczenie znika, więc na końcu zaznaczasz pozycję w nowym miejscu.

TEORIA

Wybór wielu pozycji

SelectionModeJak wybiera użytkownik
Single (domyślny)tylko jedna pozycja
Multiplekażde kliknięcie przełącza pozycję — jak pola wyboru
Extendedjak w Eksploratorze Windows: klik = jedna, Ctrl + klik = dodaj / odejmij, Shift + klik = zakres

Przy wyborze wielu pozycji zaznaczone są w kolekcji SelectedItems. SelectedIndex i SelectedItem wskazują wtedy tylko pierwszą z nich.

MainWindow.xaml.cs
Info.Text = $"Zaznaczono: {Uczniowie.SelectedItems.Count}";

string nazwiska = "";
foreach (string uczen in Uczniowie.SelectedItems)   // pozycje dodane jako napisy
{
    nazwiska += uczen + "\n";
}

Uczniowie.SelectAll();          // zaznacz wszystkie (Multiple / Extended)
Uczniowie.UnselectAll();        // odznacz wszystkie
TEORIA

Usuwanie zaznaczonych

Naturalny pomysł — foreach po SelectedItems i Items.Remove w środku — kończy się wyjątkiem InvalidOperationException: kolekcja zmienia się w trakcie przechodzenia po niej. Usunięcie pozycji z listy usuwa ją także z SelectedItems. Wystarczy więc w pętli while usuwać zawsze pierwszą zaznaczoną, aż nie zostanie żadna:

MainWindow.xaml.cs
// ✗ InvalidOperationException
foreach (object p in Lista.SelectedItems)
{
    Lista.Items.Remove(p);
}

// ✓ usuwaj pierwszą zaznaczoną, dopóki jakaś jest
while (Lista.SelectedItems.Count > 0)
{
    Lista.Items.Remove(Lista.SelectedItems[0]);
}
TEORIA

ListBox czy ComboBox?

ComboBoxListBox
Miejsce w okniejedna linia, rozwija sięwidoczne od razu kilka–kilkanaście pozycji
Wybórjedna pozycjajedna albo wiele (SelectionMode)
Typowowybór jednej wartości w formularzudane, którymi zarządzasz: dodajesz, usuwasz, przeglądasz
PRZYKŁAD

Zakupy na biwak klasowy

Dwie listy: „Do kupienia” i „W koszyku”. Nowy produkt dopisuje się do pierwszej. Strzałki przenoszą zaznaczone produkty między listami (można zaznaczyć kilka z Ctrl lub Shift). Pod pierwszą listą przyciski przestawiania, sortowania i usuwania. Przyciski, których w danej chwili nie da się użyć, są nieaktywne.

Zakupy na biwak klasowy–□✕
węgiel drzewny
Dodaj
Do kupienia (4)W koszyku (2)
chleb
kiełbaski
ketchup
pianki
→
←
woda 6 × 1,5 l
węgiel drzewny
▲
▼
A–Z
Usuń

dwa produkty zaznaczone z Ctrl — strzałki ▲▼ są nieaktywne, bo przesuwamy tylko jedną pozycję

MainWindow.xaml — pełny plik
<Window x:Class="Lekcja17.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Zakupy na biwak klasowy" Height="440" Width="560"
        MinWidth="480" MinHeight="360" WindowStartupLocation="CenterScreen">

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

        <!-- dodawanie produktu -->
        <DockPanel Grid.Row="0" Grid.ColumnSpan="3" Margin="0,0,0,10">
            <Button DockPanel.Dock="Right" Content="Dodaj" Width="80" Margin="8,0,0,0"
                    IsDefault="True" Click="Dodaj_Click"/>
            <TextBox x:Name="Produkt" MaxLength="40" VerticalContentAlignment="Center"/>
        </DockPanel>

        <TextBlock x:Name="NaglowekDoKupienia" Grid.Row="1" Grid.Column="0"
                   FontWeight="Bold" Margin="0,0,0,4"/>
        <TextBlock x:Name="NaglowekKoszyk" Grid.Row="1" Grid.Column="2"
                   FontWeight="Bold" Margin="0,0,0,4"/>

        <!-- dwie listy z wyborem wielu pozycji (Ctrl / Shift + klik) -->
        <ListBox x:Name="DoKupienia" Grid.Row="2" Grid.Column="0"
                 SelectionMode="Extended" SelectionChanged="Lista_SelectionChanged"/>

        <StackPanel Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" Margin="8,0">
            <Button x:Name="DoKoszyka" Content="→" Width="40" Height="30" Margin="0,0,0,6"
                    ToolTip="Przenieś do koszyka" Click="DoKoszyka_Click"/>
            <Button x:Name="ZKoszyka" Content="←" Width="40" Height="30"
                    ToolTip="Wyjmij z koszyka" Click="ZKoszyka_Click"/>
        </StackPanel>

        <ListBox x:Name="Koszyk" Grid.Row="2" Grid.Column="2"
                 SelectionMode="Extended" SelectionChanged="Lista_SelectionChanged"/>

        <!-- operacje na liście „do kupienia” -->
        <StackPanel Grid.Row="3" Grid.Column="0" Orientation="Horizontal" Margin="0,8,0,0">
            <Button x:Name="WGore" Content="▲" Width="32" Margin="0,0,4,0" Click="WGore_Click"/>
            <Button x:Name="WDol" Content="▼" Width="32" Margin="0,0,4,0" Click="WDol_Click"/>
            <Button Content="A–Z" Width="40" Margin="0,0,4,0" Click="Sortuj_Click"/>
            <Button x:Name="Usun" Content="Usuń" Width="60" Click="Usun_Click"/>
        </StackPanel>
    </Grid>
</Window>
MainWindow.xaml.cs — pełny plik
using System.Windows;
using System.Windows.Controls;

namespace Lekcja17;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        string[] start = { "chleb", "kiełbaski", "ketchup", "woda 6 × 1,5 l", "pianki" };
        foreach (string p in start)
        {
            DoKupienia.Items.Add(p);
        }

        Odswiez();
    }

    private void Dodaj_Click(object sender, RoutedEventArgs e)
    {
        string nowy = Produkt.Text.Trim().ToLower();

        if (nowy == "")
        {
            Produkt.Focus();
            return;
        }

        if (DoKupienia.Items.Contains(nowy) || Koszyk.Items.Contains(nowy))
        {
            MessageBox.Show($"„{nowy}” jest już na liście.", "Powtórka",
                            MessageBoxButton.OK, MessageBoxImage.Information);
            Produkt.SelectAll();
            Produkt.Focus();
            return;
        }

        DoKupienia.Items.Add(nowy);
        DoKupienia.SelectedItem = nowy;           // zaznacz nowy produkt
        DoKupienia.ScrollIntoView(nowy);          // i przewiń do niego
        Produkt.Clear();
        Produkt.Focus();
        Odswiez();
    }

    private void DoKoszyka_Click(object sender, RoutedEventArgs e)
    {
        Przenies(DoKupienia, Koszyk);
    }

    private void ZKoszyka_Click(object sender, RoutedEventArgs e)
    {
        Przenies(Koszyk, DoKupienia);
    }

    private void WGore_Click(object sender, RoutedEventArgs e)
    {
        int i = DoKupienia.SelectedIndex;
        if (i > 0)
        {
            object produkt = DoKupienia.Items[i];
            DoKupienia.Items.RemoveAt(i);
            DoKupienia.Items.Insert(i - 1, produkt);
            DoKupienia.SelectedIndex = i - 1;
        }
    }

    private void WDol_Click(object sender, RoutedEventArgs e)
    {
        int i = DoKupienia.SelectedIndex;
        if (i >= 0 && i < DoKupienia.Items.Count - 1)
        {
            object produkt = DoKupienia.Items[i];
            DoKupienia.Items.RemoveAt(i);
            DoKupienia.Items.Insert(i + 1, produkt);
            DoKupienia.SelectedIndex = i + 1;
        }
    }

    private void Sortuj_Click(object sender, RoutedEventArgs e)
    {
        // kopia pozycji do tablicy, sortowanie, wpisanie z powrotem
        string[] produkty = new string[DoKupienia.Items.Count];
        for (int i = 0; i < produkty.Length; i++)
        {
            produkty[i] = (string)DoKupienia.Items[i];
        }

        Array.Sort(produkty);

        DoKupienia.Items.Clear();
        foreach (string p in produkty)
        {
            DoKupienia.Items.Add(p);
        }
    }

    private void Usun_Click(object sender, RoutedEventArgs e)
    {
        int ile = DoKupienia.SelectedItems.Count;
        MessageBoxResult odp = MessageBox.Show($"Usunąć zaznaczone produkty ({ile})?", "Usuwanie",
                                               MessageBoxButton.YesNo, MessageBoxImage.Question);
        if (odp != MessageBoxResult.Yes)
        {
            return;
        }

        // usuwamy zawsze pierwszy zaznaczony, aż nie zostanie żaden
        while (DoKupienia.SelectedItems.Count > 0)
        {
            DoKupienia.Items.Remove(DoKupienia.SelectedItems[0]);
        }
        Odswiez();
    }

    private void Lista_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        Odswiez();
    }

    // Przenosi wszystkie zaznaczone pozycje z jednej listy na drugą
    private void Przenies(ListBox skad, ListBox dokad)
    {
        while (skad.SelectedItems.Count > 0)
        {
            object produkt = skad.SelectedItems[0];
            skad.Items.Remove(produkt);
            dokad.Items.Add(produkt);
        }
        Odswiez();
    }

    private void Odswiez()
    {
        if (NaglowekKoszyk == null || Usun == null)
        {
            return;
        }

        NaglowekDoKupienia.Text = $"Do kupienia ({DoKupienia.Items.Count})";
        NaglowekKoszyk.Text = $"W koszyku ({Koszyk.Items.Count})";

        int zaznaczone = DoKupienia.SelectedItems.Count;
        DoKoszyka.IsEnabled = zaznaczone > 0;
        ZKoszyka.IsEnabled = Koszyk.SelectedItems.Count > 0;
        Usun.IsEnabled = zaznaczone > 0;
        WGore.IsEnabled = zaznaczone == 1;      // przesuwamy tylko jedną pozycję
        WDol.IsEnabled = zaznaczone == 1;
    }
}

Co dzieje się po kolei

  • Górny wiersz to DockPanel z lekcji 13: przycisk przyklejony do prawej, pole wypełnia resztę.
  • Obie listy mają SelectionMode="Extended" i stoją w wierszu * — rosną z oknem i przewijają się same.
  • Konstruktor dodaje pięć produktów startowych jako napisy. Dzięki temu Items.Contains(nowy) porównuje zwykłe napisy.
  • Dodaj_Click zapisuje produkt małymi literami, odrzuca puste wpisy i powtórki (na obu listach), a nowy produkt zaznacza i przewija do niego listę.
  • Przenies działa w obie strony — dostaje listę źródłową i docelową jako parametry. Pętla while zabiera zawsze pierwszą zaznaczoną pozycję.
  • Przesuwanie (▲▼) usuwa pozycję i wstawia ją o jedno miejsce wyżej lub niżej. Warunki i > 0 i i < Count - 1 pilnują brzegów listy.
  • Sortowanie kopiuje pozycje do tablicy, sortuje ją przez Array.Sort i wpisuje z powrotem.
  • Odswiez ustawia nagłówki z licznikami i włącza tylko te przyciski, które mają sens: strzałki ▲▼ tylko przy jednej zaznaczonej pozycji.

Sprawdź trzy rzeczy

  1. Zamień w pierwszej liście Extended na Multiple. Teraz każde kliknięcie przełącza pozycję, bez Ctrl.
  2. Zamień pętlę while w Usun_Click na foreach (object p in DoKupienia.SelectedItems) z Remove(p) w środku. Usuń dwa produkty — pojawi się wyjątek.
  3. Dodaj „Chleb” wielką literą. Program i tak wykryje powtórkę, bo zapisuje wszystko małymi literami.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementTypCo robi
ListBoxItemkontrolkaPozycja listy w XAML.
Items.Add, Insert, Remove, RemoveAt, ClearmetodyZmiana zawartości listy.
Items[i]objectPozycja o numerze i — odczyt i zamiana.
Items.Contains(x)boolCzy lista zawiera pozycję.
SelectionModeSingle, Multiple, ExtendedSposób zaznaczania.
SelectedItemskolekcjaWszystkie zaznaczone pozycje.
SelectAll(), UnselectAll()metodyZaznacza / odznacza wszystko.
ScrollIntoView(x)metodaPrzewija listę do pozycji.
Array.Sort(tablica)metodaSortuje tablicę rosnąco (napisy alfabetycznie).
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
foreach po SelectedItems z Remove w środkuInvalidOperationException. Użyj while (SelectedItems.Count > 0).
Lista.Items.RemoveAt(Lista.SelectedIndex) bez wyboruRemoveAt(-1) → ArgumentOutOfRangeException.
ListBox w pionowym StackPanelLista rośnie bez końca i nie przewija się. Postaw ją w wierszu *.
SelectedItem przy wyborze wielu pozycjiZwraca tylko pierwszą zaznaczoną.
Items.Contains("Chleb"), gdy na liście jest „chleb”false — porównanie uwzględnia wielkość liter.
(string)Lista.SelectedItem dla pozycji z XAMLInvalidCastException — to ListBoxItem.
ZADANIA

Zadania

Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Przyciski, których w danej chwili nie można użyć, powinny być nieaktywne. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.

ZAD 1Filmy na wieczór filmowy★☆☆

Klasa zbiera propozycje filmów. Pole tytułu i przycisk Dodaj (Enter), lista propozycji, przyciski Usuń (aktywny przy zaznaczeniu) i Wyczyść listę (z potwierdzeniem). Tytuł nie może się powtórzyć (bez względu na wielkość liter, ale na liście zostaje w oryginalnej pisowni). Nad listą: „Propozycji: 6”.

pokaż wskazówkę
  • Powtórkę sprawdzisz pętlą po Items i porównaniem ToLower() obu napisów.
ZAD 2Lista obecności★☆☆

Lista 12 uczniów (dodanych w konstruktorze z tablicy) z wyborem Multiple. Nauczyciel klika obecnych. Na bieżąco: „Obecnych: 10 / 12”. Przyciski Wszyscy obecni i Wyczyść. Przycisk Zapisz pokazuje w MessageBox datę lekcji i nazwiska nieobecnych (albo „Wszyscy obecni”).

pokaż wskazówkę
  • Nieobecni to pozycje z Items, których nie ma w SelectedItems — SelectedItems.Contains(uczen).
ZAD 3Kolejka w sekretariacie★★☆

Uczniowie zapisują się do kolejki: pole imienia i Dołącz dodaje „#7 Ola” (numery rosną od 1). Przycisk Obsłuż następnego zdejmuje pierwszą osobę z kolejki, pokazuje ją dużą czcionką w napisie „Teraz obsługiwany: #3 Ola” i dopisuje na górze drugiej listy „Historia” wpis z godziną („10:42 #3 Ola”). Pod kolejką: „Czekających: 4”. Przy pustej kolejce przycisk obsługi jest nieaktywny.

pokaż wskazówkę
  • Pierwsza osoba w kolejce to Items[0]; wpis na górę historii — Items.Insert(0, …).
  • Numer kolejnej osoby trzymaj w polu klasy — nie licz go z długości listy, bo lista się skraca.
ZAD 4Edytor listy zadań★★☆

Lista zadań na tydzień. Kliknięcie pozycji wpisuje jej treść do pola tekstowego pod listą. Przyciski: Dodaj nowe, Zapisz zmianę (podmienia treść zaznaczonej pozycji), Duplikuj (wstawia kopię tuż pod zaznaczoną), ▲, ▼, Usuń. Po każdej operacji zaznaczona jest pozycja, której dotyczyła.

pokaż wskazówkę
  • Zapis zmiany: Items[i] = nowyTekst; — numer zaznaczenia zapamiętaj wcześniej, bo podmiana pozycji czyści zaznaczenie.
  • Duplikat: Items.Insert(i + 1, …).
ZAD 5Wyszukiwarka miast★★☆

Tablica z 20 miastami województwa pomorskiego. Nad listą pole „Szukaj”: przy każdym naciśnięciu klawisza lista pokazuje tylko miasta zawierające wpisany tekst (bez względu na wielkość liter), a pod nią „Pokazano 4 z 20”. Zaznaczenie miasta wypisuje pod listą „Wybrano: Lębork”. Gdy żadne miasto nie pasuje, lista jest pusta, a napis mówi „Brak wyników dla „xyz””.

pokaż wskazówkę
  • Przy każdej zmianie wyczyść listę i dodaj od nowa pasujące miasta z tablicy. Contains na napisach małymi literami.
  • Po wyczyszczeniu listy zaznaczenie znika — SelectionChanged przyjdzie z SelectedItem == null.
ZAD 6Plan wycieczki do Gdańska★★★

Po lewej lista dostępnych atrakcji (8 pozycji, np. Muzeum II Wojny Światowej, Dwór Artusa, rejs statkiem), po prawej — „Plan dnia”. Każda atrakcja ma koszt na osobę i czas w godzinach (tablice). Zaznaczenie atrakcji na dowolnej liście pokazuje pod spodem jej koszt i czas. Pole Budżet na osobę. Przeniesienie do planu jest możliwe tylko wtedy, gdy nie przekroczy budżetu ani 8 godzin — w przeciwnym razie komunikat, czego brakuje. Pod planem: „Koszt: 95 zł / 120 zł, czas: 6 h / 8 h”. Przyciski ▲▼ zmieniają kolejność zwiedzania.

pokaż wskazówkę
  • Na listach trzymaj same nazwy. Koszt i czas znajdziesz przez numer w tablicy: Array.IndexOf(nazwy, (string)Plan.Items[i]).
  • Sumy planu licz zawsze od nowa w pętli po Plan.Items — wtedy usuwanie i przestawianie nie psuje liczników.
PODSUMOWANIE

Co trzeba zapamiętać

  • ListBox pokazuje wiele pozycji naraz i przewija się sam, gdy ma ograniczoną wysokość.
  • Items: Add, Insert, Remove, RemoveAt, [i], Contains, Count.
  • SelectionMode="Extended" lub Multiple pozwala zaznaczyć wiele pozycji; odczytujesz je z SelectedItems.
  • Zaznaczone usuwaj pętlą while (SelectedItems.Count > 0), nie foreach.
  • Przestawienie pozycji: zapamiętaj → usuń → wstaw w nowe miejsce → zaznacz.
  • ComboBox — wybór jednej wartości; ListBox — zarządzanie zbiorem pozycji.

Dokumentacja: Microsoft Learn — klasa ListBox.

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