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.
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ę:
ListBoxczyComboBox.
ListBox w oknie
<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>
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).
Dodawanie, usuwanie, przestawianie
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.
Wybór wielu pozycji
SelectionMode | Jak wybiera użytkownik |
|---|---|
Single (domyślny) | tylko jedna pozycja |
Multiple | każde kliknięcie przełącza pozycję — jak pola wyboru |
Extended | jak 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.
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
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:
// ✗ 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]);
}
ListBox czy ComboBox?
ComboBox | ListBox | |
|---|---|---|
| Miejsce w oknie | jedna linia, rozwija się | widoczne od razu kilka–kilkanaście pozycji |
| Wybór | jedna pozycja | jedna albo wiele (SelectionMode) |
| Typowo | wybór jednej wartości w formularzu | dane, którymi zarządzasz: dodajesz, usuwasz, przeglądasz |
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.
dwa produkty zaznaczone z Ctrl — strzałki ▲▼ są nieaktywne, bo przesuwamy tylko jedną pozycję
<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>
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
DockPanelz 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_Clickzapisuje produkt małymi literami, odrzuca puste wpisy i powtórki (na obu listach), a nowy produkt zaznacza i przewija do niego listę.Przeniesdziała w obie strony — dostaje listę źródłową i docelową jako parametry. Pętlawhilezabiera zawsze pierwszą zaznaczoną pozycję.- Przesuwanie (▲▼) usuwa pozycję i wstawia ją o jedno miejsce wyżej lub niżej. Warunki
i > 0ii < Count - 1pilnują brzegów listy. - Sortowanie kopiuje pozycje do tablicy, sortuje ją przez
Array.Sorti wpisuje z powrotem. Odswiezustawia nagłówki z licznikami i włącza tylko te przyciski, które mają sens: strzałki ▲▼ tylko przy jednej zaznaczonej pozycji.
Sprawdź trzy rzeczy
- Zamień w pierwszej liście
ExtendednaMultiple. Teraz każde kliknięcie przełącza pozycję, bez Ctrl. - Zamień pętlę
whilewUsun_Clicknaforeach (object p in DoKupienia.SelectedItems)zRemove(p)w środku. Usuń dwa produkty — pojawi się wyjątek. - Dodaj „Chleb” wielką literą. Program i tak wykryje powtórkę, bo zapisuje wszystko małymi literami.
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
ListBoxItem | kontrolka | Pozycja listy w XAML. |
Items.Add, Insert, Remove, RemoveAt, Clear | metody | Zmiana zawartości listy. |
Items[i] | object | Pozycja o numerze i — odczyt i zamiana. |
Items.Contains(x) | bool | Czy lista zawiera pozycję. |
SelectionMode | Single, Multiple, Extended | Sposób zaznaczania. |
SelectedItems | kolekcja | Wszystkie zaznaczone pozycje. |
SelectAll(), UnselectAll() | metody | Zaznacza / odznacza wszystko. |
ScrollIntoView(x) | metoda | Przewija listę do pozycji. |
Array.Sort(tablica) | metoda | Sortuje tablicę rosnąco (napisy alfabetycznie). |
Zanim utkniesz
| Zapis | Problem |
|---|---|
foreach po SelectedItems z Remove w środku | InvalidOperationException. Użyj while (SelectedItems.Count > 0). |
Lista.Items.RemoveAt(Lista.SelectedIndex) bez wyboru | RemoveAt(-1) → ArgumentOutOfRangeException. |
ListBox w pionowym StackPanel | Lista rośnie bez końca i nie przewija się. Postaw ją w wierszu *. |
SelectedItem przy wyborze wielu pozycji | Zwraca 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 XAML | InvalidCastException — to ListBoxItem. |
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.
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
Itemsi porównaniemToLower()obu napisów.
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 wSelectedItems—SelectedItems.Contains(uczen).
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.
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, …).
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.
Containsna napisach małymi literami. - Po wyczyszczeniu listy zaznaczenie znika —
SelectionChangedprzyjdzie zSelectedItem == null.
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.
Co trzeba zapamiętać
ListBoxpokazuje wiele pozycji naraz i przewija się sam, gdy ma ograniczoną wysokość.Items:Add,Insert,Remove,RemoveAt,[i],Contains,Count.SelectionMode="Extended"lubMultiplepozwala zaznaczyć wiele pozycji; odczytujesz je zSelectedItems.- Zaznaczone usuwaj pętlą
while (SelectedItems.Count > 0), nieforeach. - 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.