Dataczwartek, 13 sierpnia 2026 Czas18:25:46
← Aplikacje desktopowe (WPF)

Lekcja 14. Wyświetlanie list danych — ListBox, ListView i DataGrid

Trudny / egzaminacyjny

Po co się tego uczymy?

Prawie każda prawdziwa aplikacja pokazuje jakąś listę: produktów, uczniów, zadań do zrobienia. Ręczne tworzenie osobnej kontrolki dla każdego elementu byłoby absurdalne — WPF pozwala połączyć kolekcję danych z interfejsem tak, żeby program SAM wygenerował widok dla każdego elementu listy. To jeden z najważniejszych mechanizmów WPF, wykorzystywany w niemal każdym zadaniu egzaminacyjnym z bazą danych czy magazynem informacji.

Teoria

Trzy elementy współpracują ze sobą, żeby wyświetlić listę danych:

  • Model — klasa opisująca pojedynczy element (np. Ksiazka z właściwościami Tytuł, Autor, Status);
  • Kolekcja — zbiór obiektów modelu, najlepiej typu ObservableCollection<T> zamiast zwykłej List<T>. Różnica jest kluczowa: List<T> NIE odświeża automatycznie interfejsu, gdy coś dodasz/usuniesz, a ObservableCollection<T> informuje interfejs o każdej takiej zmianie;
  • Kontrolka listy (ListBox, ListView lub DataGrid) — wyświetla zawartość kolekcji, połączoną przez właściwość ItemsSource.

Różnice między kontrolkami list: ListBox to najprostsza lista wyboru pojedynczych elementów tekstowych. ListView pozwala na bardziej rozbudowany wygląd każdego elementu przez DataTemplate (np. kilka linii tekstu, ikonka). DataGrid pokazuje dane w formie tabeli z kolumnami — idealny do danych "tabelarycznych" jak lista produktów z cenami, i automatycznie generuje kolumny na podstawie właściwości modelu (albo pozwala je zdefiniować ręcznie).

DataTemplate określa, JAK ma wyglądać pojedynczy element listy — możesz w nim umieścić dowolne kontrolki (tekst, obrazek, przycisk), a WPF automatycznie powtórzy ten szablon dla każdego elementu kolekcji.

Schemat

Jak dane trafiaja na ekran:

Model (Ksiazka.cs)      Kolekcja                    Kontrolka listy
┌───────────────┐       ObservableCollection<Ksiazka>  ┌─────────────────┐
│ Tytul: string  │  --> [ ksiazka1, ksiazka2, ... ]  --> │ ItemsSource=     │
│ Autor: string  │                                       │ {kolekcja}       │
│ Status: string │       kazda zmiana (Add/Remove)       │                  │
└───────────────┘       automatycznie odswieza          │ [Ksiazka 1]      │
                        widok (ObservableCollection)      │ [Ksiazka 2]      │
                                                            │ [Ksiazka 3]      │
                                                            └─────────────────┘

Przykład z życia

Aplikacja biblioteczna wyświetlająca listę książek: każda pozycja na liście to jeden obiekt klasy Ksiazka w kolekcji ObservableCollection<Ksiazka>. Gdy bibliotekarz doda nową książkę przez formularz, program dodaje nowy obiekt do kolekcji — a lista na ekranie AUTOMATYCZNIE się odświeża i pokazuje nową pozycję, bez pisania ani jednej linijki kodu odświeżającego widok ręcznie.

