Desktop UI

DataGrid — сортування, фільтрація, редагування

Просунуті можливості DataGrid для роботи з великими наборами даних — сортування, фільтрація, групування, inline-редагування та валідація

DataGrid: сортування, фільтрація, редагування

У попередній статті ми навчилися створювати базові таблиці даних через DataGrid та налаштовувати колонки. Але справжня сила DataGrid розкривається, коли ми починаємо працювати з великими наборами даних — тисячами рядків, складними фільтрами, динамічним сортуванням та inline-редагуванням.

У цій статті ми розглянемо просунуті можливості DataGrid, які перетворюють його з простої таблиці на потужний інструмент для роботи з даними — як у професійних enterprise-застосунках.

Для кого ця стаття?Ця стаття призначена для студентів, які вже знайомі з базовими можливостями DataGrid (колонки, прив'язка даних, типи колонок) та розуміють концепції ICollectionView та ObservableCollection. Якщо ви вже створювали прості таблиці, але хочете додати інтерактивність — ви в правильному місці.

Сортування: від простого кліку до складних правил

Одна з найбільш очікуваних функцій будь-якої таблиці — можливість сортувати дані по колонках. DataGrid надає цю функціональність "з коробки", але також дозволяє повністю контролювати процес сортування.

Автоматичне сортування

За замовчуванням DataGrid дозволяє користувачам сортувати дані, просто клікаючи на заголовки колонок:

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Коли користувач клікає на заголовок колонки:

  1. Перший клік — сортування за зростанням (стрілка вгору ▲)
  2. Другий клік — сортування за спаданням (стрілка вниз ▼)
  3. Третій клік — скасування сортування (повернення до початкового порядку)

Контроль сортування через властивості

Ви можете контролювати, які колонки можна сортувати:

<!-- Заборонити сортування для всього DataGrid -->
<DataGrid CanUserSortColumns="False">
    <!-- ... -->
</DataGrid>

<!-- Заборонити сортування для конкретної колонки -->
<DataGridTextColumn Header="ID" 
                    Binding="{Binding Id}" 
                    CanUserSort="False" />

Програмне сортування через ICollectionView

Для більш складних сценаріїв — наприклад, сортування за кількома колонками або сортування при завантаженні — використовуйте ICollectionView:

public class EmployeesViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Employee> _employees;
    public ICollectionView EmployeesView { get; }
    
    public EmployeesViewModel()
    {
        _employees = new ObservableCollection<Employee>
        {
            new Employee { Name = "Іван", Age = 30, Salary = 50000, Department = "IT" },
            new Employee { Name = "Марія", Age = 25, Salary = 45000, Department = "HR" },
            new Employee { Name = "Петро", Age = 35, Salary = 60000, Department = "IT" },
            new Employee { Name = "Олена", Age = 28, Salary = 48000, Department = "Sales" }
        };
        
        EmployeesView = CollectionViewSource.GetDefaultView(_employees);
        
        // Сортування за замовчуванням: спочатку по відділу, потім по імені
        EmployeesView.SortDescriptions.Add(
            new SortDescription("Department", ListSortDirection.Ascending));
        EmployeesView.SortDescriptions.Add(
            new SortDescription("Name", ListSortDirection.Ascending));
    }
    
    public void SortBySalaryDescending()
    {
        EmployeesView.SortDescriptions.Clear();
        EmployeesView.SortDescriptions.Add(
            new SortDescription("Salary", ListSortDirection.Descending));
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
}

XAML прив'язка:

<DataGrid ItemsSource="{Binding EmployeesView}" 
          AutoGenerateColumns="False">
    <!-- Колонки -->
</DataGrid>
Множинне сортуванняКористувачі можуть сортувати за кількома колонками, утримуючи Shift під час кліку на заголовки. Наприклад: клік на "Відділ" → Shift+клік на "Ім'я" → дані відсортовані спочатку по відділу, потім по імені всередині кожного відділу.

Кастомне сортування через IComparer

Для складної логіки сортування (наприклад, природне сортування чисел у рядках "Item1, Item2, Item10" замість "Item1, Item10, Item2") використовуйте кастомний IComparer:

public class NaturalStringComparer : IComparer
{
    public int Compare(object x, object y)
    {
        string s1 = x?.ToString() ?? "";
        string s2 = y?.ToString() ?? "";
        
        // Логіка природного сортування
        // (спрощена версія для демонстрації)
        return string.Compare(s1, s2, StringComparison.OrdinalIgnoreCase);
    }
}

// Використання:
EmployeesView.CustomSort = new NaturalStringComparer();

Фільтрація: показуємо тільки потрібне

Фільтрація дозволяє показувати лише ті рядки, які відповідають певним критеріям, не змінюючи вихідну колекцію даних.

Базова фільтрація через ICollectionView

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Реалізація фільтрації у ViewModel:

public class EmployeesViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Employee> _employees;
    private string _searchText;
    private string _selectedDepartment;
    
    public ICollectionView EmployeesView { get; }
    
    public string SearchText
    {
        get => _searchText;
        set
        {
            _searchText = value;
            OnPropertyChanged();
            ApplyFilter();
        }
    }
    
    public string SelectedDepartment
    {
        get => _selectedDepartment;
        set
        {
            _selectedDepartment = value;
            OnPropertyChanged();
            ApplyFilter();
        }
    }
    
    public ICommand ClearFilterCommand { get; }
    
    public EmployeesViewModel()
    {
        _employees = LoadEmployees();
        EmployeesView = CollectionViewSource.GetDefaultView(_employees);
        
        ClearFilterCommand = new RelayCommand(ClearFilter);
    }
    
    private void ApplyFilter()
    {
        EmployeesView.Filter = item =>
        {
            if (item is not Employee employee)
                return false;
            
            // Фільтр по імені
            bool matchesSearch = string.IsNullOrWhiteSpace(SearchText) ||
                employee.Name.Contains(SearchText, StringComparison.OrdinalIgnoreCase);
            
            // Фільтр по відділу
            bool matchesDepartment = string.IsNullOrWhiteSpace(SelectedDepartment) ||
                SelectedDepartment == "Всі" ||
                employee.Department == SelectedDepartment;
            
            return matchesSearch && matchesDepartment;
        };
    }
    
    private void ClearFilter()
    {
        SearchText = string.Empty;
        SelectedDepartment = "Всі";
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}
Важливо про FilterКоли ви встановлюєте ICollectionView.Filter, фільтр застосовується до всієї колекції. Якщо у вас 10,000 рядків, predicate буде викликаний 10,000 разів. Для великих наборів даних розгляньте:
  • Дебаунсинг введення (затримка перед застосуванням фільтру)
  • Фільтрацію на рівні бази даних (LINQ to Entities)
  • Віртуалізацію з підвантаженням даних

Групування: організація даних за категоріями

Групування дозволяє організувати дані в ієрархічну структуру, де рядки згруповані за певною властивістю з можливістю згортання/розгортання груп.

Базове групування

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Налаштування групування у ViewModel:

public class EmployeesViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Employee> _employees;
    public ICollectionView EmployeesView { get; }
    
    public EmployeesViewModel()
    {
        _employees = LoadEmployees();
        EmployeesView = CollectionViewSource.GetDefaultView(_employees);
        
        // Групування по відділу
        EmployeesView.GroupDescriptions.Add(
            new PropertyGroupDescription("Department"));
        
        // Сортування всередині груп
        EmployeesView.SortDescriptions.Add(
            new SortDescription("Department", ListSortDirection.Ascending));
        EmployeesView.SortDescriptions.Add(
            new SortDescription("Name", ListSortDirection.Ascending));
    }
}

