TreeView та GridView
TreeView та GridView
Коли дані мають ієрархічну структуру — файлова система, організаційна структура компанії, меню навігації — звичайний список не підходить. Для таких сценаріїв WPF надає TreeView — контрол, що дозволяє відображати деревоподібні структури з можливістю згортання та розгортання вузлів.
У цій статті ми також розглянемо ListView з GridView — легковісну альтернативу DataGrid для табличного відображення даних без редагування, та навчимося реалізовувати Drag and Drop між контролами.
ListBox, DataGrid) та розуміють концепції DataTemplate і HierarchicalDataTemplate. Якщо ви вже працювали з плоскими списками даних, ви готові до вивчення ієрархічних структур.TreeView: дерева даних
TreeView — це контрол для відображення ієрархічних даних, де кожен елемент може мати дочірні елементи, які, у свою чергу, можуть мати свої дочірні елементи, і так далі.
Базова структура TreeView
Найпростіший спосіб створити TreeView — вручну визначити елементи в XAML:
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="400">
<TreeView Margin="20">
<TreeViewItem Header="📁 Документи" IsExpanded="True">
<TreeViewItem Header="📄 Звіт.docx" />
<TreeViewItem Header="📄 Презентація.pptx" />
<TreeViewItem Header="📁 Архів" IsExpanded="True">
<TreeViewItem Header="📄 Старий_звіт.docx" />
<TreeViewItem Header="📄 Backup.zip" />
</TreeViewItem>
</TreeViewItem>
<TreeViewItem Header="📁 Зображення" IsExpanded="True">
<TreeViewItem Header="🖼️ Фото1.jpg" />
<TreeViewItem Header="🖼️ Фото2.png" />
</TreeViewItem>
<TreeViewItem Header="📁 Музика">
<TreeViewItem Header="🎵 Пісня1.mp3" />
<TreeViewItem Header="🎵 Пісня2.mp3" />
</TreeViewItem>
</TreeView>
</Window>
HierarchicalDataTemplate: прив'язка до даних
Для реальних застосунків дані зазвичай приходять з моделі. HierarchicalDataTemplate дозволяє визначити, як відображати кожен рівень ієрархії:
Спочатку створимо модель даних:
public class FileSystemItem : INotifyPropertyChanged
{
private bool _isExpanded;
public string Name { get; set; }
public FileSystemItemType Type { get; set; }
public ObservableCollection<FileSystemItem> Children { get; set; }
public bool IsExpanded
{
get => _isExpanded;
set
{
_isExpanded = value;
OnPropertyChanged();
}
}
public string Icon => Type switch
{
FileSystemItemType.Folder => "📁",
FileSystemItemType.Document => "📄",
FileSystemItemType.Image => "🖼️",
FileSystemItemType.Audio => "🎵",
FileSystemItemType.Video => "🎬",
_ => "📄"
};
public FileSystemItem()
{
Children = new ObservableCollection<FileSystemItem>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public enum FileSystemItemType
{
Folder,
Document,
Image,
Audio,
Video
}
ViewModel:
public class FileExplorerViewModel : INotifyPropertyChanged
{
public ObservableCollection<FileSystemItem> RootItems { get; set; }
public FileExplorerViewModel()
{
RootItems = new ObservableCollection<FileSystemItem>
{
new FileSystemItem
{
Name = "Документи",
Type = FileSystemItemType.Folder,
IsExpanded = true,
Children = new ObservableCollection<FileSystemItem>
{
new FileSystemItem { Name = "Звіт.docx", Type = FileSystemItemType.Document },
new FileSystemItem { Name = "Презентація.pptx", Type = FileSystemItemType.Document },
new FileSystemItem
{
Name = "Архів",
Type = FileSystemItemType.Folder,
Children = new ObservableCollection<FileSystemItem>
{
new FileSystemItem { Name = "Старий_звіт.docx", Type = FileSystemItemType.Document }
}
}
}
},
new FileSystemItem
{
Name = "Зображення",
Type = FileSystemItemType.Folder,
Children = new ObservableCollection<FileSystemItem>
{
new FileSystemItem { Name = "Фото1.jpg", Type = FileSystemItemType.Image },
new FileSystemItem { Name = "Фото2.png", Type = FileSystemItemType.Image }
}
}
};
}
public event PropertyChangedEventHandler PropertyChanged;
}
XAML з HierarchicalDataTemplate:
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="450">
<TreeView Margin="20" ItemsSource="{Binding RootItems}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Icon}"
FontSize="16"
Margin="0,0,5,0" />
<TextBlock Text="{Binding Name}"
VerticalAlignment="Center" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Window>
ItemsSource— прив'язка до колекції дочірніх елементів (наприклад,{Binding Children})- Вміст шаблону — як відображати кожен вузол
ItemTemplate— вкладений шаблон для дочірніх елементів (якщо структура різна на різних рівнях)
IsExpanded: контроль згортання
Властивість IsExpanded контролює, чи розгорнутий вузол:
<TreeView.ItemContainerStyle>
<Style Selector="TreeViewItem">
<!-- TwoWay binding для синхронізації з ViewModel -->
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
Програмне керування:
// Розгорнути всі вузли
public void ExpandAll(FileSystemItem item)
{
item.IsExpanded = true;
foreach (var child in item.Children)
{
ExpandAll(child);
}
}
// Згорнути всі вузли
public void CollapseAll(FileSystemItem item)
{
item.IsExpanded = false;
foreach (var child in item.Children)
{
CollapseAll(child);
}
}
Lazy Loading: завантаження на вимогу
Для великих дерев (наприклад, файлова система з тисячами файлів) завантажуйте дочірні елементи тільки при розгортанні вузла:
public class FileSystemItem : INotifyPropertyChanged
{
private bool _isExpanded;
private bool _childrenLoaded;
public bool IsExpanded
{
get => _isExpanded;
set
{
_isExpanded = value;
OnPropertyChanged();
// Завантажити дочірні елементи при першому розгортанні
if (value && !_childrenLoaded && Type == FileSystemItemType.Folder)
{
LoadChildren();
}
}
}
private void LoadChildren()
{
_childrenLoaded = true;
// Видалити placeholder
Children.Clear();
// Завантажити реальні дочірні елементи
try
{
var directoryInfo = new DirectoryInfo(FullPath);
foreach (var dir in directoryInfo.GetDirectories())
{
Children.Add(new FileSystemItem
{
Name = dir.Name,
FullPath = dir.FullName,
Type = FileSystemItemType.Folder,
Children = new ObservableCollection<FileSystemItem>
{
// Placeholder для показу стрілки розгортання
new FileSystemItem { Name = "Loading..." }
}
});
}
foreach (var file in directoryInfo.GetFiles())
{
Children.Add(new FileSystemItem
{
Name = file.Name,
FullPath = file.FullName,
Type = GetFileType(file.Extension)
});
}
}
catch (UnauthorizedAccessException)
{
// Немає доступу до папки
Children.Add(new FileSystemItem { Name = "Access Denied" });
}
}
private FileSystemItemType GetFileType(string extension)
{
return extension.ToLower() switch
{
".jpg" or ".png" or ".gif" => FileSystemItemType.Image,
".mp3" or ".wav" => FileSystemItemType.Audio,
".mp4" or ".avi" => FileSystemItemType.Video,
_ => FileSystemItemType.Document
};
}
}
Children. При розгортанні замініть його реальними даними.SelectedItem: робота з виділенням
TreeView підтримує виділення елементів:
<TreeView ItemsSource="{Binding RootItems}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<!-- ItemTemplate -->
</TreeView>
ViewModel:
public class FileExplorerViewModel : INotifyPropertyChanged
{
private FileSystemItem _selectedItem;
public FileSystemItem SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
OnPropertyChanged();
// Реакція на зміну виділення
if (value != null)
{
Debug.WriteLine($"Виділено: {value.Name}");
LoadDetails(value);
}
}
}
private void LoadDetails(FileSystemItem item)
{
// Завантажити деталі про файл/папку
// Показати у панелі деталей
}
}
Стилізація TreeView
Ви можете повністю налаштувати вигляд TreeView:
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="450">
<TreeView Margin="20" ItemsSource="{Binding RootItems}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Border Background="Transparent"
Padding="5"
CornerRadius="4">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Icon}"
FontSize="18"
Margin="0,0,8,0" />
<TextBlock Text="{Binding Name}"
VerticalAlignment="Center"
FontSize="14" />
</StackPanel>
</Border>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="Padding" Value="2" />
<Style Selector="^:selected /template/ Border">
<Setter Property="Background" Value="#3b82f6" />
</Style>
<Style Selector="^:selected /template/ ContentPresenter">
<Setter Property="TextElement.Foreground" Value="White" />
</Style>
<Style Selector="^:pointerover /template/ Border">
<Setter Property="Background" Value="#e5e7eb" />
</Style>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Window>
ListView + GridView: табличний вигляд
ListView з GridView — це легковісна альтернатива DataGrid для відображення табличних даних без редагування.
Базовий GridView
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">
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid ColumnDefinitions="200,150,*,100" Margin="0,5">
<TextBlock Grid.Column="0"
Text="{Binding Name}"
FontWeight="Bold" />
<TextBlock Grid.Column="1"
Text="{Binding Email}"
Foreground="#6b7280" />
<TextBlock Grid.Column="2"
Text="{Binding Phone}"
Foreground="#6b7280" />
<TextBlock Grid.Column="3"
Text="{Binding Department}"
Foreground="#3b82f6" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
ListView з GridView як у WPF. Замість цього використовується ListBox з кастомним DataTemplate для імітації колонок, або DataGrid для повноцінних табличних даних.Порівняння: DataGrid vs ListView+GridView
| Функція | DataGrid | ListView + GridView |
|---|---|---|
| Редагування inline | ✅ Вбудоване | ❌ Немає |
| Сортування | ✅ Автоматичне | ⚠️ Ручна реалізація |
| Валідація | ✅ Вбудована | ❌ Немає |
| Продуктивність | ✅ Віртуалізація | ✅ Віртуалізація |
| Складність | ⚠️ Середня | ✅ Проста |
| Кастомізація | ⚠️ Обмежена | ✅ Повна свобода |
| Використання | Редагування даних | Тільки перегляд |
Коли використовувати ListView+GridView:
- Тільки перегляд даних (без редагування)
- Потрібен повний контроль над виглядом
- Простіший код без зайвої функціональності
- Легковісний інтерфейс
Коли використовувати DataGrid:
- Потрібне редагування даних
- Потрібне автоматичне сортування
- Потрібна валідація
- Табличні дані з багатьма колонками
Drag and Drop: перетягування елементів
Drag and Drop дозволяє користувачам перетягувати елементи між контролами або всередині одного контролу.
Базова реалізація Drag and Drop
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.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
BorderBrush="#d1d5db"
BorderThickness="1"
CornerRadius="8"
Padding="10">
<StackPanel>
<TextBlock Text="📋 Доступні завдання"
FontWeight="Bold"
Margin="0,0,0,10" />
<ListBox Height="300">
<ListBoxItem Content="Завдання 1" />
<ListBoxItem Content="Завдання 2" />
<ListBoxItem Content="Завдання 3" />
<ListBoxItem Content="Завдання 4" />
</ListBox>
</StackPanel>
</Border>
<TextBlock Grid.Column="1"
Text="→"
FontSize="24"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="#6b7280" />
<Border Grid.Column="2"
BorderBrush="#d1d5db"
BorderThickness="1"
CornerRadius="8"
Padding="10">
<StackPanel>
<TextBlock Text="✅ Виконані завдання"
FontWeight="Bold"
Margin="0,0,0,10" />
<ListBox Height="300"
Background="#f9fafb">
<!-- Перетягніть завдання сюди -->
</ListBox>
</StackPanel>
</Border>
</Grid>
</Window>
Реалізація у code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var sourceListBox = this.FindControl<ListBox>("SourceListBox");
var targetListBox = this.FindControl<ListBox>("TargetListBox");
// Налаштування джерела (звідки перетягуємо)
sourceListBox.PointerPressed += (s, e) =>
{
if (e.GetCurrentPoint(sourceListBox).Properties.IsLeftButtonPressed)
{
var item = sourceListBox.SelectedItem;
if (item != null)
{
var data = new DataObject();
data.Set("TaskItem", item);
DragDrop.DoDragDrop(e, data, DragDropEffects.Move);
}
}
};
// Налаштування цілі (куди перетягуємо)
targetListBox.AddHandler(DragDrop.DropEvent, (s, e) =>
{
var data = e.Data.Get("TaskItem");
if (data != null)
{
// Видалити з джерела
var sourceItems = (ObservableCollection<string>)sourceListBox.ItemsSource;
sourceItems.Remove(data.ToString());
// Додати до цілі
var targetItems = (ObservableCollection<string>)targetListBox.ItemsSource;
targetItems.Add(data.ToString());
}
});
targetListBox.AddHandler(DragDrop.DragOverEvent, (s, e) =>
{
// Дозволити drop
e.DragEffects = DragDropEffects.Move;
});
}
}
MVVM-friendly Drag and Drop
Для MVVM-підходу використовуйте Attached Behaviors або Commands:
public class DragDropBehavior
{
public static readonly DependencyProperty IsDragSourceProperty =
DependencyProperty.RegisterAttached(
"IsDragSource",
typeof(bool),
typeof(DragDropBehavior),
new PropertyMetadata(false, OnIsDragSourceChanged));
public static bool GetIsDragSource(DependencyObject obj)
{
return (bool)obj.GetValue(IsDragSourceProperty);
}
public static void SetIsDragSource(DependencyObject obj, bool value)
{
obj.SetValue(IsDragSourceProperty, value);
}
private static void OnIsDragSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ListBox listBox && (bool)e.NewValue)
{
listBox.PointerPressed += ListBox_PointerPressed;
}
}
private static void ListBox_PointerPressed(object sender, PointerPressedEventArgs e)
{
var listBox = sender as ListBox;
var item = listBox?.SelectedItem;
if (item != null)
{
var data = new DataObject();
data.Set("Item", item);
DragDrop.DoDragDrop(e, data, DragDropEffects.Move);
}
}
}
Використання в XAML:
<ListBox ItemsSource="{Binding AvailableTasks}"
local:DragDropBehavior.IsDragSource="True" />
Візуальний feedback при Drag
Для кращого UX додайте візуальний feedback:
// Зміна курсору
targetListBox.AddHandler(DragDrop.DragOverEvent, (s, e) =>
{
if (e.Data.Contains("TaskItem"))
{
e.DragEffects = DragDropEffects.Move;
// Курсор автоматично змінюється на "move"
}
else
{
e.DragEffects = DragDropEffects.None;
// Курсор "заборонено"
}
});
// Підсвічування цільового контролу
targetListBox.AddHandler(DragDrop.DragEnterEvent, (s, e) =>
{
targetListBox.Background = new SolidColorBrush(Color.FromRgb(229, 231, 235));
});
targetListBox.AddHandler(DragDrop.DragLeaveEvent, (s, e) =>
{
targetListBox.Background = Brushes.White;
});
🔵 Recap: Рекурсія та дерева даних
Для студентів, які тільки опановують програмування, важливо зрозуміти концепцію рекурсії, яка лежить в основі роботи з деревами:
Рекурсія — це коли функція викликає саму себе. Це ідеальний підхід для роботи з деревоподібними структурами:
// Рекурсивний підрахунок всіх файлів у дереві
public int CountAllFiles(FileSystemItem item)
{
int count = 0;
// Базовий випадок: якщо це файл, повертаємо 1
if (item.Type != FileSystemItemType.Folder)
return 1;
// Рекурсивний випадок: підраховуємо файли у всіх дочірніх елементах
foreach (var child in item.Children)
{
count += CountAllFiles(child); // Виклик самої себе!
}
return count;
}
// Рекурсивний пошук файлу по імені
public FileSystemItem FindFile(FileSystemItem root, string fileName)
{
// Базовий випадок: знайшли файл
if (root.Name == fileName)
return root;
// Рекурсивний випадок: шукаємо у дочірніх елементах
foreach (var child in root.Children)
{
var result = FindFile(child, fileName);
if (result != null)
return result;
}
return null; // Не знайдено
}
Ключові концепції рекурсії:
- Базовий випадок — умова зупинки рекурсії (інакше нескінченний цикл)
- Рекурсивний випадок — виклик функції самої себе з меншою задачею
- Call Stack — кожен виклик зберігається в стеку, тому глибока рекурсія може призвести до
StackOverflowException
Практичні завдання
Рівень 1: Файловий браузер з TreeView
Мета: Навчитися створювати ієрархічні структури даних та відображати їх через TreeView.
Завдання: Створіть простий файловий браузер, що показує структуру папок.
Вимоги:
- Створіть клас
FolderItemз властивостями:Name(string),Path(string),Children(ObservableCollection) - Використайте
HierarchicalDataTemplateдля відображення - Додайте іконки для папок (📁) та файлів (📄)
- Реалізуйте
IsExpandedbinding для збереження стану розгортання - Додайте
SelectedItembinding для показу шляху виділеного елемента - Створіть тестові дані з 3 рівнями вкладеності
Підказка:
var root = new FolderItem
{
Name = "C:\\",
Children = new ObservableCollection<FolderItem>
{
new FolderItem
{
Name = "Program Files",
Children = new ObservableCollection<FolderItem>
{
new FolderItem { Name = "App1" },
new FolderItem { Name = "App2" }
}
}
}
};
Рівень 2: Таблиця контактів через ListView
Мета: Опанувати створення табличного вигляду через ListView з кастомним DataTemplate.
Завдання: Створіть адресну книгу з табличним відображенням контактів.
Вимоги:
- Створіть клас
Contactз властивостями:Name(string)Email(string)Phone(string)Company(string)IsFavorite(bool)
- Використайте
ListBoxзDataTemplateдля імітації колонок - Додайте заголовки колонок (окремий Grid над ListBox)
- Реалізуйте сортування по імені (кнопка в заголовку)
- Додайте фільтр "Тільки обрані" (CheckBox)
- Стилізуйте рядки: обрані контакти мають золоту зірку ⭐
- Додайте hover-ефект для рядків
Додаткові виклики:
- Пошук по імені через TextBox
- Експорт у CSV
- Групування по компанії
Рівень 3: Drag & Drop між TreeView та ListBox
Мета: Реалізувати складну взаємодію з Drag and Drop між різними контролами.
Завдання: Створіть систему управління проєктами з можливістю перетягування завдань.
Вимоги:
- Ліворуч:
TreeViewз проєктами та їх категоріями (ієрархія) - Праворуч:
ListBoxз завданнями поточної категорії - Реалізуйте Drag and Drop:
- Перетягування завдань між категоріями (TreeView → TreeView)
- Перетягування завдань зі списку в категорію (ListBox → TreeView)
- Перетягування завдань для зміни порядку (ListBox → ListBox)
- Додайте візуальний feedback:
- Підсвічування цільової категорії при наведенні
- Зміна курсору (move/copy/none)
- Анімація при drop
- Реалізуйте через MVVM (без code-behind)
- Збережіть структуру при закритті застосунку (JSON)
Структура даних:
public class Project
{
public string Name { get; set; }
public ObservableCollection<Category> Categories { get; set; }
}
public class Category
{
public string Name { get; set; }
public ObservableCollection<Task> Tasks { get; set; }
}
public class Task
{
public string Title { get; set; }
public string Description { get; set; }
public TaskPriority Priority { get; set; }
public bool IsCompleted { get; set; }
}
Додаткові виклики:
- Підтримка Ctrl+Drag для копіювання (замість переміщення)
- Drag and Drop файлів з Explorer у застосунок
- Undo/Redo для операцій перетягування
- Анімація переміщення елемента
Резюме
У цій статті ми детально розібрали роботу з ієрархічними та табличними контролами:
TreeView:
TreeViewItemдля ручного визначення структуриHierarchicalDataTemplateдля прив'язки до данихItemsSourceвказує на колекцію дочірніх елементівIsExpandedдля контролю згортання/розгортання- Lazy Loading для великих дерев (завантаження на вимогу)
SelectedItemдля роботи з виділенням- Стилізація через
ItemContainerStyle
ListView + GridView:
- Легковісна альтернатива
DataGridдля перегляду даних - Повний контроль над виглядом через
DataTemplate - Порівняння з
DataGrid: простіший, але без редагування - Використання для read-only табличних даних
Drag and Drop:
DragDrop.DoDragDrop()для початку перетягуванняDragEnter,DragOver,Dropподії для обробкиDragEffectsдля контролю типу операції (Move, Copy, None)- Візуальний feedback для покращення UX
- MVVM-friendly підхід через Attached Behaviors
Рекурсія:
- Базовий випадок для зупинки
- Рекурсивний випадок для обходу дерева
- Застосування для підрахунку, пошуку, обходу
Наступні кроки: У наступній статті ми розглянемо систему меню, панелей інструментів та контекстних меню для створення повноцінного інтерфейсу застосунку.
Глосарій
- TreeView — контрол для відображення ієрархічних даних у вигляді дерева
- TreeViewItem — елемент дерева (вузол)
- HierarchicalDataTemplate — шаблон для рекурсивного відображення ієрархічних даних
- IsExpanded — властивість, що контролює розгортання вузла
- Lazy Loading — відкладене завантаження даних на вимогу
- ListView — контрол для відображення списків з підтримкою різних режимів
- GridView — режим відображення ListView у вигляді таблиці
- Drag and Drop — перетягування елементів мишею
- DragEffects — тип операції перетягування (Move, Copy, Link, None)
- DataObject — контейнер для даних, що передаються при Drag and Drop
- Рекурсія — виклик функції самої себе для обробки вкладених структур
- Call Stack — стек викликів функцій
Додаткові ресурси
DataGrid — сортування, фільтрація, редагування
Просунуті можливості DataGrid для роботи з великими наборами даних — сортування, фільтрація, групування, inline-редагування та валідація
Меню, Toolbar, ContextMenu, StatusBar
Побудова повної системи меню та панелей інструментів для професійних desktop-застосунків