У попередній статті ми навчилися створювати базові таблиці даних через DataGrid та налаштовувати колонки. Але справжня сила DataGrid розкривається, коли ми починаємо працювати з великими наборами даних — тисячами рядків, складними фільтрами, динамічним сортуванням та inline-редагуванням.
У цій статті ми розглянемо просунуті можливості DataGrid, які перетворюють його з простої таблиці на потужний інструмент для роботи з даними — як у професійних enterprise-застосунках.
DataGrid (колонки, прив'язка даних, типи колонок) та розуміють концепції ICollectionView та ObservableCollection. Якщо ви вже створювали прості таблиці, але хочете додати інтерактивність — ви в правильному місці.Одна з найбільш очікуваних функцій будь-якої таблиці — можливість сортувати дані по колонках. DataGrid надає цю функціональність "з коробки", але також дозволяє повністю контролювати процес сортування.
За замовчуванням DataGrid дозволяє користувачам сортувати дані, просто клікаючи на заголовки колонок:
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="600" Height="350">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="💡 Клікніть на заголовок колонки для сортування"
Margin="0,0,0,10"
Foreground="#6b7280" />
<DataGrid Grid.Row="1"
AutoGenerateColumns="False"
CanUserSortColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Ім'я"
Binding="{Binding Name}"
Width="*" />
<DataGridTextColumn Header="Вік"
Binding="{Binding Age}"
Width="80" />
<DataGridTextColumn Header="Зарплата"
Binding="{Binding Salary, StringFormat='{}{0:C0}'}"
Width="120" />
<DataGridTextColumn Header="Відділ"
Binding="{Binding Department}"
Width="150" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Коли користувач клікає на заголовок колонки:
Ви можете контролювати, які колонки можна сортувати:
<!-- Заборонити сортування для всього DataGrid -->
<DataGrid CanUserSortColumns="False">
<!-- ... -->
</DataGrid>
<!-- Заборонити сортування для конкретної колонки -->
<DataGridTextColumn Header="ID"
Binding="{Binding Id}"
CanUserSort="False" />
Для більш складних сценаріїв — наприклад, сортування за кількома колонками або сортування при завантаженні — використовуйте 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+клік на "Ім'я" → дані відсортовані спочатку по відділу, потім по імені всередині кожного відділу.Для складної логіки сортування (наприклад, природне сортування чисел у рядках "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();
Фільтрація дозволяє показувати лише ті рядки, які відповідають певним критеріям, не змінюючи вихідну колекцію даних.
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="650" Height="400">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="Пошук:"
VerticalAlignment="Center"
Margin="0,0,10,0" />
<TextBox Width="200"
Watermark="Введіть ім'я..."
Margin="0,0,10,0" />
<Button Content="🔍 Знайти"
Padding="10,5" />
<Button Content="✖ Очистити"
Padding="10,5"
Margin="5,0,0,0" />
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="Відділ:"
VerticalAlignment="Center"
Margin="0,0,10,0" />
<ComboBox Width="150">
<ComboBoxItem Content="Всі" IsSelected="True" />
<ComboBoxItem Content="IT" />
<ComboBoxItem Content="HR" />
<ComboBoxItem Content="Sales" />
</ComboBox>
</StackPanel>
<DataGrid Grid.Row="2"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Ім'я"
Binding="{Binding Name}"
Width="*" />
<DataGridTextColumn Header="Вік"
Binding="{Binding Age}"
Width="80" />
<DataGridTextColumn Header="Відділ"
Binding="{Binding Department}"
Width="120" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Реалізація фільтрації у 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));
}
}
ICollectionView.Filter, фільтр застосовується до всієї колекції. Якщо у вас 10,000 рядків, predicate буде викликаний 10,000 разів. Для великих наборів даних розгляньте:Групування дозволяє організувати дані в ієрархічну структуру, де рядки згруповані за певною властивістю з можливістю згортання/розгортання груп.
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="650" Height="450">
<Grid Margin="20">
<DataGrid AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Ім'я"
Binding="{Binding Name}"
Width="*" />
<DataGridTextColumn Header="Посада"
Binding="{Binding Position}"
Width="150" />
<DataGridTextColumn Header="Зарплата"
Binding="{Binding Salary, StringFormat='{}{0:C0}'}"
Width="120" />
</DataGrid.Columns>
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Background="#f3f4f6"
Margin="-10,0">
<TextBlock Text="📁 "
FontSize="16"
Margin="10,5" />
<TextBlock Text="{Binding Name}"
FontWeight="Bold"
FontSize="14"
Margin="0,5" />
<TextBlock Text=" ("
Foreground="#6b7280"
Margin="5,5,0,5" />
<TextBlock Text="{Binding ItemCount}"
Foreground="#6b7280"
Margin="0,5" />
<TextBlock Text=" співробітників)"
Foreground="#6b7280"
Margin="0,5,10,5" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
</Grid>
</Window>
Налаштування групування у 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()));
Одна з найпотужніших можливостей 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);
Валідація забезпечує коректність даних, що вводяться користувачем.
Найсучасніший підхід — реалізація 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));
}
}
Ви можете налаштувати відображення помилок:
<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)...
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="700" Height="400">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="💡 Спробуйте ввести невалідні дані (порожнє ім'я, невірний email)"
Margin="0,0,0,10"
Foreground="#6b7280" />
<DataGrid Grid.Row="1"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Ім'я"
Binding="{Binding Name, ValidatesOnNotifyDataErrors=True}"
Width="*" />
<DataGridTextColumn Header="Email"
Binding="{Binding Email, ValidatesOnNotifyDataErrors=True}"
Width="200" />
<DataGridTextColumn Header="Зарплата"
Binding="{Binding Salary, ValidatesOnNotifyDataErrors=True}"
Width="120" />
</DataGrid.Columns>
</DataGrid>
<Border Grid.Row="2"
Background="#fef3c7"
BorderBrush="#f59e0b"
BorderThickness="1"
CornerRadius="4"
Padding="10"
Margin="0,10,0,0">
<TextBlock Text="⚠️ Комірки з помилками будуть підсвічені червоним"
Foreground="#92400e" />
</Border>
</Grid>
</Window>
DataGrid підтримує різні режими виділення для різних сценаріїв використання.
<!-- Виділення цілих рядків (за замовчуванням) -->
<DataGrid SelectionUnit="FullRow" />
<!-- Виділення окремих комірок -->
<DataGrid SelectionUnit="Cell" />
<!-- Виділення рядків або комірок -->
<DataGrid SelectionUnit="CellOrRowHeader" />
<!-- Одиночне виділення (за замовчуванням) -->
<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 — перевикористовує контейнери (швидше, менше пам'яті)Для дуже великих наборів даних завантажуйте дані порціями:
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 з конвертером -->
<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 => "Звільнений",
_ => "Невідомо"
};
}
Мета: Навчитися налаштовувати базове сортування та розуміти його поведінку.
Завдання: Створіть застосунок для перегляду списку книг з можливістю сортування.
Вимоги:
Book з властивостями: Title (string), Author (string), Year (int), Pages (int), Rating (double)ObservableCollection<Book> у ViewModelDataGrid з 5 колонкамиПідказка:
// Програмне сортування
var view = CollectionViewSource.GetDefaultView(Books);
view.SortDescriptions.Add(new SortDescription("Rating", ListSortDirection.Descending));
// Скидання сортування
view.SortDescriptions.Clear();
Мета: Опанувати комбінування фільтрації по різних критеріях.
Завдання: Розробіть систему перегляду замовлень інтернет-магазину з фільтрацією та пошуком.
Вимоги:
Order з властивостями:
Id (int)CustomerName (string)Product (string)Amount (decimal)Status (enum: Pending, Processing, Shipped, Delivered, Cancelled)OrderDate (DateTime)TextBox для пошуку по імені клієнта або назві продуктуComboBox для фільтрації по статусуDatePicker для фільтрації по даті (від/до)Додаткові виклики:
Мета: Створити повнофункціональну систему управління даними з 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")DataGridTemplateColumn)Приклад структури:
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.SortDescriptionsIComparerФільтрація:
ICollectionView.Filter predicateГрупування:
PropertyGroupDescriptionGroupStyleРедагування:
BeginningEdit, CellEditEnding, RowEditEndingIsReadOnly, CanUserAddRows, CanUserDeleteRowsВалідація:
INotifyDataErrorInfo для сучасної валідаціїErrorTemplate для відображення помилокSelection:
FullRow, Cell, CellOrRowHeaderПродуктивність:
Standard та RecyclingНаступні кроки:
У наступній статті ми розглянемо TreeView для ієрархічних даних та ListView з GridView для табличного відображення без редагування.