Коли ви працюєте з даними у desktop-застосунках, рано чи пізно виникає потреба відобразити список об'єктів — продуктів, користувачів, замовлень, повідомлень. У WPF та Avalonia для цього існує потужна система контролів колекцій, яка дозволяє не просто показати дані, а й повністю контролювати їхній вигляд, поведінку та продуктивність.
У цій статті ми розберемо архітектуру ItemsControl — базового класу для всіх контролів колекцій, дослідимо, як WPF перетворює колекцію об'єктів на UI-елементи, і навчимося створювати складні візуалізації даних через ListBox та ListView.
Уявіть, що вам потрібно відобразити список із 100 продуктів. Ви могли б створити 100 окремих TextBlock елементів вручну, але це було б абсолютно непрактично. Контроли колекцій вирішують цю проблему елегантно:
🔄 Автоматична генерація UI
📊 Шаблонізація
DataTemplate), який застосовується до всіх елементів колекції.⚡ Віртуалізація
🎯 Інтерактивність
ItemsControl — це базовий клас для всіх контролів, що відображають колекції даних. Від нього успадковуються ListBox, ListView, ComboBox, TreeView та інші. Розуміння його архітектури — ключ до майстерного володіння контролами колекцій.
Коли ви встановлюєте властивість ItemsSource, запускається складний процес перетворення даних на візуальні елементи:
Розберемо кожен етап детально:
1. ItemsSource — це колекція об'єктів, яку ви прив'язуєте до контрола. Це може бути List<T>, ObservableCollection<T>, масив або будь-який інший тип, що реалізує IEnumerable.
2. ItemContainerGenerator — внутрішній механізм WPF, який відповідає за створення контейнерів для кожного елемента даних. Він знає, який тип контейнера потрібен (для ListBox це ListBoxItem, для ComboBox — ComboBoxItem).
3. ItemsPanel — панель, яка розташовує контейнери. За замовчуванням це VirtualizingStackPanel (вертикальний список), але ви можете замінити її на WrapPanel, UniformGrid або навіть Canvas.
4. ItemContainer — обгортка навколо кожного елемента даних. Вона надає функціональність виділення, hover-ефектів, фокусу та інших інтерактивних можливостей.
5. ContentPresenter — елемент, який відображає вміст контейнера згідно з DataTemplate.
6. DataTemplate — шаблон, який визначає, як саме виглядатиме кожен елемент даних.
Розуміння цієї архітектури дозволяє вам:
ItemsPanel.ItemTemplate (шаблон даних) та ItemContainerStyle (стиль контейнера), ви можете створювати складні візуалізації.За замовчуванням ItemsControl використовує VirtualizingStackPanel для вертикального розташування елементів. Але що, якщо вам потрібен горизонтальний список? Або сітка? Або навіть довільне розташування?
Властивість ItemsPanel дозволяє замінити стандартну панель на будь-яку іншу:
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="200">
<ListBox Margin="20">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBoxItem Content="Елемент 1" />
<ListBoxItem Content="Елемент 2" />
<ListBoxItem Content="Елемент 3" />
<ListBoxItem Content="Елемент 4" />
<ListBoxItem Content="Елемент 5" />
</ListBox>
</Window>
ListBoxItem. У реальному WPF елементи матимуть більш виражені рамки при виділенні.WrapPanel автоматично переносить елементи на новий рядок, коли вони не вміщуються по ширині. Це ідеально для галерей зображень або карткових інтерфейсів:
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="500" Height="400">
<ListBox Margin="20" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Width="100" Height="100"
Background="#3b82f6"
CornerRadius="8"
Margin="5">
<TextBlock Text="{Binding}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="White"
FontWeight="Bold" />
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBoxItem Content="1" />
<ListBoxItem Content="2" />
<ListBoxItem Content="3" />
<ListBoxItem Content="4" />
<ListBoxItem Content="5" />
<ListBoxItem Content="6" />
<ListBoxItem Content="7" />
<ListBoxItem Content="8" />
</ListBox>
</Window>
UniformGrid розподіляє елементи по рівномірній сітці з однаковими розмірами комірок:
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="400">
<ListBox Margin="20">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="#f3f4f6"
CornerRadius="8"
Padding="15"
Margin="5">
<StackPanel>
<TextBlock Text="{Binding}"
FontWeight="Bold"
FontSize="16" />
<TextBlock Text="$99.99"
Foreground="#6b7280"
Margin="0,5,0,0" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBoxItem Content="Продукт 1" />
<ListBoxItem Content="Продукт 2" />
<ListBoxItem Content="Продукт 3" />
<ListBoxItem Content="Продукт 4" />
<ListBoxItem Content="Продукт 5" />
<ListBoxItem Content="Продукт 6" />
</ListBox>
</Window>
| Панель | Розташування | Віртуалізація | Використання |
|---|---|---|---|
StackPanel | Вертикально/горизонтально | ❌ Ні | Невеликі списки |
VirtualizingStackPanel | Вертикально/горизонтально | ✅ Так | Великі списки (за замовчуванням) |
WrapPanel | Адаптивна сітка | ❌ Ні | Галереї, теги |
UniformGrid | Рівномірна сітка | ❌ Ні | Каталоги продуктів |
Canvas | Абсолютне позиціонування | ❌ Ні | Діаграми, графіки |
VirtualizingStackPanel. Без віртуалізації WPF створить візуальні елементи для всіх об'єктів одразу, що призведе до затримок при завантаженні та великого споживання пам'яті.Коли ви працюєте з ListBox, кожен елемент даних обгортається в ListBoxItem — це і є контейнер. ItemContainerStyle дозволяє стилізувати ці контейнери, змінюючи їхній вигляд при наведенні, виділенні або в звичайному стані.
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<ListBox Margin="20">
<ListBox.ItemContainerStyle>
<Style Selector="ListBoxItem">
<Setter Property="Margin" Value="0,0,0,10" />
<Setter Property="Padding" Value="15" />
<Setter Property="Background" Value="#f3f4f6" />
<Setter Property="CornerRadius" Value="8" />
<Style Selector="^:pointerover">
<Setter Property="Background" Value="#e5e7eb" />
</Style>
<Style Selector="^:selected">
<Setter Property="Background" Value="#3b82f6" />
<Setter Property="Foreground" Value="White" />
</Style>
</Style>
</ListBox.ItemContainerStyle>
<ListBoxItem Content="Перший елемент" />
<ListBoxItem Content="Другий елемент" />
<ListBoxItem Content="Третій елемент" />
<ListBoxItem Content="Четвертий елемент" />
</ListBox>
</Window>
Для покращення читабельності довгих списків часто використовують чергування кольорів рядків. WPF надає для цього властивість AlternationCount:
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="350">
<ListBox Margin="20" AlternationCount="2">
<ListBox.ItemContainerStyle>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="15,10" />
<Setter Property="Background" Value="White" />
<Style Selector="^:nth-child(2n)">
<Setter Property="Background" Value="#f9fafb" />
</Style>
<Style Selector="^:selected">
<Setter Property="Background" Value="#3b82f6" />
<Setter Property="Foreground" Value="White" />
</Style>
</Style>
</ListBox.ItemContainerStyle>
<ListBoxItem Content="Рядок 1" />
<ListBoxItem Content="Рядок 2" />
<ListBoxItem Content="Рядок 3" />
<ListBoxItem Content="Рядок 4" />
<ListBoxItem Content="Рядок 5" />
<ListBoxItem Content="Рядок 6" />
<ListBoxItem Content="Рядок 7" />
<ListBoxItem Content="Рядок 8" />
</ListBox>
</Window>
AlternationCount визначає, скільки різних стилів чергуються (зазвичай 2 для зебри). AlternationIndex — це attached property, яка містить індекс поточного елемента в циклі чергування (0, 1, 0, 1...). Ви можете використовувати AlternationIndex у тригерах для застосування різних стилів.Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="450" Height="400">
<ListBox Margin="20">
<ListBox.ItemContainerStyle>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0,0,0,8" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="#f3f4f6"
CornerRadius="8"
Padding="15">
<Grid ColumnDefinitions="Auto,*,Auto">
<Border Grid.Column="0"
Width="40" Height="40"
Background="#3b82f6"
CornerRadius="20"
Margin="0,0,12,0">
<TextBlock Text="📧"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="20" />
</Border>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="{Binding}"
FontWeight="Bold"
FontSize="14" />
<TextBlock Text="2 хвилини тому"
Foreground="#6b7280"
FontSize="12"
Margin="0,2,0,0" />
</StackPanel>
<Border Grid.Column="2"
Background="#ef4444"
CornerRadius="10"
Padding="8,4"
VerticalAlignment="Center">
<TextBlock Text="NEW"
Foreground="White"
FontSize="10"
FontWeight="Bold" />
</Border>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBoxItem Content="Нове повідомлення від Олексія" />
<ListBoxItem Content="Запрошення на зустріч" />
<ListBoxItem Content="Оновлення системи" />
</ListBox>
</Window>
ListBox та ListView — два найпопулярніші контроли для відображення списків. Хоча вони дуже схожі, між ними є важливі відмінності.
ListBox — це базовий контрол для відображення списків. Він простий, легкий у налаштуванні та підходить для більшості сценаріїв.
Переваги ListBox:
DataTemplateКоли використовувати:
ListView успадковується від ListBox і додає підтримку різних режимів відображення, зокрема GridView для табличного вигляду.
Переваги ListView:
GridViewКоли використовувати:
📋 ListBox
Використовуйте для:
📊 ListView
Використовуйте для:
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="300">
<Grid Margin="20">
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid ColumnDefinitions="200,150,*" Margin="0,5">
<TextBlock Grid.Column="0" Text="{Binding}" FontWeight="Bold" />
<TextBlock Grid.Column="1" Text="example@email.com" Foreground="#6b7280" />
<TextBlock Grid.Column="2" Text="+380 XX XXX XX XX" Foreground="#6b7280" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBoxItem Content="Іван Петренко" />
<ListBoxItem Content="Марія Коваленко" />
<ListBoxItem Content="Олександр Шевченко" />
<ListBoxItem Content="Анна Мельник" />
</ListBox>
</Grid>
</Window>
ListView з GridView як у WPF. Замість цього використовується DataGrid для табличних даних або ListBox з кастомним DataTemplate для імітації колонок.Для студентів, які тільки опановують ООП, важливо зрозуміти, як принципи об'єктно-орієнтованого програмування застосовуються в архітектурі контролів колекцій:
Успадкування (Inheritance):
Object → DispatcherObject → DependencyObject → Visual → UIElement
→ FrameworkElement → Control → ItemsControl → ListBox
Кожен клас у цій ієрархії додає нову функціональність. ItemsControl додає можливість роботи з колекціями, ListBox додає виділення елементів.
Інкапсуляція (Encapsulation):ItemContainerGenerator — це приклад інкапсуляції. Ви не бачите, як саме створюються контейнери, але можете використовувати результат його роботи.
Поліморфізм (Polymorphism):
Властивість ItemsPanel приймає будь-яку панель, що успадковується від Panel. Це дозволяє підставляти різні реалізації (StackPanel, WrapPanel, UniformGrid) без зміни коду ItemsControl.
Композиція (Composition):ItemsControl складається з багатьох компонентів: ItemsPanel, ItemContainerGenerator, ScrollViewer. Кожен компонент відповідає за свою частину функціональності.
Мета: Навчитися змінювати ItemsPanel для створення адаптивного розташування елементів.
Завдання: Створіть застосунок з горизонтальним списком кольорових карток. Коли вікно звужується, картки повинні автоматично переноситися на новий рядок.
Вимоги:
ListBox з WrapPanel як ItemsPanelScaleTransform)TextBoxПідказка:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
Мета: Опанувати створення складних візуалізацій через DataTemplate та роботу з зображеннями.
Завдання: Створіть галерею зображень у стилі Pinterest з картками різної висоти.
Вимоги:
Photo з властивостями: Title, Author, ImageUrl, LikesObservableCollection<Photo> як джерело данихWrapPanel для адаптивного розташуванняWindow або Popup)Структура класу:
public class Photo
{
public string Title { get; set; }
public string Author { get; set; }
public string ImageUrl { get; set; }
public int Likes { get; set; }
}
Мета: Створити професійний інтерфейс каталогу продуктів з анімаціями та інтерактивністю.
Завдання: Розробіть каталог інтернет-магазину з картками продуктів, фільтрацією та анімаціями.
Вимоги:
Product з властивостями: Name, Price, Category, ImageUrl, Rating, InStockUniformGrid з 3 колонкамиItemContainerStyle з анімаціями:
ComboBox для фільтрації по категоріяхICollectionView для фільтрації без зміни вихідної колекціїДодаткові виклики:
IsNew)Приклад структури:
public class Product : INotifyPropertyChanged
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
public string ImageUrl { get; set; }
public double Rating { get; set; }
public bool InStock { get; set; }
public bool IsNew { get; set; }
}
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<Product> Products { get; set; }
public ICollectionView ProductsView { get; set; }
public string SearchText { get; set; }
public string SelectedCategory { get; set; }
private void ApplyFilters()
{
ProductsView.Filter = item =>
{
var product = item as Product;
// Логіка фільтрації
};
}
}
У цій статті ми детально розібрали архітектуру контролів колекцій у WPF та Avalonia:
Ключові концепції:
ItemsControl — базовий клас для всіх контролів колекцій, який перетворює дані на UI через ланцюжок: ItemsSource → ItemContainerGenerator → ItemsPanel → ItemContainer → DataTemplateItemsPanel дозволяє змінювати розташування елементів (StackPanel, WrapPanel, UniformGrid, Canvas)ItemContainerStyle надає контроль над виглядом контейнерів (hover, selection, alternating colors)ListBox — простий та гнучкий контрол для більшості сценаріївListView — розширений контрол з підтримкою табличного вигляду через GridViewВажливі принципи:
VirtualizingStackPanel для великих списків (тисячі елементів)ItemTemplate для даних, ItemContainerStyle для контейнераAlternationCount та AlternationIndex для зебра-стилюListBox для кастомного дизайну, ListView для табличних данихНаступні кроки:
У наступній статті ми розглянемо DataGrid — найпотужніший контрол для роботи з табличними даними, який надає вбудовану підтримку сортування, фільтрації, редагування та валідації.
Avalonia Themes — Fluent Design та система тематизації
Вбудована система тем Avalonia — Fluent Theme, Simple Theme, ThemeVariant для Dark/Light режимів, runtime switching та порівняння з WPF
DataGrid — колонки та базове відображення
Знайомство з DataGrid — потужним контролом для табличних даних з підтримкою редагування та сортування