У попередніх статтях ми вивчили базовий Data Binding — прив'язку до DataContext:
<TextBlock Text="{Binding FirstName}"/>
Але що, якщо потрібно:
TextBlock до значення Slider?Window і прочитати його властивість?FirstName та LastName в одне поле FullName?Для цих сценаріїв потрібні розширені можливості Binding: ElementName, RelativeSource, MultiBinding, StringFormat, FallbackValue.
ElementName дозволяє прив'язати властивість одного елемента до властивості іншого елемента.
<TextBlock Text="{Binding ElementName=targetElement, Path=PropertyName}"/>
Параметри:
ElementName — назва елемента (встановлена через x:Name)Path — властивість цього елементаКласичний приклад — синхронізація Slider та TextBlock:
<StackPanel Margin="20">
<TextBlock Text="Гучність:"/>
<Slider x:Name="volumeSlider"
Minimum="0"
Maximum="100"
Value="50"/>
<TextBlock Text="{Binding ElementName=volumeSlider, Path=Value}"
FontSize="24"
FontWeight="Bold"/>
</StackPanel>
Що відбувається:
Slider має x:Name="volumeSlider"TextBlock.Text прив'язаний до volumeSlider.ValueSlider → TextBlock автоматично оновлюєтьсяLoading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="10">
<TextBlock Text="Гучність:"/>
<Slider Value="50" Minimum="0" Maximum="100"/>
<TextBlock Text="50" FontSize="24" FontWeight="Bold"/>
<TextBlock Text="(У реальному WPF число оновлюється при русі слайдера)"
FontSize="10"
Foreground="Gray"/>
</StackPanel>
Два слайдери, що синхронізуються між собою:
<StackPanel Margin="20">
<TextBlock Text="Слайдер 1:"/>
<Slider x:Name="slider1"
Minimum="0"
Maximum="100"
Value="{Binding ElementName=slider2, Path=Value, Mode=TwoWay}"/>
<TextBlock Text="Слайдер 2:" Margin="0,20,0,0"/>
<Slider x:Name="slider2"
Minimum="0"
Maximum="100"
Value="50"/>
<TextBlock Text="{Binding ElementName=slider1, Path=Value}"
FontSize="16"
Margin="0,10,0,0"/>
</StackPanel>
Ключовий момент: Mode=TwoWay — зміна slider1 оновлює slider2, і навпаки.
Динамічна зміна розміру шрифту через Slider:
<StackPanel Margin="20">
<TextBlock Text="Розмір шрифту:"/>
<Slider x:Name="fontSizeSlider"
Minimum="10"
Maximum="72"
Value="16"/>
<TextBlock Text="{Binding ElementName=fontSizeSlider, Path=Value, StringFormat='Розмір: {0:F0} pt'}"
Margin="0,10,0,0"/>
<TextBlock Text="Це текст з динамічним розміром шрифту"
FontSize="{Binding ElementName=fontSizeSlider, Path=Value}"
Margin="0,20,0,0"
TextWrapping="Wrap"/>
</StackPanel>
Що нового:
StringFormat='Розмір: {0:F0} pt' — форматування значення (детальніше у розділі StringFormat)FontSize="{Binding ElementName=...}" — прив'язка до властивості FontSizeLoading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="10">
<TextBlock Text="Розмір шрифту:"/>
<Slider Value="16" Minimum="10" Maximum="72"/>
<TextBlock Text="Розмір: 16 pt"/>
<TextBlock Text="Це текст з динамічним розміром шрифту"
FontSize="16"
TextWrapping="Wrap"/>
<TextBlock Text="(У реальному WPF розмір змінюється при русі слайдера)"
FontSize="10"
Foreground="Gray"/>
</StackPanel>
🎚️ Контроли налаштувань
🔗 Синхронізація контролів
👁️ Live preview
✅ Умовна видимість
Visibility Binding).RelativeSource дозволяє знайти елемент відносно поточного елемента у Visual/Logical Tree.
| Режим | Опис | Use Case |
|---|---|---|
Self | Прив'язка до себе | Tooltip з власною властивістю |
FindAncestor | Пошук батька по типу | Доступ до Window з дочірнього елемента |
TemplatedParent | Батьківський елемент у ControlTemplate | Кастомні контроли |
PreviousData | Попередній елемент у колекції | Порівняння з попереднім значенням |
Прив'язка властивості елемента до іншої його властивості.
Синтаксис:
<TextBlock Text="{Binding RelativeSource={RelativeSource Self}, Path=PropertyName}"/>
Приклад: Tooltip з шириною елемента
<Border Width="200"
Height="100"
Background="LightBlue"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=ActualWidth, StringFormat='Ширина: {0:F0} px'}"/>
Що відбувається:
RelativeSource Self — посилання на сам BorderPath=ActualWidth — читає властивість ActualWidth цього BorderПриклад: Кнопка з текстом про свій стан
<Button Content="Натисни мене"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=IsEnabled, StringFormat='Активна: {0}'}"/>
Пошук батьківського елемента певного типу у Visual Tree.
Синтаксис:
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Title}"/>
Параметри:
AncestorType — тип батька (Window, Grid, StackPanel, UserControl)AncestorLevel — рівень (1 = перший батько, 2 = дідусь, тощо)Приклад: Доступ до Window.Title з дочірнього елемента
<Window x:Class="MyApp.MainWindow"
Title="Моє вікно"
Width="400" Height="300">
<StackPanel Margin="20">
<TextBlock Text="Заголовок вікна:"/>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Title}"
FontWeight="Bold"
FontSize="16"/>
</StackPanel>
</Window>
Що відбувається:
TextBlock шукає батька типу WindowTitleПриклад: Доступ до DataContext батьківського Grid
<Grid x:Name="parentGrid" DataContext="{Binding SomeViewModel}">
<StackPanel>
<Border>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=DataContext.PropertyName}"/>
</Border>
</StackPanel>
</Grid>
Use Case: Коли дочірній елемент має свій DataContext, але потрібен доступ до батьківського.
Приклад: AncestorLevel — пошук дідуся
<Grid x:Name="grandparent" Background="LightGray">
<Grid x:Name="parent" Background="LightBlue">
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Grid, AncestorLevel=2}, Path=Name}"
Margin="20"/>
</Grid>
</Grid>
Результат: TextBlock показує "grandparent" (другий батько типу Grid).
Використовується у кастомних контролах для доступу до властивостей батьківського контролу.
Приклад: Кастомна кнопка
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
Що відбувається:
TemplatedParent — посилання на Button, для якого застосовується цей templatePath=Content — читає властивість Content цієї кнопкиTemplateBinding — це скорочений синтаксис для RelativeSource TemplatedParent. Працює тільки у ControlTemplate. TemplateBinding Background = Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background.🪟 Доступ до Window
Window.Title, Window.Width, або викликає команди Window.📦 Доступ до батьківського контейнера
Grid.ActualWidth для адаптивного layout.🎨 Кастомні контроли
TemplatedParent.🔗 Обхід DataContext
MultiBinding дозволяє об'єднати кілька властивостей в одне значення через IMultiValueConverter.
Як відобразити FullName, якщо у моделі є тільки FirstName та LastName?
Неправильний підхід:
// ❌ Додавати обчислювану властивість у ViewModel для кожної комбінації
public string FullName => $"{FirstName} {LastName}";
public string FullNameWithTitle => $"{Title} {FirstName} {LastName}";
public string ShortName => $"{FirstName[0]}. {LastName}";
// ... десятки варіантів
Правильний підхід: MultiBinding + IMultiValueConverter — логіка об'єднання у конвертері, а не у ViewModel.
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource fullNameConverter}">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Інтерфейс для перетворення масиву значень в одне значення:
public interface IMultiValueConverter
{
object Convert(object[] values, Type targetType, object parameter, CultureInfo culture);
object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture);
}
Параметри:
values — масив значень з усіх Binding-івtargetType — тип Target властивості (наприклад, string)parameter — додатковий параметр (через ConverterParameter)culture — культура для локалізаціїConverter:
using System;
using System.Globalization;
using System.Windows.Data;
public class FullNameConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Перевірка: чи всі значення — рядки
if (values.Length < 2 || values[0] is not string firstName || values[1] is not string lastName)
return string.Empty;
// Об'єднання
return $"{firstName} {lastName}";
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
// ConvertBack для MultiBinding рідко використовується
throw new NotImplementedException();
}
}
Реєстрація у ресурсах:
<Window.Resources>
<local:FullNameConverter x:Key="fullNameConverter"/>
</Window.Resources>
Використання:
<StackPanel Margin="20">
<TextBlock Text="Ім'я:"/>
<TextBox Text="{Binding FirstName}"/>
<TextBlock Text="Прізвище:"/>
<TextBox Text="{Binding LastName}"/>
<TextBlock Text="Повне ім'я:" FontWeight="Bold" Margin="0,10,0,0"/>
<TextBlock FontSize="16">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource fullNameConverter}">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
Результат: При зміні FirstName або LastName → FullName автоматично оновлюється.
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="10">
<TextBlock Text="Ім'я:"/>
<TextBox Text="Іван"/>
<TextBlock Text="Прізвище:"/>
<TextBox Text="Петренко"/>
<TextBlock Text="Повне ім'я:" FontWeight="Bold"/>
<TextBlock Text="Іван Петренко" FontSize="16"/>
<TextBlock Text="(У реальному WPF оновлюється при зміні будь-якого поля)"
FontSize="10"
Foreground="Gray"/>
</StackPanel>
Converter для суми:
public class SumConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double sum = 0;
foreach (var value in values)
{
if (value is string str && double.TryParse(str, out double number))
sum += number;
else if (value is double d)
sum += d;
}
return sum;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<Window.Resources>
<local:SumConverter x:Key="sumConverter"/>
</Window.Resources>
<StackPanel Margin="20">
<TextBlock Text="Число 1:"/>
<TextBox x:Name="txt1" Text="10"/>
<TextBlock Text="Число 2:"/>
<TextBox x:Name="txt2" Text="20"/>
<TextBlock Text="Число 3:"/>
<TextBox x:Name="txt3" Text="30"/>
<TextBlock Text="Сума:" FontWeight="Bold" Margin="0,10,0,0"/>
<TextBlock FontSize="20">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource sumConverter}">
<Binding ElementName="txt1" Path="Text"/>
<Binding ElementName="txt2" Path="Text"/>
<Binding ElementName="txt3" Path="Text"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
Результат: Сума трьох TextBox-ів оновлюється миттєво при зміні будь-якого з них.
Converter для AND логіки:
public class BooleanAndConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Всі значення мають бути true
foreach (var value in values)
{
if (value is not bool b || !b)
return false;
}
return true;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<StackPanel Margin="20">
<CheckBox x:Name="chk1" Content="Умова 1"/>
<CheckBox x:Name="chk2" Content="Умова 2"/>
<CheckBox x:Name="chk3" Content="Умова 3"/>
<Button Content="Доступна тільки якщо всі CheckBox-и вибрані" Margin="0,20,0,0">
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource booleanAndConverter}">
<Binding ElementName="chk1" Path="IsChecked"/>
<Binding ElementName="chk2" Path="IsChecked"/>
<Binding ElementName="chk3" Path="IsChecked"/>
</MultiBinding>
</Button.IsEnabled>
</Button>
</StackPanel>
Результат: Кнопка активна тільки коли всі три CheckBox-и вибрані.
StringFormat дозволяє форматувати значення без створення конвертера.
<TextBlock Text="{Binding Price, StringFormat='{}{0:C2}'}"/>
Важливо: {} на початку — escape-послідовність, щоб XAML parser не сприймав {0} як Markup Extension.
| Формат | Опис | Приклад (1234.56) |
|---|---|---|
C2 | Currency (2 знаки) | $1,234.56 |
N0 | Number без дробової | 1,235 |
N2 | Number з 2 знаками | 1,234.56 |
P0 | Percent без дробової | 123,456% |
P2 | Percent з 2 знаками | 123,456.00% |
F2 | Fixed-point 2 знаки | 1234.56 |
Приклад:
<StackPanel Margin="20">
<TextBlock Text="{Binding Price, StringFormat='Ціна: {0:C2}'}"/>
<TextBlock Text="{Binding Discount, StringFormat='Знижка: {0:P0}'}"/>
<TextBlock Text="{Binding Quantity, StringFormat='Кількість: {0:N0} шт.'}"/>
</StackPanel>
Результат:
| Формат | Опис | Приклад |
|---|---|---|
d | Short date | 10.04.2026 |
D | Long date | 10 квітня 2026 р. |
t | Short time | 14:30 |
T | Long time | 14:30:45 |
g | Short date + time | 10.04.2026 14:30 |
G | Long date + time | 10.04.2026 14:30:45 |
Приклад:
<StackPanel Margin="20">
<TextBlock Text="{Binding CreatedDate, StringFormat='Створено: {0:d}'}"/>
<TextBlock Text="{Binding LastModified, StringFormat='Змінено: {0:G}'}"/>
<TextBlock Text="{Binding Time, StringFormat='Час: {0:HH:mm:ss}'}"/>
</StackPanel>
<!-- Телефон -->
<TextBlock Text="{Binding Phone, StringFormat='Тел: +38 ({0:000}) 000-00-00}'}"/>
<!-- Дата народження -->
<TextBlock Text="{Binding BirthDate, StringFormat='Народився: {0:dd MMMM yyyy} року'}"/>
<!-- Розмір файлу -->
<TextBlock Text="{Binding FileSize, StringFormat='{}{0:N0} байт'}"/>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1} ({2} років)">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
<Binding Path="Age"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Результат: "Іван Петренко (25 років)"
IValueConverter.Ці властивості визначають, що показувати при помилці Binding або null значенні.
Показується, коли Binding не може знайти властивість або виникла помилка.
<TextBlock Text="{Binding NonExistentProperty, FallbackValue='Дані недоступні'}"/>
Use Cases:
🔌 Завантаження даних
❌ Помилка Binding
🖼️ Placeholder зображення
Приклад:
<StackPanel Margin="20">
<TextBlock Text="{Binding UserName, FallbackValue='Гість'}"/>
<Image Source="{Binding AvatarUrl, FallbackValue='/Images/default-avatar.png'}"
Width="100"
Height="100"/>
</StackPanel>
Показується, коли Source властивість має значення null.
<TextBlock Text="{Binding MiddleName, TargetNullValue='(немає по батькові)'}"/>
Різниця між FallbackValue та TargetNullValue:
| Ситуація | FallbackValue | TargetNullValue |
|---|---|---|
| Властивість не існує | ✅ Показується | ❌ Не показується |
Властивість = null | ❌ Не показується | ✅ Показується |
| Помилка конвертера | ✅ Показується | ❌ Не показується |
| Binding до неправильного типу | ✅ Показується | ❌ Не показується |
Приклад:
<StackPanel Margin="20">
<!-- Якщо Email = null → показати "(не вказано)" -->
<TextBlock Text="{Binding Email, TargetNullValue='(не вказано)'}"/>
<!-- Якщо Phone = null → показати "N/A", якщо помилка → "Помилка" -->
<TextBlock Text="{Binding Phone, TargetNullValue='N/A', FallbackValue='Помилка'}"/>
</StackPanel>
Binding помилки у WPF — мовчазні. Як їх знайти?
При помилці Binding WPF пише у Output Window:
System.Windows.Data Error: 40 : BindingExpression path error: 'Nane' property not found on 'object' ''PersonViewModel' (HashCode=12345)'. BindingExpression:Path=Nane; DataItem='PersonViewModel' (HashCode=12345); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
Як читати:
Error: 40 — код помилки (40 = property not found)'Nane' property not found — властивість не знайденаDataItem='PersonViewModel' — тип DataContexttarget element is 'TextBlock' — елемент з помилкоюДля детальної діагностики увімкніть трасування:
<TextBlock Text="{Binding Name}"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
diag:PresentationTraceSources.TraceLevel="High"/>
Рівні трасування:
None — без трасування (за замовчуванням)Low — тільки помилкиMedium — помилки + попередженняHigh — вся інформація (кожен крок Binding)Output при TraceLevel=High:
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=Name; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 20 : BindingExpression cannot retrieve value due to missing information. BindingExpression:Path=Name; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 21 : BindingExpression cannot retrieve value from null data item. BindingExpression:Path=Name; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
TraceLevel=High генерує величезну кількість логів. Використовуйте тільки для діагностики конкретного Binding, а не для всього проєкту.❌ Property not found
❌ DataContext is null
❌ Wrong type
❌ Converter error
Convert() конвертера.Мета: Навчитися використовувати ElementName Binding для синхронізації контролів.
Завдання:
Створіть форму налаштувань з трьома параметрами:
StringFormat='{}{0:F0}%')Критерії успіху:
Підказка:
<Slider x:Name="volumeSlider" Minimum="0" Maximum="100" Value="50"/>
<TextBlock Text="{Binding ElementName=volumeSlider, Path=Value, StringFormat='Гучність: {0:F0}'}"/>
Мета: Створити калькулятор, що об'єднує кілька TextBox через MultiBinding.
Завдання:
Створіть калькулятор з чотирма операціями:
Converter:
public class CalculatorConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 3) return 0;
if (!double.TryParse(values[0]?.ToString(), out double num1)) return 0;
if (!double.TryParse(values[1]?.ToString(), out double num2)) return 0;
string operation = values[2]?.ToString();
return operation switch
{
"+" => num1 + num2,
"-" => num1 - num2,
"*" => num1 * num2,
"/" => num2 != 0 ? num1 / num2 : 0,
_ => 0
};
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
UI:
MultiBindingКритерії успіху:
Мета: Створити складну форму з MultiBinding для умовної активації кнопки.
Завдання:
Створіть форму реєстрації з такими полями:
ViewModel:
public class RegistrationViewModel : INotifyPropertyChanged
{
private string _userName;
private string _email;
private string _password;
private string _confirmPassword;
private bool _agreeToTerms;
// Властивості з INPC...
}
Вимоги до валідації:
UserName — не порожній, мінімум 3 символиEmail — містить "@"Password — мінімум 6 символівConfirmPassword — співпадає з PasswordAgreeToTerms — CheckBox вибранийConverter для валідації:
public class RegistrationValidationConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 5) return false;
string userName = values[0]?.ToString();
string email = values[1]?.ToString();
string password = values[2]?.ToString();
string confirmPassword = values[3]?.ToString();
bool agreeToTerms = values[4] is bool b && b;
return !string.IsNullOrWhiteSpace(userName) && userName.Length >= 3 &&
!string.IsNullOrWhiteSpace(email) && email.Contains("@") &&
!string.IsNullOrWhiteSpace(password) && password.Length >= 6 &&
password == confirmPassword &&
agreeToTerms;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
UI:
MultiBinding на IsEnabled)Критерії успіху:
UpdateSourceTrigger=PropertyChanged для миттєвої валідаціїДодатково (складно):
FallbackValue для відображення помилокВи опанували розширені можливості Data Binding, що дозволяють вирішувати складні сценарії без code-behind.
Ключові висновки:
🔗 ElementName Binding
🔍 RelativeSource Binding
Self, FindAncestor, TemplatedParent. Доступ до батьківських властивостей.🔀 MultiBinding
IMultiValueConverter. Калькулятори, валідація, складні умови.📝 StringFormat
🛡️ FallbackValue
🐛 Debugging
PresentationTraceSources.TraceLevel=High для діагностики помилок.Що далі?
IValueConverter та створення бібліотеки конвертерівElementName та RelativeSource для UI-логіки (синхронізація контролів). Використовуйте DataContext Binding для бізнес-логіки (дані з ViewModel).x:Name.RelativeSource Binding — пошук елемента відносно поточного елемента у Visual/Logical Tree. Режими: Self, FindAncestor, TemplatedParent, PreviousData.MultiBinding — об'єднання кількох Binding-ів в одне значення через IMultiValueConverter.IMultiValueConverter — інтерфейс для перетворення масиву значень (object[]) в одне значення. Використовується у MultiBinding.StringFormat — властивість Binding для форматування значення без створення конвертера. Підтримує стандартні формати .NET.FallbackValue — значення, що показується при помилці Binding (властивість не знайдена, помилка конвертера).TargetNullValue — значення, що показується коли Source властивість має значення null.PresentationTraceSources.TraceLevel — рівень трасування для діагностики Binding помилок. Значення: None, Low, Medium, High.Compiled Bindings в Avalonia — Безпека на етапі компіляції
Ключова перевага Avalonia над WPF — compile-time перевірка Data Binding через Compiled Bindings замість Reflection
Value Converters — Перетворення типів даних у Data Binding
IValueConverter для перетворення типів між Source та Target — від Boolean до Visibility, від Enum до Color, від DateTime до String