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
| Kontrolka | Co pokazuje | Kiedy ją wybrać |
|---|---|---|
ListBox | Prostą listę jednokolumnową | Wybór jednej lub kilku pozycji tekstowych. |
ListView | Elementy z dowolnym wyglądem przez DataTemplate (kilka linii, ikonka) | Karty, wiersze z obrazkiem lub przyciskiem. |
DataGrid | Tabelę z kolumnami, jak arkusz kalkulacyjny | Dane tabelaryczne, np. produkty z cenami. |
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.
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.
namespace BibliotekaApp.Modele;
public class Ksiazka
{
public string Tytul { get; set; } = "";
public string Autor { get; set; } = "";
public string Status { get; set; } = "Dostępna";
}
{ 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.
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);
}
}
}
ObservableCollection<Ksiazka> KsiazkiKolekcja, która powiadamia kontrolkę o zmianach.new ObservableCollection<Ksiazka> { ... }Inicjalizator kolekcji z dwiema przykładowymi książkami.ListaKsiazek.ItemsSource = KsiazkiŁączy kontrolkę z kolekcją. Wystarczy raz.SelectedItem is Ksiazka wybranaSelectedItemma typobject, więc sprawdzamy typ i rzutujemy. Jednocześnie zabezpiecza to przed brakiem zaznaczenia (null).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.
<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>
AutoGenerateColumns="True"Kolumny powstają z właściwości klasyKsiazka.IsReadOnly="True"Użytkownik nie edytuje komórek, a dane zmieniamy z kodu.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.
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));
}
}
private string _statusPole prywatne przechowujące wartość.Zmieniono()wsetPo każdej zmianie wywołuje zdarzeniePropertyChanged.[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>`
public List<Ksiazka> Ksiazki { get; set; } = new();
...
Ksiazki.Add(nowa); // ekran bez zmianLista wypełnia się przy starcie, ale późniejsze dodanie lub usunięcie nie jest widoczne.
public ObservableCollection<Ksiazka> Ksiazki { get; set; } = new();
...
Ksiazki.Add(nowa); // ekran odświeżonyTylko ObservableCollection powiadamia kontrolkę o zmianach.
Brak rzutowania `SelectedItem`
string tytul = ListaKsiazek.SelectedItem.Tytul;Błąd kompilatora: object nie zawiera definicji „Tytul”.
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
Ksiazki.Remove((Ksiazka)ListaKsiazek.SelectedItem);Bez zaznaczenia SelectedItem jest null, więc program się wywraca.
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.
Podsumowanie
Co warto zapamiętać
- Lista w WPF to model (jeden element), kolekcja i kontrolka połączona przez
ItemsSource. ListBoxto prosta lista,ListViewma własny wygląd elementów (DataTemplate),DataGridto tabela.- **
ObservableCollection<T>** odświeża ekran po dodaniu i usunięciu elementu, zwykłaList<T>nie. - Zmianę właściwości istniejącego obiektu zgłasza **
INotifyPropertyChanged** w modelu. SelectedItemma typobject, więc użyjis 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ę →