Множинне групування

Ви можете групувати за кількома рівнями:

// Спочатку по відділу, потім по посаді
EmployeesView.GroupDescriptions.Add(
    new PropertyGroupDescription("Department"));
EmployeesView.GroupDescriptions.Add(
    new PropertyGroupDescription("Position"));

Кастомне групування

Для складної логіки групування створіть власний IValueConverter:

public class SalaryRangeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is decimal salary)
        {
            if (salary < 30000) return "Низька (< 30k)";
            if (salary < 50000) return "Середня (30k-50k)";
            if (salary < 70000) return "Висока (50k-70k)";
            return "Дуже висока (> 70k)";
        }
        return "Невідомо";
    }
    
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

// Використання:
EmployeesView.GroupDescriptions.Add(
    new PropertyGroupDescription("Salary", new SalaryRangeConverter()));

Редагування: inline-зміна даних

Одна з найпотужніших можливостей DataGrid — редагування даних безпосередньо в таблиці без окремих форм.

Базове редагування

За замовчуванням DataGrid дозволяє редагувати комірки подвійним кліком або натисканням F2:

<DataGrid ItemsSource="{Binding Employees}" 
          AutoGenerateColumns="False"
          CanUserAddRows="True"
          CanUserDeleteRows="True">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Ім'я" 
                            Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged}" 
                            Width="*" />
        <DataGridTextColumn Header="Email" 
                            Binding="{Binding Email, UpdateSourceTrigger=PropertyChanged}" 
                            Width="200" />
        <DataGridCheckBoxColumn Header="Активний" 
                                Binding="{Binding IsActive}" 
                                Width="100" />
    </DataGrid.Columns>
