Aplikacje desktopowe · Listy

ListBox, ListView i DataGrid: wyświetlanie list danych

Prawie każda aplikacja pokazuje jakąś listę: produktów, uczniów, zadań. Zamiast tworzyć kontrolkę dla każdego elementu, połączysz kolekcję z listą, a WPF sam wygeneruje widok. Zobaczysz też, dlaczego ekran czasem „nie odświeża się”.

  • około 50 minut
  • WPF · ObservableCollection · DataGrid
  • INF.04.5.3

Po tej lekcji: połączyć kolekcję z ListBox, ListView lub DataGrid przez ItemsSource, uzasadnić użycie ObservableCollection<T> i odczytać zaznaczony element przez SelectedItem.

Zacznijmy od biblioteki

Jedna książka to jeden obiekt

W aplikacji bibliotecznej każda pozycja na liście to **obiekt klasy Ksiazka w kolekcji. Gdy bibliotekarz dodaje książkę, program dodaje obiekt do kolekcji, a lista na ekranie powinna sama** pokazać nowy wiersz, bez ręcznego odświeżania.

Ten mechanizm to jeden z najważniejszych w WPF i pojawia się w niemal każdym zadaniu egzaminacyjnym o magazynie danych czy bazie.

1 · Współpraca trzech części

Model, kolekcja i kontrolka listy

Modeljeden element

Klasa opisująca pojedynczą pozycję, np. Ksiazka z właściwościami Tytul, Autor, Status.

Na przykład new Ksiazka { Tytul = "Lalka" }

Kolekcjazbiór obiektów

Najlepiej ObservableCollection<T>. W odróżnieniu od List<T> informuje interfejs o każdym dodaniu i usunięciu.

Na przykład ObservableCollection<Ksiazka>

Kontrolka listyItemsSource

ListBox, ListView albo DataGrid wyświetla kolekcję przypisaną do ItemsSource.

Na przykład ListaKsiazek.ItemsSource = Ksiazki;

2 · Trzy kontrolki

ListBox, ListView i DataGrid

KontrolkaCo pokazujeKiedy ją wybrać
ListBoxProstą listę jednokolumnowąWybór jednej lub kilku pozycji tekstowych.
ListViewElementy z dowolnym wyglądem przez DataTemplate (kilka linii, ikonka)Karty, wiersze z obrazkiem lub przyciskiem.
DataGridTabelę z kolumnami, jak arkusz kalkulacyjnyDane tabelaryczne, np. produkty z cenami.
`DataGrid` sam tworzy kolumny z właściwości modelu (`AutoGenerateColumns`) albo pozwala zdefiniować je ręcznie. `DataTemplate` opisuje wygląd pojedynczego elementu i powtarza się dla każdego obiektu.

Laboratorium · ćwiczenie w przeglądarce, do którego odsyłają rozgrzewki na końcu lekcji

3 · Sprawdź w działaniu

Dlaczego ekran czasem się nie odświeża

Dodaj książkę przy ObservableCollection: wiersz pojawia się sam. Przełącz na List<T> i dodaj kolejną: licznik „w kolekcji” rośnie, a ekran stoi w miejscu. Potem zaznacz wiersz, kliknij „Wypożycz” i porównaj wynik z włączonym i wyłączonym INotifyPropertyChanged.

Ta interakcja wymaga włączonego JavaScriptu.

ObservableCollection pilnuje dodawania i usuwania elementów. Zmianę pojedynczej właściwości obiektu zgłasza dopiero INotifyPropertyChanged w modelu.

4 · Element listy

Ksiazka.cs

Zwykła klasa z trzema właściwościami. DataGrid utworzy z nich trzy kolumny.

Modele/Ksiazka.cscsharp
namespace BibliotekaApp.Modele;

public class Ksiazka
{
    public string Tytul { get; set; } = "";
    public string Autor { get; set; } = "";
    public string Status { get; set; } = "Dostępna";
}
  1. { get; set; } Tylko publiczne właściwości stają się kolumnami. Pola klasy nie zostaną pokazane.

5 · Kolekcja i przyciski

MainWindow.xaml.cs

Najważniejsza lekcja jest w metodzie Dodaj: po Ksiazki.Add(...) nie ma żadnego kodu odświeżającego.

MainWindow.xaml.cscsharp
using System.Collections.ObjectModel;
using System.Windows;
using BibliotekaApp.Modele;

namespace BibliotekaApp;

public partial class MainWindow : Window
{
    // ObservableCollection automatycznie odświeża 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 = "Bolesław Prus" }
        };

        ListaKsiazek.ItemsSource = Ksiazki;   // połączenie kolekcji z kontrolką
    }

    private void PrzyciskDodaj_Click(object sender, RoutedEventArgs e)
    {
        Ksiazki.Add(new Ksiazka
        {
            Tytul = PoleTytul.Text,
            Autor = PoleAutor.Text
        });
        // Lista na ekranie odświeży się sama: nic więcej nie trzeba robić
    }

    private void PrzyciskUsun_Click(object sender, RoutedEventArgs e)
    {
        if (ListaKsiazek.SelectedItem is Ksiazka wybrana)
        {
            Ksiazki.Remove(wybrana);
        }
    }
}
  1. ObservableCollection<Ksiazka> Ksiazki Kolekcja, która powiadamia kontrolkę o zmianach.
  2. new ObservableCollection<Ksiazka> { ... } Inicjalizator kolekcji z dwiema przykładowymi książkami.
  3. ListaKsiazek.ItemsSource = Ksiazki Łączy kontrolkę z kolekcją. Wystarczy raz.
  4. SelectedItem is Ksiazka wybrana SelectedItem ma typ object, więc sprawdzamy typ i rzutujemy. Jednocześnie zabezpiecza to przed brakiem zaznaczenia (null).
  5. Ksiazki.Remove(wybrana) Usunięcie z kolekcji też odświeża ekran automatycznie.