Kod (C#)

// Modele/Ksiazka.cs
namespace BibliotekaApp.Modele
{
    public class Ksiazka
    {
        public string Tytul { get; set; } = "";
        public string Autor { get; set; } = "";
        public string Status { get; set; } = "Dostepna";
    }
}

// MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.Windows;
using BibliotekaApp.Modele;

namespace BibliotekaApp
{
    public partial class MainWindow : Window
    {
        // ObservableCollection automatycznie odswieza widok listy
        public ObservableCollection<Ksiazka> Ksiazki { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            Ksiazki = new ObservableCollection<Ksiazka>
            {
                new Ksiazka { Tytul = "Pan Tadeusz", Autor = "Adam Mickiewicz" },
                new Ksiazka { Tytul = "Lalka", Autor = "Boleslaw Prus" }
            };

            ListaKsiazek.ItemsSource = Ksiazki; // polaczenie kolekcji z kontrolka
        }

        private void PrzyciskDodaj_Click(object sender, RoutedEventArgs e)
        {
            Ksiazki.Add(new Ksiazka
            {
                Tytul = PoleTytul.Text,
                Autor = PoleAutor.Text
            });
            // Lista na ekranie odswiezy sie SAMA - nie trzeba nic wiecej robic!
        }

        private void PrzyciskUsun_Click(object sender, RoutedEventArgs e)
        {
            if (ListaKsiazek.SelectedItem is Ksiazka wybrana)
            {
                Ksiazki.Remove(wybrana);
            }
        }
    }
}

Komentarz i wyjaśnienie kodu

W code-behind okna kolekcja Ksiazki jest typu ObservableCollection<Ksiazka> — po jej stworzeniu wypełniamy dwoma przykładowymi książkami, a potem łączymy z kontrolką listy (ListaKsiazek.ItemsSource = Ksiazki, gdzie ListaKsiazek to ListView albo DataGrid zdefiniowany w XAML z odpowiednim x:Name).

Kluczowy moment jest w PrzyciskDodaj_Click: dodajemy nowy obiekt przez Ksiazki.Add(...) — i to WSZYSTKO. Nie trzeba ręcznie odświeżać listy na ekranie, bo ObservableCollection sama informuje połączoną z nią kontrolkę o zmianie. Gdyby to była zwykła List<Ksiazka>, lista na ekranie NIE pokazałaby nowej książki, dopóki nie ustawilibyśmy ItemsSource na nowo.

PrzyciskUsun_Click pokazuje odczyt zaznaczonego elementu przez ListaKsiazek.SelectedItem, z rzutowaniem (is Ksiazka wybrana) na właściwy typ, a potem usunięcie go z kolekcji tym samym mechanizmem automatycznego odświeżania.

Ćwiczenie samodzielne

Utwórz projekt z modelem Ksiazka, kolekcją ObservableCollection<Ksiazka>, kontrolką ListView lub DataGrid w XAML, oraz przyciskiem "Dodaj" dokładnie jak w przykładzie. Sprawdź, że dodanie nowej książki od razu pojawia się na liście bez dodatkowego kodu odświeżającego.

Zadania do pracy własnej

  1. Dodaj do modelu Ksiazka właściwość RokWydania (int) i wyświetl ją jako dodatkową kolumnę w DataGrid.

  2. Zbuduj listę zadań do zrobienia (model Zadanie z właściwościami Tresc i CzyWykonane typu bool), z przyciskiem "Dodaj zadanie" i możliwością usunięcia zaznaczonego zadania z listy — wykorzystaj ObservableCollection dokładnie jak w przykładzie.

  3. Rozbuduj aplikację biblioteczną o funkcję wypożyczania: kliknięcie przycisku "Wypożycz" przy zaznaczonej książce zmienia jej Status na "Wypożyczona" (zamiast usuwać z listy) — sprawdź, czy zmiana właściwości POJEDYNCZEGO elementu w ObservableCollection również odświeża widok (podpowiedź: może być potrzebne odświeżenie widoku ręcznie albo interfejs INotifyPropertyChanged w modelu — poszukaj tego terminu, to naturalne rozwinięcie tej lekcji).

Typowe błędy

Użycie zwykłej List<T> zamiast ObservableCollection<T> — lista wypełni się poprawnie przy starcie, ale dodawanie/usuwanie elementów PÓŹNIEJ nie będzie widoczne na ekranie bez ręcznego odświeżania ItemsSource. To najczęstszy błąd na starcie z listami w WPF.

Zapominanie o rzutowaniu typu przy odczycie SelectedItemSelectedItem ma typ ogólny object, więc trzeba go rzutować na konkretny typ modelu (jak w przykładzie: is Ksiazka wybrana), zanim odczytamy jego właściwości.

Brak sprawdzenia, czy coś jest zaznaczone przed usunięciem — kliknięcie "Usuń" bez zaznaczonego elementu (SelectedItem == null) spowoduje błąd, jeśli nie ma odpowiedniego zabezpieczenia.

Nawiązanie do egzaminu zawodowego

To praktyczne rozwinięcie INF.04.5.3 (kontrolki i interfejs) połączone z INF.04.3.2 (struktury danych, kolekcje) z działu programowania konsolowego — ObservableCollection to nic innego jak List, którą już znasz, tylko "inteligentniejsza" i przystosowana do współpracy z interfejsem graficznym.