DataGrid w WPF krok po kroku - binding, edycja i wydajność

Bruno Krawczyk 28 czerwca 2026
Widok projektanta XAML z listą "Data Sources" po lewej, zawierającą dane klienta, oraz obszarem projektowania okna z polami tekstowymi dla adresu, miasta, nazwy firmy i innych danych. Widoczny jest element wpf datagrid.

Spis treści

Gdy aplikacja desktopowa ma pokazać listę klientów, zamówień albo wyników raportu, zwykły ListBox szybko przestaje wystarczać. WPF DataGrid daje gotową tabelę z kolumnami, sortowaniem, edycją i walidacją, ale dopiero właściwe powiązanie danych decyduje o tym, czy rozwiązanie będzie wygodne i stabilne. Pokażę, jak zacząć, które właściwości ustawić oraz gdzie pojawiają się typowe problemy z wydajnością i edycją.

DataGrid w WPF najlepiej działa wtedy, gdy tabela jest świadomie związana z modelem danych

  • ItemsSource łączy tabelę z kolekcją obiektów.
  • ObservableCollection odświeża widok po dodaniu lub usunięciu elementu.
  • INotifyPropertyChanged informuje interfejs o zmianie właściwości wiersza.
  • AutoGenerateColumns="False" daje kontrolę nad kolejnością, formatem i edycją kolumn.
  • ICollectionView pozwala dodać filtrowanie, sortowanie i grupowanie bez mieszania logiki z widokiem.

Do czego służy DataGrid w aplikacji WPF

DataGrid to kontrolka przeznaczona do prezentowania danych w układzie wierszy i kolumn. Każdy wiersz reprezentuje obiekt, a każda kolumna zwykle wskazuje jego właściwość, na przykład Name, Price albo IsActive. Dzięki temu jedna kontrolka może wyświetlać dane z kolekcji C#, zapytania LINQ, bazy SQL albo usługi sieciowej.

W praktyce dostajemy od razu kilka ważnych mechanizmów. Użytkownik może zaznaczać wiersze, zmieniać szerokość kolumn, sortować dane kliknięciem nagłówka, a w razie potrzeby również edytować wartości bez otwierania osobnego formularza.

To jednak nie jest kontrolka wyłącznie do wyświetlania tabeli. Można w niej umieścić pola wyboru, listy rozwijane, przyciski, obrazy czy własne szablony komórek. Ja traktuję ją jako warstwę prezentacji danych, a nie miejsce na reguły biznesowe. Walidację, zapis do bazy i operacje na rekordach lepiej trzymać w modelu lub ViewModelu.

Element Rola
DataGrid Kontener tabeli i obsługa interakcji użytkownika
DataGridTextColumn Wyświetlanie tekstu, liczb i dat
DataGridCheckBoxColumn Wartości typu bool
DataGridComboBoxColumn Wybór wartości z listy
DataGridTemplateColumn Własny wygląd i tryb edycji komórki

Najprostsze powiązanie tabeli z kolekcją

Najkrótsza droga do działającej tabeli prowadzi przez właściwość ItemsSource. W przykładzie użyję listy produktów, ponieważ dobrze pokazuje zarówno tekst, liczbę, jak i wartość logiczną.

Po stronie C# potrzebujemy właściwości zawierającej dane. Do dynamicznych zmian najlepiej użyć ObservableCollection, ponieważ implementuje mechanizm powiadamiania interfejsu o dodaniu i usunięciu elementu.

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public bool IsAvailable { get; set; }
}

public class ProductsViewModel
{
    public ObservableCollection Products { get; } = new()
    {
        new Product { Id = 1, Name = "Klawiatura", Price = 249.99m, IsAvailable = true },
        new Product { Id = 2, Name = "Mysz", Price = 129.90m, IsAvailable = true }
    };
}

Jeżeli ustawiam DataContext okna na ViewModel, binding zaczyna działać bez ręcznego przypisywania danych w kodzie widoku.

public MainWindow()
{
    InitializeComponent();
    DataContext = new ProductsViewModel();
}

Automatyczne kolumny są świetne do prototypu i szybkiego podglądu danych. W aplikacji produkcyjnej zwykle wyłączam je jednak przez AutoGenerateColumns="False". Dzięki temu przypadkowo dodana właściwość modelu nie pojawi się użytkownikowi jako nowa kolumna, a kolejność i format pozostają pod kontrolą.

Jak skonfigurować kolumny i edycję

Jawne kolumny dają przewidywalny interfejs

Ręczne zdefiniowanie kolumn wymaga kilku dodatkowych wierszy XAML, ale szybko się zwraca. Możemy ustawić nagłówki po polsku, szerokości, format liczb oraz to, które pola są tylko do odczytu.


    
        

        

        
    

Warto rozróżnić szerokość stałą, taką jak 120, od proporcjonalnej, zapisanej jako 2*. Ta druga pozwala wykorzystać wolne miejsce w oknie i zwykle lepiej sprawdza się przy kolumnach z dłuższym tekstem.

Edycja nie kończy się na zmianie wartości w komórce