</DataGrid>

Контроль редагування

Ви можете контролювати, що можна редагувати:

<!-- Заборонити редагування для всього DataGrid -->
<DataGrid IsReadOnly="True">
    <!-- ... -->
</DataGrid>

<!-- Заборонити редагування для конкретної колонки -->
<DataGridTextColumn Header="ID" 
                    Binding="{Binding Id}" 
                    IsReadOnly="True" />

<!-- Заборонити додавання нових рядків -->
<DataGrid CanUserAddRows="False" />

<!-- Заборонити видалення рядків -->
<DataGrid CanUserDeleteRows="False" />

Події редагування

DataGrid надає події для контролю процесу редагування:

public class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        
        var dataGrid = this.FindControl<DataGrid>("EmployeesGrid");
        
        // Початок редагування комірки
        dataGrid.BeginningEdit += (s, e) =>
        {
            var employee = e.Row.DataContext as Employee;
            Debug.WriteLine($"Початок редагування: {employee?.Name}");
            
            // Можна скасувати редагування
            // e.Cancel = true;
        };
        
        // Завершення редагування комірки
        dataGrid.CellEditEnding += (s, e) =>
        {
            if (e.EditAction == DataGridEditAction.Commit)
            {
                var employee = e.Row.DataContext as Employee;
                Debug.WriteLine($"Збереження змін: {employee?.Name}");
            }
            else
            {
                Debug.WriteLine("Скасування змін");
            }
        };
        
        // Завершення редагування рядка
        dataGrid.RowEditEnding += (s, e) =>
        {
            if (e.EditAction == DataGridEditAction.Commit)
            {
                var employee = e.Row.DataContext as Employee;
                Debug.WriteLine($"Рядок збережено: {employee?.Name}");
                
                // Тут можна зберегти в базу даних
                // await _repository.UpdateAsync(employee);
            }
        };
    }
}

Програмне керування редагуванням

// Почати редагування конкретної комірки
dataGrid.CurrentCell = new DataGridCellInfo(employee, dataGrid.Columns[1]);
dataGrid.BeginEdit();

// Завершити редагування з збереженням
dataGrid.CommitEdit(DataGridEditingUnit.Row, true);

// Скасувати редагування
dataGrid.CancelEdit(DataGridEditingUnit.Row);

Валідація у DataGrid

Валідація забезпечує коректність даних, що вводяться користувачем.

Валідація через INotifyDataErrorInfo

Найсучасніший підхід — реалізація INotifyDataErrorInfo у моделі:

