Lekcja 14. Wyświetlanie list danych — ListBox, ListView i DataGrid
Trudny / egzaminacyjnyPo 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.
Ksiazkaz właściwościami Tytuł, Autor, Status); - Kolekcja — zbiór obiektów modelu, najlepiej typu
ObservableCollection<T>zamiast zwykłejList<T>. Różnica jest kluczowa:List<T>NIE odświeża automatycznie interfejsu, gdy coś dodasz/usuniesz, aObservableCollection<T>informuje interfejs o każdej takiej zmianie; - Kontrolka listy (
ListBox,ListViewlubDataGrid) — 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
Dodaj do modelu
KsiazkawłaściwośćRokWydania(int) i wyświetl ją jako dodatkową kolumnę wDataGrid.Zbuduj listę zadań do zrobienia (model
Zadaniez właściwościami Tresc i CzyWykonane typu bool), z przyciskiem "Dodaj zadanie" i możliwością usunięcia zaznaczonego zadania z listy — wykorzystajObservableCollectiondokładnie jak w przykładzie.Rozbuduj aplikację biblioteczną o funkcję wypożyczania: kliknięcie przycisku "Wypożycz" przy zaznaczonej książce zmienia jej
Statusna "Wypożyczona" (zamiast usuwać z listy) — sprawdź, czy zmiana właściwości POJEDYNCZEGO elementu wObservableCollectionrównież odświeża widok (podpowiedź: może być potrzebne odświeżenie widoku ręcznie albo interfejsINotifyPropertyChangedw 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 SelectedItem — SelectedItem 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.