Уявіть, що у вас є клас Person:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
І ви хочете відобразити його у UI:
<ContentControl Content="{Binding CurrentPerson}"/>
Що ви побачите? Рядок "MyApp.Models.Person" — результат виклику ToString().
Проблема: WPF не знає, як відобразити ваш об'єкт. Він знає тільки, що це об'єкт типу Person, і викликає ToString() за замовчуванням.
Як показати щось красиве — ім'я, вік, email, можливо фото? Як зробити так, щоб кожен Person відображався як картка з аватаром та інформацією?
Рішення: Data Templates — шаблони, що визначають, як WPF має візуалізувати об'єкти певного типу.
Розберемо детально, чому об'єкти відображаються як рядки.
ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
private Person _currentPerson;
public Person CurrentPerson
{
get => _currentPerson;
set
{
_currentPerson = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
CurrentPerson = new Person
{
FirstName = "Іван",
LastName = "Петренко",
Age = 25,
Email = "ivan@example.com"
};
}
// ... INotifyPropertyChanged implementation
}
XAML (без DataTemplate):
<Window x:Class="MyApp.MainWindow"
DataContext="{Binding Source={StaticResource viewModel}}">
<StackPanel Margin="20">
<TextBlock Text="Поточна особа:"/>
<ContentControl Content="{Binding CurrentPerson}"/>
</StackPanel>
</Window>
Результат: ContentControl показує "MyApp.Models.Person" — результат ToString().
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="10">
<TextBlock Text="Поточна особа:"/>
<TextBlock Text="MyApp.Models.Person" Foreground="Gray"/>
<TextBlock Text="(WPF не знає, як відобразити об'єкт, тому викликає ToString())"
FontSize="10"
Foreground="Red"/>
</StackPanel>
Процес:
ContentControl.Content отримує об'єкт PersonDataTemplate для типу PersonDataTemplate не знайдено → викликає Person.ToString()ToString() як текстМожна перевизначити ToString():
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Email { get; set; }
public override string ToString()
{
return $"{FirstName} {LastName}, {Age} років, {Email}";
}
}
Результат: ContentControl показує "Іван Петренко, 25 років, ivan@example.com".
Але це антипатерн:
❌ Обмежене форматування
❌ Один формат
ToString() повертає один рядок. Не можна мати різні представлення для різних контекстів.❌ Змішування логіки
❌ Не масштабується
ToString() стає нечитабельним.DataTemplate — це XAML-шаблон, що визначає, як WPF має відображати об'єкти певного типу.
<DataTemplate DataType="{x:Type local:Person}">
<!-- XAML розмітка для відображення Person -->
<StackPanel>
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding LastName}"/>
<TextBlock Text="{Binding Age}"/>
</StackPanel>
</DataTemplate>
Ключові елементи:
DataTemplate — контейнер для шаблонуDataType="{x:Type local:Person}" — тип об'єкта, для якого цей шаблонDataContext всередині шаблону — це сам об'єкт (Person)Визначення DataTemplate у ресурсах:
<Window.Resources>
<DataTemplate DataType="{x:Type local:Person}">
<Border Background="LightBlue"
BorderBrush="DarkBlue"
BorderThickness="1"
Padding="10"
CornerRadius="5">
<StackPanel>
<TextBlock Text="{Binding FirstName}"
FontWeight="Bold"
FontSize="16"/>
<TextBlock Text="{Binding LastName}"
FontSize="14"/>
<TextBlock Text="{Binding Age, StringFormat='Вік: {0} років'}"
FontSize="12"
Foreground="Gray"/>
<TextBlock Text="{Binding Email}"
FontSize="12"
Foreground="DarkBlue"/>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<StackPanel Margin="20">
<TextBlock Text="Поточна особа:"/>
<ContentControl Content="{Binding CurrentPerson}"/>
</StackPanel>
Результат: Замість "MyApp.Models.Person" ви побачите красиву картку з ім'ям, прізвищем, віком та email.
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="10">
<TextBlock Text="Поточна особа:"/>
<Border Background="LightBlue"
BorderBrush="DarkBlue"
BorderThickness="1"
Padding="10"
CornerRadius="5">
<StackPanel Spacing="5">
<TextBlock Text="Іван" FontWeight="Bold" FontSize="16"/>
<TextBlock Text="Петренко" FontSize="14"/>
<TextBlock Text="Вік: 25 років" FontSize="12" Foreground="Gray"/>
<TextBlock Text="ivan@example.com" FontSize="12" Foreground="DarkBlue"/>
</StackPanel>
</Border>
</StackPanel>
Процес:
ContentControl.Content отримує об'єкт PersonDataTemplate з DataType="{x:Type Person}"DataContext створеного UI = об'єкт PersonPersonDataTemplate може бути неявним (implicit) або явним (explicit).
Визначення: DataTemplate без x:Key — автоматично застосовується до всіх об'єктів цього типу.
<Window.Resources>
<!-- Implicit — без x:Key -->
<DataTemplate DataType="{x:Type local:Person}">
<Border Background="LightBlue" Padding="10">
<TextBlock Text="{Binding FirstName}"/>
</Border>
</DataTemplate>
</Window.Resources>
<!-- Автоматично використовує DataTemplate для Person -->
<ContentControl Content="{Binding CurrentPerson}"/>
<ContentControl Content="{Binding AnotherPerson}"/>
<ListBox ItemsSource="{Binding People}"/> <!-- Кожен Person відображається через шаблон -->
Переваги:
Person виглядають однаковоКоли використовувати:
Визначення: DataTemplate з x:Key — застосовується вручну через ContentTemplate або ItemTemplate.
<Window.Resources>
<!-- Explicit — з x:Key -->
<DataTemplate x:Key="personCardTemplate" DataType="{x:Type local:Person}">
<Border Background="LightGreen" Padding="10">
<StackPanel>
<TextBlock Text="{Binding FirstName}" FontWeight="Bold"/>
<TextBlock Text="{Binding Email}"/>
</StackPanel>
</Border>
</DataTemplate>
<DataTemplate x:Key="personCompactTemplate" DataType="{x:Type local:Person}">
<TextBlock Text="{Binding FirstName}"/>
</DataTemplate>
</Window.Resources>
<!-- Вручну вказуємо шаблон -->
<ContentControl Content="{Binding CurrentPerson}"
ContentTemplate="{StaticResource personCardTemplate}"/>
<ContentControl Content="{Binding AnotherPerson}"
ContentTemplate="{StaticResource personCompactTemplate}"/>
Переваги:
Коли використовувати:
| Аспект | Implicit DataTemplate | Explicit DataTemplate |
|---|---|---|
| x:Key | ❌ Немає | ✅ Є |
| Застосування | Автоматичне для всіх об'єктів типу | Вручну через ContentTemplate/ItemTemplate |
| Область дії | Всі об'єкти типу у scope ресурсів | Тільки де явно вказано |
| Кількість шаблонів | Один на тип | Необмежена кількість |
| Use Case | Стандартне відображення | Спеціальні випадки, варіації |
Різні контроли використовують DataTemplate по-різному.
ContentControl (Button, Label, GroupBox) відображає один об'єкт через ContentTemplate.
Синтаксис:
<ContentControl Content="{Binding CurrentPerson}"
ContentTemplate="{StaticResource personTemplate}"/>
Приклад: Картка профілю
<Window.Resources>
<DataTemplate x:Key="profileCardTemplate">
<Border Background="#F5F5F5"
BorderBrush="#CCCCCC"
BorderThickness="1"
Padding="20"
CornerRadius="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Аватар (placeholder) -->
<Ellipse Width="80"
Height="80"
Fill="#2196F3"
Grid.Column="0"
Margin="0,0,20,0"/>
<!-- Інформація -->
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock FontSize="20" FontWeight="Bold">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Text="{Binding Age, StringFormat='Вік: {0} років'}"
FontSize="14"
Foreground="Gray"
Margin="0,5,0,0"/>
<TextBlock Text="{Binding Email}"
FontSize="14"
Foreground="#2196F3"
Margin="0,5,0,0"/>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentPerson}"
ContentTemplate="{StaticResource profileCardTemplate}"/>
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Border Background="#F5F5F5"
BorderBrush="#CCCCCC"
BorderThickness="1"
Padding="20"
CornerRadius="10"
Margin="20">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Width="80" Height="80" Fill="#2196F3" Grid.Column="0" Margin="0,0,20,0"/>
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="5">
<TextBlock Text="Іван Петренко" FontSize="20" FontWeight="Bold"/>
<TextBlock Text="Вік: 25 років" FontSize="14" Foreground="Gray"/>
<TextBlock Text="ivan@example.com" FontSize="14" Foreground="#2196F3"/>
</StackPanel>
</Grid>
</Border>
ItemsControl (ListBox, ComboBox, ListView) відображає колекцію об'єктів через ItemTemplate.
Синтаксис:
<ListBox ItemsSource="{Binding People}"
ItemTemplate="{StaticResource personTemplate}"/>
Приклад: Список контактів
<Window.Resources>
<DataTemplate x:Key="contactItemTemplate">
<Border Background="White"
BorderBrush="#E0E0E0"
BorderThickness="0,0,0,1"
Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Аватар -->
<Ellipse Width="40"
Height="40"
Fill="#4CAF50"
Grid.Column="0"
Margin="0,0,10,0"/>
<!-- Інформація -->
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock FontWeight="Bold" FontSize="14">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Text="{Binding Email}"
FontSize="12"
Foreground="Gray"/>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{Binding People}"
ItemTemplate="{StaticResource contactItemTemplate}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<Person> People { get; set; }
public MainViewModel()
{
People = new ObservableCollection<Person>
{
new Person { FirstName = "Іван", LastName = "Петренко", Email = "ivan@example.com" },
new Person { FirstName = "Марія", LastName = "Коваленко", Email = "maria@example.com" },
new Person { FirstName = "Олександр", LastName = "Шевченко", Email = "alex@example.com" }
};
}
// ... INotifyPropertyChanged implementation
}
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="0">
<Border Background="White" BorderBrush="#E0E0E0" BorderThickness="0,0,0,1" Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Width="40" Height="40" Fill="#4CAF50" Grid.Column="0" Margin="0,0,10,0"/>
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="3">
<TextBlock Text="Іван Петренко" FontWeight="Bold" FontSize="14"/>
<TextBlock Text="ivan@example.com" FontSize="12" Foreground="Gray"/>
</StackPanel>
</Grid>
</Border>
<Border Background="White" BorderBrush="#E0E0E0" BorderThickness="0,0,0,1" Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Width="40" Height="40" Fill="#4CAF50" Grid.Column="0" Margin="0,0,10,0"/>
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="3">
<TextBlock Text="Марія Коваленко" FontWeight="Bold" FontSize="14"/>
<TextBlock Text="maria@example.com" FontSize="12" Foreground="Gray"/>
</StackPanel>
</Grid>
</Border>
<Border Background="White" BorderBrush="#E0E0E0" BorderThickness="0,0,0,1" Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Width="40" Height="40" Fill="#4CAF50" Grid.Column="0" Margin="0,0,10,0"/>
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="3">
<TextBlock Text="Олександр Шевченко" FontWeight="Bold" FontSize="14"/>
<TextBlock Text="alex@example.com" FontSize="12" Foreground="Gray"/>
</StackPanel>
</Grid>
</Border>
</StackPanel>
| Аспект | ContentTemplate | ItemTemplate |
|---|---|---|
| Контроли | ContentControl, Button, Label | ListBox, ComboBox, ListView |
| Кількість | Один об'єкт | Колекція об'єктів |
| Властивість | ContentTemplate | ItemTemplate |
| DataContext | Об'єкт з Content | Кожен елемент колекції |
| Use Case | Картка профілю, деталі об'єкта | Списки, меню, випадаючі списки |
DataTemplateSelector дозволяє вибирати шаблон програмно залежно від даних.
👥 Різні типи об'єктів
⭐ Різні категорії
📊 Умовне форматування
🔀 Складна логіка
Крок 1: Створити клас-селектор
using System.Windows;
using System.Windows.Controls;
namespace MyApp.Selectors
{
public class MessageTemplateSelector : DataTemplateSelector
{
// Шаблони (встановлюються з XAML)
public DataTemplate TextMessageTemplate { get; set; }
public DataTemplate ImageMessageTemplate { get; set; }
public DataTemplate LinkMessageTemplate { get; set; }
// Метод вибору шаблону
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
// Перевірка типу об'єкта
return item switch
{
TextMessage => TextMessageTemplate,
ImageMessage => ImageMessageTemplate,
LinkMessage => LinkMessageTemplate,
_ => base.SelectTemplate(item, container)
};
}
}
}
Крок 2: Визначити моделі
public abstract class Message
{
public string Sender { get; set; }
public DateTime Timestamp { get; set; }
}
public class TextMessage : Message
{
public string Text { get; set; }
}
public class ImageMessage : Message
{
public string ImageUrl { get; set; }
public string Caption { get; set; }
}
public class LinkMessage : Message
{
public string Url { get; set; }
public string Title { get; set; }
}
Крок 3: Визначити шаблони та селектор у XAML
<Window.Resources>
<!-- Шаблон для текстових повідомлень -->
<DataTemplate x:Key="textMessageTemplate">
<Border Background="#E3F2FD"
Padding="10"
Margin="5"
CornerRadius="5">
<StackPanel>
<TextBlock Text="{Binding Sender}"
FontWeight="Bold"
FontSize="12"/>
<TextBlock Text="{Binding Text}"
FontSize="14"
TextWrapping="Wrap"
Margin="0,5,0,0"/>
<TextBlock Text="{Binding Timestamp, StringFormat='HH:mm'}"
FontSize="10"
Foreground="Gray"
HorizontalAlignment="Right"/>
</StackPanel>
</Border>
</DataTemplate>
<!-- Шаблон для зображень -->
<DataTemplate x:Key="imageMessageTemplate">
<Border Background="#F3E5F5"
Padding="10"
Margin="5"
CornerRadius="5">
<StackPanel>
<TextBlock Text="{Binding Sender}"
FontWeight="Bold"
FontSize="12"/>
<Rectangle Width="200"
Height="150"
Fill="LightGray"
Margin="0,5,0,0"/>
<TextBlock Text="{Binding Caption}"
FontSize="12"
TextWrapping="Wrap"
Margin="0,5,0,0"/>
</StackPanel>
</Border>
</DataTemplate>
<!-- Шаблон для посилань -->
<DataTemplate x:Key="linkMessageTemplate">
<Border Background="#FFF3E0"
Padding="10"
Margin="5"
CornerRadius="5">
<StackPanel>
<TextBlock Text="{Binding Sender}"
FontWeight="Bold"
FontSize="12"/>
<TextBlock Text="{Binding Title}"
FontSize="14"
Foreground="#2196F3"
TextDecorations="Underline"
Margin="0,5,0,0"/>
<TextBlock Text="{Binding Url}"
FontSize="10"
Foreground="Gray"
Margin="0,5,0,0"/>
</StackPanel>
</Border>
</DataTemplate>
<!-- Селектор -->
<local:MessageTemplateSelector x:Key="messageTemplateSelector"
TextMessageTemplate="{StaticResource textMessageTemplate}"
ImageMessageTemplate="{StaticResource imageMessageTemplate}"
LinkMessageTemplate="{StaticResource linkMessageTemplate}"/>
</Window.Resources>
<ListBox ItemsSource="{Binding Messages}"
ItemTemplateSelector="{StaticResource messageTemplateSelector}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
ViewModel:
public class ChatViewModel : INotifyPropertyChanged
{
public ObservableCollection<Message> Messages { get; set; }
public ChatViewModel()
{
Messages = new ObservableCollection<Message>
{
new TextMessage
{
Sender = "Іван",
Text = "Привіт! Як справи?",
Timestamp = DateTime.Now
},
new ImageMessage
{
Sender = "Марія",
ImageUrl = "/images/photo.jpg",
Caption = "Подивись на це фото!",
Timestamp = DateTime.Now
},
new LinkMessage
{
Sender = "Олександр",
Url = "https://example.com",
Title = "Цікава стаття",
Timestamp = DateTime.Now
}
};
}
// ... INotifyPropertyChanged implementation
}
Результат: Кожен тип повідомлення відображається з власним шаблоном — текст на синьому фоні, зображення на фіолетовому, посилання на помаранчевому.
HierarchicalDataTemplate — спеціальний тип DataTemplate для ієрархічних даних (дерева, вкладені структури).
| Аспект | DataTemplate | HierarchicalDataTemplate |
|---|---|---|
| Використання | Плоскі списки (ListBox) | Ієрархічні дані (TreeView) |
| ItemsSource | ❌ Немає | ✅ Є (для дочірніх елементів) |
| Рекурсія | ❌ Не підтримує | ✅ Автоматична рекурсія |
| Use Case | Списки контактів, повідомлень | Файлова система, організаційна структура |
Модель:
public class FileSystemItem : INotifyPropertyChanged
{
private string _name;
private bool _isExpanded;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public bool IsFolder { get; set; }
public bool IsExpanded
{
get => _isExpanded;
set
{
_isExpanded = value;
OnPropertyChanged();
}
}
// Дочірні елементи (для папок)
public ObservableCollection<FileSystemItem> Children { get; set; }
public FileSystemItem()
{
Children = new ObservableCollection<FileSystemItem>();
}
// ... INotifyPropertyChanged implementation
}
ViewModel:
public class FileExplorerViewModel : INotifyPropertyChanged
{
public ObservableCollection<FileSystemItem> RootItems { get; set; }
public FileExplorerViewModel()
{
RootItems = new ObservableCollection<FileSystemItem>
{
new FileSystemItem
{
Name = "Documents",
IsFolder = true,
IsExpanded = true,
Children = new ObservableCollection<FileSystemItem>
{
new FileSystemItem
{
Name = "Work",
IsFolder = true,
Children = new ObservableCollection<FileSystemItem>
{
new FileSystemItem { Name = "Report.docx", IsFolder = false },
new FileSystemItem { Name = "Presentation.pptx", IsFolder = false }
}
},
new FileSystemItem { Name = "Resume.pdf", IsFolder = false }
}
},
new FileSystemItem
{
Name = "Pictures",
IsFolder = true,
Children = new ObservableCollection<FileSystemItem>
{
new FileSystemItem { Name = "Vacation.jpg", IsFolder = false },
new FileSystemItem { Name = "Family.png", IsFolder = false }
}
}
};
}
// ... INotifyPropertyChanged implementation
}
XAML з HierarchicalDataTemplate:
<Window.Resources>
<!-- Шаблон для файлів та папок -->
<HierarchicalDataTemplate DataType="{x:Type local:FileSystemItem}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<!-- Іконка (папка або файл) -->
<TextBlock Text="{Binding IsFolder, Converter={local:BoolToIconConverter}}"
FontFamily="Segoe MDL2 Assets"
FontSize="16"
Margin="0,0,5,0"/>
<!-- Назва -->
<TextBlock Text="{Binding Name}"
FontSize="14"/>
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView ItemsSource="{Binding RootItems}"/>
Конвертер для іконок:
public class BoolToIconConverter : BaseConverter<BoolToIconConverter>
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool isFolder && isFolder)
return "\uE8B7"; // Іконка папки (Segoe MDL2 Assets)
return "\uE8A5"; // Іконка файлу
}
}
Результат: TreeView з ієрархічною структурою — папки можна розгортати/згортати, файли відображаються як листя дерева.
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="5">
<TextBlock Text="📁 Documents" FontSize="14"/>
<StackPanel Margin="20,0,0,0" Spacing="5">
<TextBlock Text="📁 Work" FontSize="14"/>
<StackPanel Margin="20,0,0,0" Spacing="5">
<TextBlock Text="📄 Report.docx" FontSize="14"/>
<TextBlock Text="📄 Presentation.pptx" FontSize="14"/>
</StackPanel>
<TextBlock Text="📄 Resume.pdf" FontSize="14"/>
</StackPanel>
<TextBlock Text="📁 Pictures" FontSize="14"/>
<StackPanel Margin="20,0,0,0" Spacing="5">
<TextBlock Text="📄 Vacation.jpg" FontSize="14"/>
<TextBlock Text="📄 Family.png" FontSize="14"/>
</StackPanel>
<TextBlock Text="(У реальному WPF TreeView з можливістю розгортання/згортання)"
FontSize="10"
Foreground="Gray"
Margin="0,10,0,0"/>
</StackPanel>
1. ItemsSource — прив'язка до дочірніх елементів
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<!-- Шаблон для батьківського елемента -->
</HierarchicalDataTemplate>
2. ItemTemplate — шаблон для дочірніх елементів (якщо відрізняється)
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<!-- Шаблон для батьківського елемента -->
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
<!-- Шаблон для дочірніх елементів -->
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontSize="12"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
3. Рекурсія — автоматична для однотипних структур
Якщо дочірні елементи того самого типу, що й батьківські, HierarchicalDataTemplate автоматично застосовується рекурсивно.
Модель:
public class Employee
{
public string Name { get; set; }
public string Position { get; set; }
public ObservableCollection<Employee> Subordinates { get; set; }
public Employee()
{
Subordinates = new ObservableCollection<Employee>();
}
}
ViewModel:
public class OrganizationViewModel
{
public ObservableCollection<Employee> TopManagement { get; set; }
public OrganizationViewModel()
{
TopManagement = new ObservableCollection<Employee>
{
new Employee
{
Name = "Іван Петренко",
Position = "CEO",
Subordinates = new ObservableCollection<Employee>
{
new Employee
{
Name = "Марія Коваленко",
Position = "CTO",
Subordinates = new ObservableCollection<Employee>
{
new Employee { Name = "Олександр Шевченко", Position = "Senior Developer" },
new Employee { Name = "Анна Мельник", Position = "Junior Developer" }
}
},
new Employee
{
Name = "Петро Бондаренко",
Position = "CFO"
}
}
}
};
}
}
XAML:
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Employee}"
ItemsSource="{Binding Subordinates}">
<Border Background="LightBlue"
Padding="5"
Margin="2"
CornerRadius="3">
<StackPanel>
<TextBlock Text="{Binding Name}"
FontWeight="Bold"
FontSize="14"/>
<TextBlock Text="{Binding Position}"
FontSize="12"
Foreground="Gray"/>
</StackPanel>
</Border>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView ItemsSource="{Binding TopManagement}"/>
Результат: Організаційна структура компанії у вигляді дерева — CEO → CTO → розробники.
Мета: Навчитися створювати базовий DataTemplate для відображення об'єкта.
Завдання:
Створіть клас Person з властивостями:
FirstName (string)LastName (string)Age (int)Email (string)Створіть DataTemplate, що відображає Person як картку з:
Критерії успіху:
Підказка:
<Window.Resources>
<DataTemplate DataType="{x:Type local:Person}">
<Border Background="LightGray" Padding="10" BorderBrush="Gray" BorderThickness="1">
<StackPanel>
<TextBlock FontSize="16" FontWeight="Bold">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!-- Додайте інші поля -->
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
Мета: Створити красивий список контактів з аватарами та інформацією.
Завдання:
Створіть додаток "Контакти" з:
Модель Contact:
public class Contact : INotifyPropertyChanged
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string AvatarColor { get; set; } // Hex колір для аватара
}
Вимоги до UI:
AvatarColorКритерії успіху:
Підказка для ініціалів:
public string Initials => $"{FirstName?[0]}{LastName?[0]}";
Мета: Створити чат з різними типами повідомлень через DataTemplateSelector.
Завдання:
Створіть додаток "Чат" з підтримкою трьох типів повідомлень:
Базовий клас:
public abstract class Message
{
public string Sender { get; set; }
public DateTime Timestamp { get; set; }
public bool IsOwnMessage { get; set; } // Своє чи чуже повідомлення
}
Типи повідомлень:
public class TextMessage : Message
{
public string Text { get; set; }
}
public class ImageMessage : Message
{
public string ImagePath { get; set; }
public string Caption { get; set; }
}
public class FileMessage : Message
{
public string FileName { get; set; }
public long FileSize { get; set; }
}
Вимоги:
MessageTemplateSelector з трьома шаблонамиКритерії успіху:
Додатково (складно):
Data Templates — це потужний механізм для автоматичної візуалізації C#-об'єктів у WPF. Вони дозволяють повністю розділити дані та їх представлення.
Ключові висновки:
📄 DataTemplate
ToString() — красивий UI.🔄 Implicit vs Explicit
📦 ContentTemplate vs ItemTemplate
🎯 DataTemplateSelector
🌳 HierarchicalDataTemplate
🎨 Розділення відповідальності
Коли використовувати DataTemplate:
Коли використовувати DataTemplateSelector:
Коли використовувати HierarchicalDataTemplate:
Що далі?
ObservableCollectionICollectionViewx:Key, що автоматично застосовується до всіх об'єктів вказаного типу у scope ресурсів.Explicit DataTemplate — DataTemplate з x:Key, що застосовується вручну через ContentTemplate або ItemTemplate.ContentTemplate — властивість ContentControl для встановлення DataTemplate для одного об'єкта.ItemTemplate — властивість ItemsControl для встановлення DataTemplate для кожного елемента колекції.DataTemplateSelector — клас, що програмно вибирає DataTemplate залежно від даних через метод SelectTemplate().HierarchicalDataTemplate — спеціальний тип DataTemplate для ієрархічних даних (TreeView). Підтримує ItemsSource для дочірніх елементів.ItemsSource — властивість HierarchicalDataTemplate, що вказує на колекцію дочірніх елементів для рекурсивного відображення.DataContext — об'єкт даних, до якого прив'язуються Binding-и всередині DataTemplate. Для ItemTemplate — це кожен елемент колекції.ResourceDictionary — колекція ресурсів (стилі, шаблони, конвертери), що можуть бути перевикористані у різних частинах додатку.Value Converters — Перетворення типів даних у Data Binding
IValueConverter для перетворення типів між Source та Target — від Boolean до Visibility, від Enum до Color, від DateTime до String
Collections Binding Part 1 — ObservableCollection та ItemsControl
Прив'язка колекцій C#-об'єктів до UI-списків з автоматичним оновленням через ObservableCollection та INotifyCollectionChanged