public class Employee : INotifyPropertyChanged, INotifyDataErrorInfo
{
    private string _name;
    private string _email;
    private decimal _salary;
    private readonly Dictionary<string, List<string>> _errors = new();
    
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            OnPropertyChanged();
            ValidateName();
        }
    }
    
    public string Email
    {
        get => _email;
        set
        {
            _email = value;
            OnPropertyChanged();
            ValidateEmail();
        }
    }
    
    public decimal Salary
    {
        get => _salary;
        set
        {
            _salary = value;
            OnPropertyChanged();
            ValidateSalary();
        }
    }
    
    private void ValidateName()
    {
        ClearErrors(nameof(Name));
        
        if (string.IsNullOrWhiteSpace(Name))
            AddError(nameof(Name), "Ім'я обов'язкове");
        else if (Name.Length < 2)
            AddError(nameof(Name), "Ім'я має бути не менше 2 символів");
        else if (Name.Length > 50)
            AddError(nameof(Name), "Ім'я має бути не більше 50 символів");
    }
    
    private void ValidateEmail()
    {
        ClearErrors(nameof(Email));
        
        if (string.IsNullOrWhiteSpace(Email))
            AddError(nameof(Email), "Email обов'язковий");
        else if (!Email.Contains("@"))
            AddError(nameof(Email), "Невірний формат email");
    }
    
    private void ValidateSalary()
    {
        ClearErrors(nameof(Salary));
        
        if (Salary < 0)
            AddError(nameof(Salary), "Зарплата не може бути від'ємною");
        else if (Salary > 1000000)
            AddError(nameof(Salary), "Зарплата занадто велика");
    }
    
    private void AddError(string propertyName, string error)
    {
        if (!_errors.ContainsKey(propertyName))
            _errors[propertyName] = new List<string>();
        
        if (!_errors[propertyName].Contains(error))
        {
            _errors[propertyName].Add(error);
            OnErrorsChanged(propertyName);
        }
    }
    
    private void ClearErrors(string propertyName)
    {
        if (_errors.Remove(propertyName))
            OnErrorsChanged(propertyName);
    }
    
    public bool HasErrors => _errors.Any();
    
    public IEnumerable GetErrors(string propertyName)
    {
        return _errors.ContainsKey(propertyName) 
            ? _errors[propertyName] 
            : Enumerable.Empty<string>();
    }
    
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected void OnErrorsChanged(string propertyName)
    {
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    }
    
    protected void OnPropertyChanged([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

Кастомний ErrorTemplate

Ви можете налаштувати відображення помилок:

<DataGrid ItemsSource="{Binding Employees}">
    <DataGrid.RowValidationErrorTemplate>
        <ControlTemplate>
            <Grid>
                <Ellipse Width="12" Height="12" 
                         Fill="#ef4444" 
                         ToolTip.Tip="{Binding RelativeSource={RelativeSource AncestorType=DataGridRow}, 
                                       Path=(Validation.Errors)[0].ErrorContent}" />
            </Grid>
        </ControlTemplate>
    </DataGrid.RowValidationErrorTemplate>
</DataGrid>

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Avalonia vs WPF: В Avalonia валідація працює аналогічно, але деякі властивості можуть мати інші назви. Завжди перевіряйте документацію Avalonia для специфічних деталей валідації.

Selection: режими виділення

DataGrid підтримує різні режими виділення для різних сценаріїв використання.

SelectionUnit: що можна виділяти

<!-- Виділення цілих рядків (за замовчуванням) -->
<DataGrid SelectionUnit="FullRow" />

<!-- Виділення окремих комірок -->
<DataGrid SelectionUnit="Cell" />

<!-- Виділення рядків або комірок -->
<DataGrid SelectionUnit="CellOrRowHeader" />

SelectionMode: скільки можна виділити

<!-- Одиночне виділення (за замовчуванням) -->
<DataGrid SelectionMode="Single" />

<!-- Множинне виділення -->
<DataGrid SelectionMode="Extended" />

Робота з виділенням у коді

public class EmployeesViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Employee> _employees;
    private Employee _selectedEmployee;
    private ObservableCollection<Employee> _selectedEmployees;
    
    // Одиночне виділення
    public Employee SelectedEmployee
    {
        get => _selectedEmployee;
        set
        {
            _selectedEmployee = value;
            OnPropertyChanged();
            
            // Реакція на зміну виділення
            if (value != null)
            {
                Debug.WriteLine($"Виділено: {value.Name}");
            }
        }
    }
    
    // Множинне виділення
    public ObservableCollection<Employee> SelectedEmployees
    {
        get => _selectedEmployees;
        set
        {
            _selectedEmployees = value;
            OnPropertyChanged();
        }
    }
    
    public ICommand DeleteSelectedCommand { get; }
    
    public EmployeesViewModel()
    {
        _employees = LoadEmployees();
        _selectedEmployees = new ObservableCollection<Employee>();
        
        DeleteSelectedCommand = new RelayCommand(
            execute: DeleteSelected,
            canExecute: () => SelectedEmployees?.Count > 0);
    }
    
    private void DeleteSelected()
    {
        var itemsToDelete = SelectedEmployees.ToList();
        foreach (var employee in itemsToDelete)
        {
            _employees.Remove(employee);
        }
    }
}