6 · Wygląd

MainWindow.xaml

DataGrid z automatycznymi kolumnami i panel z polami do dodawania.

MainWindow.xamlxaml
<Window x:Class="BibliotekaApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Biblioteka" Height="380" Width="460">
    <DockPanel Margin="15">
        <StackPanel DockPanel.Dock="Bottom" Margin="0,10,0,0">
            <StackPanel Orientation="Horizontal">
                <TextBox x:Name="PoleTytul" Width="150" Margin="0,0,8,0" />
                <TextBox x:Name="PoleAutor" Width="150" Margin="0,0,8,0" />
                <Button Content="Dodaj" Click="PrzyciskDodaj_Click" Margin="0,0,8,0" />
                <Button Content="Usuń" Click="PrzyciskUsun_Click" />
            </StackPanel>
        </StackPanel>
        <DataGrid x:Name="ListaKsiazek" AutoGenerateColumns="True" IsReadOnly="True" />
    </DockPanel>
</Window>
  1. AutoGenerateColumns="True" Kolumny powstają z właściwości klasy Ksiazka.
  2. IsReadOnly="True" Użytkownik nie edytuje komórek, a dane zmieniamy z kodu.
  3. DockPanel.Dock="Bottom" Panel z przyciskami przypięty do dołu, tabela wypełnia resztę okna.

7 · Zmiana pojedynczej wartości

KsiazkaZPowiadomieniem.cs

Jeśli chcesz, aby zmiana Status od razu pojawiła się w komórce, model musi powiadomić widok. Robi to interfejs INotifyPropertyChanged. Ten sam wzorzec zobaczyłeś w lekcji o MVVM.

Modele/KsiazkaZPowiadomieniem.cscsharp
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace BibliotekaApp.Modele;

public class KsiazkaZPowiadomieniem : INotifyPropertyChanged
{
    private string _status = "Dostępna";

    public string Tytul { get; set; } = "";
    public string Autor { get; set; } = "";

    public string Status
    {
        get => _status;
        set
        {
            _status = value;
            Zmieniono();   // powiadamia widok o zmianie tej właściwości
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    private void Zmieniono([CallerMemberName] string? nazwa = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nazwa));
    }
}
  1. private string _status Pole prywatne przechowujące wartość.
  2. Zmieniono() w set Po każdej zmianie wywołuje zdarzenie PropertyChanged.
  3. [CallerMemberName] Kompilator sam podstawia nazwę właściwości (Status), więc nie wpisujesz jej ręcznie.

8 · Czego unikać

Trzy błędy z listami

`List<T>` zamiast `ObservableCollection<T>`
Tak nie
public List<Ksiazka> Ksiazki { get; set; } = new();
...
Ksiazki.Add(nowa);   // ekran bez zmian

Lista wypełnia się przy starcie, ale późniejsze dodanie lub usunięcie nie jest widoczne.

Tak
public ObservableCollection<Ksiazka> Ksiazki { get; set; } = new();
...
Ksiazki.Add(nowa);   // ekran odświeżony

Tylko ObservableCollection powiadamia kontrolkę o zmianach.

Brak rzutowania `SelectedItem`
Tak nie
string tytul = ListaKsiazek.SelectedItem.Tytul;

Błąd kompilatora: object nie zawiera definicji „Tytul”.

Tak
if (ListaKsiazek.SelectedItem is Ksiazka wybrana)
{
    string tytul = wybrana.Tytul;
}

SelectedItem ma typ object. Sprawdzenie is rzutuje i chroni przed null.

Usuwanie bez sprawdzenia zaznaczenia
Tak nie
Ksiazki.Remove((Ksiazka)ListaKsiazek.SelectedItem);

Bez zaznaczenia SelectedItem jest null, więc program się wywraca.

Tak
if (ListaKsiazek.SelectedItem is Ksiazka wybrana)
{
    Ksiazki.Remove(wybrana);
}

Wzorzec is Ksiazka wybrana jest false dla null, więc ciało if się nie wykona.

9 · Sprawdź się

Kolekcja, kontrolka czy powiadomienie?

Cztery sytuacje z listami.

Ta interakcja wymaga włączonego JavaScriptu.

Podsumowanie

Co warto zapamiętać

  1. Lista w WPF to model (jeden element), kolekcja i kontrolka połączona przez ItemsSource.
  2. ListBox to prosta lista, ListView ma własny wygląd elementów (DataTemplate), DataGrid to tabela.
  3. **ObservableCollection<T>** odświeża ekran po dodaniu i usunięciu elementu, zwykła List<T> nie.
  4. Zmianę właściwości istniejącego obiektu zgłasza **INotifyPropertyChanged** w modelu.
  5. SelectedItem ma typ object, więc użyj is Ksiazka wybrana.

Dalej na swoim koncie

Rozgrzewki, quiz i zadania są w biurku ucznia.

Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.

Zaloguj się →
Źródła i dalsza nauka