DataGrid pozwala edytować komórki domyślnie, ale zapis zmiany do obiektu wymaga poprawnego bindingu. Jeżeli właściwość ma być zmieniana od razu po wpisaniu tekstu, przydaje się UpdateSourceTrigger=PropertyChanged. Przy bardziej złożonych danych bezpieczniej pozostawić aktualizację po opuszczeniu komórki i zatwierdzać cały wiersz.

Gdy użytkownik może wpisywać dane, model powinien implementować INotifyPropertyChanged. Bez tego tabela może zmienić obiekt, ale nie odświeżyć innych kontrolek, które korzystają z tej samej właściwości.

Do pól wyboru używam DataGridComboBoxColumn, a do niestandardowej prezentacji DataGridTemplateColumn. Ten drugi wariant pozwala mieć na przykład TextBlock w trybie wyświetlania i DatePicker po wejściu w edycję.

Przeczytaj również: var w JavaScripcie - zakres, hoisting i pułapki starego kodu

Walidację trzeba pokazać użytkownikowi

Najczęstszy błąd polega na przyjęciu, że typ decimal sam rozwiąże walidację ceny. Nie rozwiąże. Trzeba jeszcze obsłużyć pustą wartość, zakres liczby i komunikat, który wyjaśni, co użytkownik ma poprawić.

W prostych ekranach wystarczy binding z ValidatesOnExceptions. Przy większych formularzach lepiej użyć ValidationRule, IDataErrorInfo albo INotifyDataErrorInfo. Walidacja komórki sprawdza pojedynczą wartość, natomiast walidacja wiersza nadaje się do reguł zależnych od kilku pól, na przykład daty rozpoczęcia i zakończenia.

Sortowanie, filtrowanie i zaznaczanie rekordów

Kliknięcie nagłówka kolumny uruchamia domyślne sortowanie. To wystarcza dla prostych tabel, ale filtrowanie nie pojawia się automatycznie jako gotowy panel. Do takich operacji najwygodniej wykorzystać ICollectionView, czyli widok kolekcji, który potrafi nakładać sortowanie, filtrowanie i grupowanie bez zmieniania źródłowych danych.

public ICollectionView ProductView { get; }

public ProductsViewModel()
{
    Products = new ObservableCollection();
    ProductView = CollectionViewSource.GetDefaultView(Products);
}

public void FilterByName(string phrase)
{
    ProductView.Filter = item =>
    {
        var product = (Product)item;
        return string.IsNullOrWhiteSpace(phrase)
            || product.Name.Contains(phrase, StringComparison.OrdinalIgnoreCase);
    };
}

W XAML wystarczy wtedy podać jako źródło widok, a nie bezpośrednio kolekcję.

Jeżeli filtr reaguje na każdy znak wpisywany w pole tekstowe, przy dużej liczbie rekordów warto zastosować opóźnienie rzędu 200-300 ms. Bez niego każde naciśnięcie klawisza może uruchomić kosztowne filtrowanie i sprawić wrażenie, że aplikacja się zawiesza.

Do obsługi zaznaczonego rekordu najczęściej wystarczy SelectedItem. Gdy użytkownik ma wybrać wiele pozycji, ustawiam SelectionMode="Extended" i korzystam z SelectedItems. Ta właściwość nie jest klasyczną właściwością zależną WPF, dlatego w architekturze MVVM wymaga dodatkowego zachowania albo dołączonej właściwości.

Wydajność przy większej liczbie wierszy

Dla kilkudziesięciu lub kilkuset rekordów standardowa konfiguracja zwykle wystarcza. Problemy zaczynają się wtedy, gdy widok próbuje jednocześnie wyświetlić tysiące obiektów, rozbudowane szablony i obrazy. Samo włączenie wirtualizacji nie naprawi sytuacji, jeśli aplikacja wcześniej pobiera całą bazę do pamięci i wykonuje ciężkie operacje na wątku interfejsu.

Warto pozostawić włączoną wirtualizację wierszy i unikać ustawień, które wymuszają utworzenie wizualnych elementów dla wszystkich rekordów. Pomaga też ograniczenie liczby kolumn z własnymi kontrolkami oraz ładowanie danych partiami, na przykład po 100-500 elementów.

  • Do małych zbiorów użyj zwykłej kolekcji i prostego bindingu.
  • Przy dynamicznych zmianach wybierz ObservableCollection.
  • Przy dużych zbiorach filtruj i sortuj po stronie zapytania do bazy.
  • Nie twórz ciężkich kontrolek w każdej komórce, jeśli wystarczy tekst lub ikona.
  • Nie blokuj wątku UI podczas pobierania danych z SQL albo API.

Moja praktyczna zasada jest prosta. Jeśli użytkownik potrzebuje przeglądać 50 tysięcy rekordów, nie próbuję na siłę załadować wszystkich do jednego DataGridu. Wybieram paginację lub filtrowanie po stronie serwera, bo poprawa architektury źródła danych daje znacznie więcej niż kolejne sztuczki w XAML.

Typowe problemy i rozsądne rozwiązania