XAML прив'язка:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    
    <DataGrid Grid.Row="0"
              ItemsSource="{Binding Employees}"
              SelectedItem="{Binding SelectedEmployee}"
              SelectionMode="Extended">
        <!-- Колонки -->
    </DataGrid>
    
    <StackPanel Grid.Row="1" 
                Orientation="Horizontal" 
                Margin="0,10,0,0">
        <Button Content="🗑️ Видалити виділені" 
                Command="{Binding DeleteSelectedCommand}" 
                Padding="10,5" />
        <TextBlock Text="{Binding SelectedEmployees.Count, StringFormat='Виділено: {0}'}" 
                   VerticalAlignment="Center" 
                   Margin="15,0,0,0"
                   Foreground="#6b7280" />
    </StackPanel>
</Grid>

Програмне виділення

// Виділити конкретний рядок
dataGrid.SelectedItem = employees[0];

// Виділити кілька рядків
dataGrid.SelectedItems.Clear();
dataGrid.SelectedItems.Add(employees[0]);
dataGrid.SelectedItems.Add(employees[2]);

// Виділити всі рядки
dataGrid.SelectAll();

// Зняти виділення
dataGrid.UnselectAll();

// Прокрутити до виділеного елемента
dataGrid.ScrollIntoView(dataGrid.SelectedItem);

Продуктивність: робота з великими наборами даних

Коли у вас тисячі або десятки тисяч рядків, важливо оптимізувати продуктивність DataGrid.

Віртуалізація

За замовчуванням DataGrid використовує віртуалізацію — створює візуальні елементи тільки для видимих рядків:

<!-- Віртуалізація увімкнена за замовчуванням -->
<DataGrid VirtualizingPanel.IsVirtualizing="True"
          VirtualizingPanel.VirtualizationMode="Recycling">
    <!-- Колонки -->
</DataGrid>

Режими віртуалізації:

  • Standard — створює нові контейнери для кожного рядка
  • Recycling — перевикористовує контейнери (швидше, менше пам'яті)

Відкладене завантаження (Lazy Loading)

Для дуже великих наборів даних завантажуйте дані порціями:

public class EmployeesViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Employee> _employees;
    private int _currentPage = 0;
    private const int PageSize = 100;
    
    public ObservableCollection<Employee> Employees
    {
        get => _employees;
        set
        {
            _employees = value;
            OnPropertyChanged();
        }
    }
    
    public ICommand LoadMoreCommand { get; }
    
    public EmployeesViewModel()
    {
        _employees = new ObservableCollection<Employee>();
        LoadMoreCommand = new AsyncRelayCommand(LoadMoreAsync);
        
        // Завантажити першу порцію
        _ = LoadMoreAsync();
    }
    
    private async Task LoadMoreAsync()
    {
        var newEmployees = await _repository.GetPageAsync(_currentPage, PageSize);
        
        foreach (var employee in newEmployees)
        {
            Employees.Add(employee);
        }
        
        _currentPage++;
    }
}

Оптимізація Binding

Для великих таблиць уникайте складних Binding виразів:

<!-- ❌ Повільно: складний Binding з конвертером -->
<DataGridTextColumn Header="Статус" 
                    Binding="{Binding Status, Converter={StaticResource StatusToTextConverter}}" />

<!-- ✅ Швидко: проста властивість -->
<DataGridTextColumn Header="Статус" 
                    Binding="{Binding StatusText}" />

У моделі:

public class Employee
{
    public EmployeeStatus Status { get; set; }
    
    // Обчислювана властивість для швидкого Binding
    public string StatusText => Status switch
    {
        EmployeeStatus.Active => "Активний",
        EmployeeStatus.OnLeave => "У відпустці",
        EmployeeStatus.Terminated => "Звільнений",
        _ => "Невідомо"
    };
}

Практичні завдання

Рівень 1: DataGrid з сортуванням

Мета: Навчитися налаштовувати базове сортування та розуміти його поведінку.

Завдання: Створіть застосунок для перегляду списку книг з можливістю сортування.

Вимоги:

  • Створіть клас Book з властивостями: Title (string), Author (string), Year (int), Pages (int), Rating (double)
  • Використайте ObservableCollection<Book> у ViewModel
  • Створіть DataGrid з 5 колонками
  • Увімкніть сортування для всіх колонок
  • Додайте програмне сортування за замовчуванням (по рейтингу, спадання)
  • Додайте кнопку "Скинути сортування"

Підказка:

// Програмне сортування
var view = CollectionViewSource.GetDefaultView(Books);
view.SortDescriptions.Add(new SortDescription("Rating", ListSortDirection.Descending));

// Скидання сортування
view.SortDescriptions.Clear();

Рівень 2: Таблиця замовлень з фільтрацією та пошуком

Мета: Опанувати комбінування фільтрації по різних критеріях.

Завдання: Розробіть систему перегляду замовлень інтернет-магазину з фільтрацією та пошуком.

Вимоги:

  • Створіть клас Order з властивостями:
    • Id (int)
    • CustomerName (string)
    • Product (string)
    • Amount (decimal)
    • Status (enum: Pending, Processing, Shipped, Delivered, Cancelled)
    • OrderDate (DateTime)
  • Реалізуйте фільтрацію:
    • TextBox для пошуку по імені клієнта або назві продукту
    • ComboBox для фільтрації по статусу
    • DatePicker для фільтрації по даті (від/до)
  • Додайте лічильник відфільтрованих записів
  • Реалізуйте кнопку "Очистити всі фільтри"
  • Додайте сортування за замовчуванням (по даті, найновіші спочатку)

Додаткові виклики:

  • Дебаунсинг для пошуку (затримка 300мс перед застосуванням фільтру)
  • Збереження стану фільтрів при закритті застосунку
  • Експорт відфільтрованих даних у CSV

Рівень 3: Повний CRUD через DataGrid

Мета: Створити повнофункціональну систему управління даними з inline-редагуванням, валідацією та збереженням.

Завдання: Розробіть систему управління співробітниками компанії з повним CRUD функціоналом.