Problem Najczęstsza przyczyna Rozwiązanie
Puste komórki Niepoprawna nazwa właściwości w bindingu Sprawdź ścieżkę, typ obiektu i komunikaty bindingu w Output
Brak odświeżenia po dodaniu rekordu Użycie List zamiast kolekcji obserwowalnej Zastosuj ObservableCollection
Zmiana modelu niewidoczna w UI Brak INotifyPropertyChanged Dodaj powiadomienia w setterach
Niepożądane kolumny Automatyczne generowanie Ustaw AutoGenerateColumns="False"
Powolne filtrowanie Filtrowanie całej kolekcji przy każdym znaku Dodaj opóźnienie albo przenieś operację do bazy

Warto też pamiętać o różnicy między DataContext a ItemsSource. Pierwszy wskazuje obiekt, z którego binding pobiera właściwości, drugi określa konkretną kolekcję wierszy. Gdy tabela jest pusta, najpierw sprawdzam właśnie te dwa miejsca, a dopiero później wygląd kontrolki.

W aplikacjach korzystających z MVVM nie podpinam zdarzeń do kodu okna tylko po to, żeby wykonać operację na zaznaczonym wierszu. Lepszy jest command w ViewModelu, szczególnie gdy przycisk znajduje się w DataGridTemplateColumn. Dzięki temu logikę można łatwiej przetestować i ponownie wykorzystać.

Jaką konfigurację wybrać w konkretnej aplikacji

Do prototypu wystarczy ItemsSource i automatyczne kolumny. To dobry sposób, aby sprawdzić, czy model danych jest poprawny. Gdy ekran trafia do użytkowników, przechodzę na jawne kolumny, wyłączam niepotrzebną edycję i dodaję formatowanie, walidację oraz jasne komunikaty błędów.

Jeśli tabela ma być głównie raportem, ustawiam IsReadOnly="True" i skupiam się na sortowaniu, filtrowaniu oraz eksporcie. Jeżeli ma pełnić funkcję edytora, projektuję ją bardziej jak formularz rozłożony na wiersze, z ograniczoną liczbą edytowalnych pól i walidacją działającą przed zapisem.

Nie każda tabela powinna też być DataGridem. Gdy użytkownik ma edytować jeden rekord z wieloma zależnościami, klasyczny formularz będzie czytelniejszy. DataGrid wygrywa wtedy, gdy najważniejsza jest praca na wielu podobnych rekordach jednocześnie.

Od czego zacząć, żeby tabela nie urosła w problem

Najpierw przygotuj prosty model i sprawdź binding na kilku rekordach. Dopiero potem dodawaj edycję, filtry, grupowanie i własne szablony. Taka kolejność pozwala szybko odróżnić problem danych od problemu wyglądu.

W większości aplikacji desktopowych wystarczy połączenie ObservableCollection, jawnych kolumn, ICollectionView i walidacji. Najwięcej czasu tracimy nie na samą kontrolkę, lecz na niejasny model danych, brak powiadomień i próbę upchnięcia całej logiki biznesowej w XAML.

FAQ - Najczęstsze pytania

Ustaw właściwość ItemsSource na kolekcję, na przykład ObservableCollection<Product>, i przypisz ViewModel do DataContext. Dzięki temu kolumny mogą korzystać z właściwości obiektów, takich jak Name, Price czy IsAvailable.

ObservableCollection<T> powiadamia interfejs o dodaniu lub usunięciu elementu z kolekcji. INotifyPropertyChanged jest potrzebne wtedy, gdy zmienia się właściwość istniejącego obiektu i interfejs powinien odświeżyć jej wartość.

W aplikacji produkcyjnej warto ustawić AutoGenerateColumns="False" i jawnie zdefiniować kolumny. Do natychmiastowego zapisu wartości można użyć UpdateSourceTrigger=PropertyChanged, a walidację oprzeć na ValidationRule, IDataErrorInfo lub INotifyDataErrorInfo.

Przy tysiącach rekordów należy ograniczyć ciężkie szablony, pozostawić włączoną wirtualizację wierszy i rozważyć ładowanie danych partiami po 100-500 elementów. Dla bardzo dużych zbiorów lepsze będą paginacja oraz filtrowanie i sortowanie po stronie serwera.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

walidacja
wirtualizacja
datagrid
binding
observablecollection
Autor Bruno Krawczyk
Bruno Krawczyk
Mam na imię Bruno i od 8 lat zgłębiam tajniki programowania w ekosystemie .NET, chmury Azure oraz sztucznej inteligencji. Moja przygoda z technologią zaczęła się od ciekawości, jak złożone systemy mogą ułatwiać codzienne życie i rozwiązywać realne problemy. Dziś moją misją jest dzielenie się tą wiedzą, starając się przybliżyć nawet najbardziej skomplikowane zagadnienia w sposób zrozumiały i przystępny dla każdego. W moich artykułach na kursdotnet.pl skupiam się na praktycznych aspektach, analizuję najnowsze trendy i weryfikuję informacje, aby dostarczyć Wam treści, które są nie tylko dokładne i aktualne, ale przede wszystkim użyteczne w Waszej własnej ścieżce rozwoju technologicznego.

Udostępnij artykuł

Napisz komentarz