Вимоги:

  • Створіть клас Employee з INotifyPropertyChanged та INotifyDataErrorInfo:
    • Id (int, read-only)
    • FirstName (string, обов'язкове, 2-50 символів)
    • LastName (string, обов'язкове, 2-50 символів)
    • Email (string, обов'язкове, валідний email)
    • Phone (string, формат +380XXXXXXXXX)
    • Department (enum: IT, HR, Sales, Finance, Management)
    • Position (string)
    • Salary (decimal, > 0, < 1,000,000)
    • HireDate (DateTime, не в майбутньому)
    • IsActive (bool)
  • Реалізуйте функціональність:
    • Додавання нового співробітника (кнопка "Додати" або CanUserAddRows="True")
    • Редагування inline з валідацією
    • Видалення з підтвердженням (кнопка в DataGridTemplateColumn)
    • Збереження змін у SQLite через Repository pattern
    • Відміна змін (кнопка "Скасувати")
  • Додайте візуальні індикатори:
    • Червона рамка для комірок з помилками
    • Tooltip з текстом помилки
    • Іконка помилки біля рядка
    • Disabled кнопка "Зберегти" якщо є помилки валідації
  • Реалізуйте фільтрацію та сортування:
    • Пошук по імені/прізвищу
    • Фільтр по відділу
    • Фільтр "Тільки активні"
    • Групування по відділу з підрахунком
  • Додайте статистику:
    • Загальна кількість співробітників
    • Середня зарплата
    • Розподіл по відділах

Приклад структури:

public class EmployeesViewModel : ObservableObject
{
    private readonly IEmployeeRepository _repository;
    private ObservableCollection<Employee> _employees;
    
    public ICollectionView EmployeesView { get; }
    public ICommand AddCommand { get; }
    public ICommand SaveCommand { get; }
    public ICommand DeleteCommand { get; }
    public ICommand CancelCommand { get; }
    
    public bool HasErrors => Employees.Any(e => e.HasErrors);
    
    private async Task SaveAsync()
    {
        if (HasErrors)
        {
            // Показати повідомлення про помилки
            return;
        }
        
        foreach (var employee in Employees)
        {
            if (employee.Id == 0)
                await _repository.AddAsync(employee);
            else
                await _repository.UpdateAsync(employee);
        }
    }
}

Резюме

У цій статті ми розглянули просунуті можливості DataGrid:

Сортування:

  • Автоматичне сортування по кліку на заголовок
  • Програмне сортування через ICollectionView.SortDescriptions
  • Множинне сортування (Shift+клік)
  • Кастомне сортування через IComparer

Фільтрація:

  • Фільтрація через ICollectionView.Filter predicate
  • Комбінування кількох критеріїв фільтрації
  • Динамічне оновлення фільтрів

Групування:

  • Групування через PropertyGroupDescription
  • Множинне групування за кількома рівнями
  • Кастомні заголовки груп через GroupStyle

Редагування:

  • Inline-редагування комірок
  • Події BeginningEdit, CellEditEnding, RowEditEnding
  • Програмне керування редагуванням
  • Контроль через IsReadOnly, CanUserAddRows, CanUserDeleteRows

Валідація:

  • INotifyDataErrorInfo для сучасної валідації
  • Кастомні ErrorTemplate для відображення помилок
  • Валідація на рівні комірки та рядка

Selection:

  • Режими виділення: FullRow, Cell, CellOrRowHeader
  • Одиночне та множинне виділення
  • Програмне керування виділенням

Продуктивність:

  • Віртуалізація для великих наборів даних
  • Режими Standard та Recycling
  • Відкладене завантаження (Lazy Loading)
  • Оптимізація Binding

Наступні кроки: У наступній статті ми розглянемо TreeView для ієрархічних даних та ListView з GridView для табличного відображення без редагування.

Глосарій

Основні терміни:
  • ICollectionView — інтерфейс для роботи з колекціями (сортування, фільтрація, групування)
  • SortDescription — опис правила сортування (властивість + напрямок)
  • PropertyGroupDescription — опис правила групування по властивості
  • Filter — predicate-функція для фільтрації елементів колекції
  • SelectionUnit — що можна виділяти (рядок, комірка, обидва)
  • SelectionMode — скільки можна виділити (один, багато)
  • BeginningEdit — подія початку редагування
  • CellEditEnding — подія завершення редагування комірки
  • RowEditEnding — подія завершення редагування рядка
  • INotifyDataErrorInfo — інтерфейс для валідації з підтримкою кількох помилок
  • Віртуалізація — техніка створення UI-елементів тільки для видимих рядків
  • Recycling — режим віртуалізації з перевикористанням контейнерів
  • Lazy Loading — відкладене завантаження даних порціями

Додаткові ресурси

📚 Microsoft Docs: DataGrid Sorting

Офіційна документація по сортуванню в DataGrid з прикладами

🎨 WPF Tutorial: DataGrid Filtering

Детальний туторіал по фільтрації DataGrid

⚡ Performance Tips: DataGrid

Рекомендації по оптимізації продуктивності контролів WPF

🔧 GitHub: DataGrid Examples

Офіційні приклади складних сценаріїв використання DataGrid