Уявіть, що вам треба описати кімнату. Можна зробити це по-різному.
Перший спосіб — процедурний: "Зайдіть у кімнату. Підійдіть до лівої стіни. Поставте стіл. Поверніться і поставте стілець. На стіл покладіть лампу." Це — послідовність дій.
Другий спосіб — декларативний: "Кімната: стіл зі стільцем і лампою зліва." Ви описуєте що повинно бути, а не як це зробити. Результат той самий, але запис набагато лаконічніший і читабельніший.
XAML — це декларативний спосіб опису інтерфейсу. Замість того, щоб писати десятки рядків C#-коду для створення кнопок, текстових полів і панелей — ви описуєте готовий результат у вигляді розмітки. Але є ключова деталь, яку потрібно зрозуміти з самого початку.
"Red" → Brushes.Red). Attached Property — властивість, визначена в одному класі, але застосована до об'єкта іншого класу (наприклад, Grid.Row на Button).Найпоширеніша помилка при першому знайомстві з XAML — думати, що це "щось схоже на HTML". Це не так, і ця помилка спричиняє непорозуміння.
HTML — це мова розмітки документів. Вона описує сторінку: заголовки, параграфи, посилання, таблиці. Браузер читає HTML і малює сторінку.
XAML — це серіалізований C#-об'єктний граф. Кожен тег XAML — це виклик конструктора. Кожен атрибут — це присвоєння властивості. Ніякої "магії" браузера. Ніякого прощення помилок.
Подивіться на цю паралель:
<Button Content="OK" Width="100" IsDefault="True"/>
Це буквально те саме, що:
var button = new Button();
button.Content = "OK";
button.Width = 100;
button.IsDefault = true;
Ніякої різниці в результаті. XAML — це просто інший, коротший синтаксис для створення тих самих C#-об'єктів. Якщо ви завжди пам'ятатимете цей факт — XAML ніколи не буде для вас магічною чорною скринькою.
XAML побудований на основі XML. А XML — на відміну від HTML — є суворою мовою. Браузер HTML "вибачає" незакриті теги, відсутні лапки та інші помилки. Парсер XML — ні. Одна помилка — і XAML взагалі не завантажиться.
Вам потрібно знати ці правила напам'ять.
XML є case-sensitive. <Button> і <button> — це різні теги. Content і content — різні атрибути.
<!-- ✅ Правильно -->
<Button Content="OK"/>
<!-- ❌ Помилка: 'button' не є відомим тегом -->
<button Content="OK"/>
<!-- ❌ Помилка: 'content' не є властивістю Button -->
<Button content="OK"/>
У WPF всі класи та властивості починаються з великої літери (PascalCase) — Button, StackPanel, HorizontalAlignment. Дотримуйтесь цього при написанні XAML.
У HTML можна написати <br> без закриваючого тегу. В XML — ні. Кожен відкритий тег повинен мати закриваючий.
<!-- ✅ Самозакривний тег (якщо немає вмісту) -->
<Button Content="OK"/>
<!-- ✅ Явний закриваючий тег (якщо є вміст) -->
<StackPanel>
<Button Content="OK"/>
</StackPanel>
<!-- ❌ Помилка: тег не закритий -->
<Button Content="OK">
В XML кожне значення атрибута повинно бути в лапках — одинарних або подвійних. В HTML деякі значення можна писати без лапок.
<!-- ✅ Правильно -->
<Button Width="100" Content="OK"/>
<!-- ❌ Помилка: значення без лапок -->
<Button Width=100 Content=OK/>
У XAML прийнято використовувати подвійні лапки. Одинарні — лише коли значення містить подвійні лапки всередині (що трапляється рідко).
Теги не можуть перекриватися. Якщо тег <A> відкрито всередині <B>, він повинен бути закритий до закриття <B>.
<!-- ✅ Правильна вкладеність -->
<StackPanel>
<TextBlock>
<Bold>Жирний текст</Bold>
</TextBlock>
</StackPanel>
<!-- ❌ Помилка: перекриття тегів -->
<StackPanel>
<TextBlock>
<Bold>Жирний текст
</TextBlock>
</Bold>
</StackPanel>
| Ситуація | HTML (браузер) | XAML (WPF) |
|---|---|---|
| Незакритий тег | ✅ Виправляє сам | ❌ XmlException |
| Значення без лапок | ✅ Приймає | ❌ XmlException |
| Неправильний регістр тегу | ✅ Ігнорує | ❌ XamlParseException |
| Невідомий атрибут | ✅ Ігнорує | ❌ XamlParseException |
| Перекриті теги | ✅ Виправляє | ❌ XmlException |
Це означає: коли у вашому XAML є синтаксична помилка — застосунок або не скомпілюється, або впаде при запуску з XamlParseException. Ніякого "а ну й ладно, спробуємо відобразити як є".
В XAML існують два синтаксично еквівалентних способи задати значення властивості. Перший — короткий і зручний там, де значення є простим рядком чи числом. Другий — розгорнутий, необхідний тоді, коли значення є складним об'єктом. Розуміння різниці між ними — одна з ключових навичок роботи з XAML.
Коли значення властивості можна виразити одним рядком — використовується Attribute Syntax. Значення передається як атрибут XML прямо у відкриваючому тезі:
<Button Content="Натисни мене"
Width="150"
FontSize="16"
IsEnabled="True"/>
Це зручно і читабельно. Більшість властивостей у XAML задається саме так: текст, числа, булеві значення, перерахування (HorizontalAlignment="Center").
Але що, якщо значення властивості — це не рядок, а складний об'єкт? Наприклад, фон кнопки має бути не однорідним кольором, а градієнтом. Як записати об'єкт LinearGradientBrush як значення атрибуту? Ніяк — рядок цього не зможе передати.
Тут на допомогу приходить Property Element Syntax. Замість атрибута властивість записується як вкладений тег у форматі ТипОб'єкта.НазваВластивості:
<Button Width="150">
<Button.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="#3b82f6" Offset="0"/>
<GradientStop Color="#8b5cf6" Offset="1"/>
</LinearGradientBrush>
</Button.Background>
<Button.Content>
Натисни мене
</Button.Content>
</Button>
Те саме в C# виглядало б так:
var button = new Button();
button.Width = 150;
var brush = new LinearGradientBrush();
brush.StartPoint = new Point(0, 0);
brush.EndPoint = new Point(1, 0);
brush.GradientStops.Add(new GradientStop(Color.FromArgb(255, 59, 130, 246), 0));
brush.GradientStops.Add(new GradientStop(Color.FromArgb(255, 139, 92, 246), 1));
button.Background = brush;
button.Content = "Натисни мене";
Обидві форми дають однаковий результат. Property Element Syntax — це спосіб "помістити повноцінний об'єкт у властивість" через вкладений тег.
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="16" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Width="200" HorizontalAlignment="Center">
<Button.Background>
<LinearGradientBrush>
<GradientStop Color="#3b82f6" Offset="0"/>
<GradientStop Color="#8b5cf6" Offset="1"/>
</LinearGradientBrush>
</Button.Background>
<Button.Foreground>
<SolidColorBrush Color="White"/>
</Button.Foreground>
<Button.Content>
Градієнтна кнопка
</Button.Content>
</Button>
<Button Content="Звичайна кнопка" Width="200" HorizontalAlignment="Center"/>
</StackPanel>
Обидві форми можна змішувати в одному тезі. Загальне правило просте:
"#3b82f6").<!-- Змішаний підхід — цілком нормально -->
<Button Width="200"
FontSize="14"
Foreground="White">
<Button.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="#3b82f6" Offset="0"/>
<GradientStop Color="#8b5cf6" Offset="1"/>
</LinearGradientBrush>
</Button.Background>
Натисни мене
</Button>
Погляньте на цей рядок XAML:
<Button>Натисни мене</Button>
Тут є щось нелогічне. Button має властивість Content, але ми ніде не написали Content=. Просто помістили текст між тегами. І воно працює. Чому?
[ContentProperty]У WPF є атрибут [ContentProperty], яким клас може позначити одну зі своїх властивостей як властивість за замовчуванням для вмісту між тегами. Все що знаходиться між відкриваючим і закриваючим тегом — автоматично призначається саме цій властивості.
Для класу ContentControl (від якого успадковує Button) це виглядає так у вихідному коді WPF:
[ContentProperty(Name = "Content")]
public class ContentControl : Control
{
public object Content { get; set; }
// ...
}
Тому <Button>Натисни мене</Button> і <Button Content="Натисни мене"/> — абсолютно еквівалентні записи. XAML-парсер бачить текст між тегами і автоматично надсилає його у властивість Content.
Різні класи мають різні Content Property — залежно від своєї ролі в ієрархії:
| Клас | Content Property | Що може містити |
|---|---|---|
ContentControl (Button, Window...) | Content | Один довільний об'єкт |
Panel (StackPanel, Grid...) | Children | Колекція UIElement |
ItemsControl (ListBox, ComboBox...) | Items | Колекція об'єктів |
TextBlock | Inlines | Колекція Inline-елементів |
Border | Child | Один UIElement |
Це пояснює, чому StackPanel приймає кілька дочірніх елементів між тегами — його Content Property це Children, колекція:
<!-- Всі ці елементи потрапляють у StackPanel.Children -->
<StackPanel>
<TextBlock Text="Перший рядок"/>
<TextBlock Text="Другий рядок"/>
<Button Content="Кнопка"/>
</StackPanel>
А Button — приймає лише один елемент між тегами, бо Content — не колекція, а object. Але цим одним елементом може бути ціла панель зі складним вмістом:
<!-- Кнопка з іконкою і текстом всередині -->
<Button>
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="💾" FontSize="16"/>
<TextBlock Text="Зберегти"/>
</StackPanel>
</Button>
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="12" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="💾" FontSize="16"/>
<TextBlock Text="Зберегти документ"/>
</StackPanel>
</Button>
<Button HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="🗑️" FontSize="16"/>
<TextBlock Text="Видалити"/>
</StackPanel>
</Button>
<Button HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="✏️" FontSize="16"/>
<TextBlock Text="Редагувати"/>
</StackPanel>
</Button>
</StackPanel>
ContentControl свідомо спроектований для довільного вмісту, а не лише для тексту.Поверніться на секунду до цього рядка XAML:
<Button Background="Red" Width="150" Margin="10,20,10,20"/>
Зверніть: атрибут Background отримує значення "Red" — рядок. Але в C# властивість Background очікує об'єкт типу Brush. Між рядком "Red" і об'єктом Brushes.Red — прірва типів. Як WPF її перетинає?
Через механізм Type Converters.
TypeConverter — це клас, що вміє перетворювати значення одного типу в інший. В контексті XAML: з рядка — у потрібний C#-тип. WPF включає десятки вже готових Type Converters для всіх стандартних типів.
Коли XAML-парсер зустрічає рядкове значення атрибута — він запитує: "Який тип очікує ця властивість? Чи є для нього TypeConverter?" Якщо є — TypeConverter викликається і перетворює рядок у потрібний об'єкт.
Ось які перетворення відбуваються непомітно для вас під час парсингу XAML:
| XAML рядок | Очікуваний тип | Результат TypeConverter |
|---|---|---|
"Red" | Brush | Brushes.Red (тобто new SolidColorBrush(Colors.Red)) |
"#3b82f6" | Brush | new SolidColorBrush(Color.FromRgb(59, 130, 246)) |
"150" | double | 150.0 |
"True" | bool | true |
"10,20,10,20" | Thickness | new Thickness(10, 20, 10, 20) |
"10,20" | Thickness | new Thickness(10, 20, 10, 20) (h,v → all four) |
"10" | Thickness | new Thickness(10) (uniform) |
"Bold" | FontWeight | FontWeights.Bold |
"Center" | HorizontalAlignment | HorizontalAlignment.Center |
"0,0 1,0" | Point[] | масив Point для GradientBrush |
"Star" | GridLength | new GridLength(1, GridUnitType.Star) |
"Auto" | GridLength | GridLength.Auto |
Особливо цікаво поведінка Thickness. Одне число "10" — відступ з усіх боків. Два числа "10,20" — horizontal (left+right) та vertical (top+bottom). Чотири числа "10,20,30,40" — left, top, right, bottom явно. Все це робить один TypeConverter.
Пошук TypeConverter відбувається через атрибут [TypeConverter] на типі або через атрибут [TypeConverter] на властивості. Наприклад, клас Thickness у вихідному коді WPF позначений:
[TypeConverter(typeof(ThicknessConverter))]
public struct Thickness
{
public double Left;
public double Top;
public double Right;
public double Bottom;
// ...
}
Коли XAML-парсер зустрічає Margin="10,20" і бачить, що Margin має тип Thickness — він знаходить ThicknessConverter через цей атрибут і викликає ThicknessConverter.ConvertFrom("10,20").
"Red" і "#FF0000" і "#f00" і "Crimson" — всі чотири коректні значення для Background. Все це обробляє BrushConverter, знаючи про різні формати запису кольорів. Вам як розробнику не треба думати про це — просто пишіть зручний рядок.Пам'ятаєте, як у WPF з Grid виставити кнопці рядок та стовпець?
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="1" Grid.Column="0" Content="Submit"/>
</Grid>
Тут є дещо дивне: Grid.Row і Grid.Column — це атрибути на елементі Button. Але клас Button нічого не знає про Grid! У нього немає властивості Row і немає властивості Column. Як це взагалі компілюється?
Це — Attached Properties (прикріплені властивості). Один з найелегантніших механізмів WPF.
Attached Property — це властивість, яка визначена в одному класі (наприклад, Grid), але може бути встановлена на об'єктах будь-якого іншого класу (наприклад, Button, TextBox, StackPanel).
Синтаксично у XAML: КласВласника.НазваВластивості="значення":
<Button Grid.Row="1" <!-- Grid визначив Row, Button отримав значення -->
Grid.Column="0" <!-- Grid визначив Column, Button отримав значення -->
DockPanel.Dock="Top" <!-- DockPanel визначив Dock, Button отримав значення -->
Canvas.Left="100" <!-- Canvas визначив Left, Button отримав значення -->
Panel.ZIndex="5"/> <!-- Panel визначив ZIndex, Button отримав значення -->
Це не конфлікт і не помилка. Кожна з цих властивостей зберігається у самому Button-об'єкті (в словнику його DependencyObject), але читає їх батьківський елемент під час Layout:
Grid розташовує дочірні елементи — він читає Grid.Row та Grid.Column з кожного дочірньогоDockPanel розташовує дочірні — він читає DockPanel.Dock з нихCanvas розташовує — він читає Canvas.Left, Canvas.TopАльтернативний підхід — дати кожному елементу (Button, TextBox, тощо) властивості Row, Column, Dock, Left, Top, ZIndex... Але це 1) неможливо заздалегідь передбачити всі варіанти, 2) забруднює API кожного елемента властивостями, які йому не належать.
Attached Properties вирішують задачу елегантно: панель визначає потрібні їй властивості у собі, а дочірні елементи їх лише "несуть" — не знаючи і не турбуючись про значення та сенс.
Ось найчастіше вживані:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Ім'я:" Grid.Row="0" Grid.Column="0"/>
<TextBox Grid.Row="0" Grid.Column="1"/>
<!-- RowSpan: елемент займає 2 рядки -->
<Button Content="OK" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"/>
</Grid>
<DockPanel>
<!-- Кожен елемент вказує до якої сторони "прикріпитись" -->
<Menu DockPanel.Dock="Top" />
<StatusBar DockPanel.Dock="Bottom" />
<TreeView DockPanel.Dock="Left" Width="200"/>
<TextBox /> <!-- LastChildFill: займає решту простору -->
</DockPanel>
<Canvas>
<!-- Абсолютне позиціонування -->
<Button Canvas.Left="50" Canvas.Top="30" Content="A"/>
<Button Canvas.Left="200" Canvas.Top="100" Content="B"/>
</Canvas>
<!-- ZIndex: порядок перекриття елементів -->
<Grid>
<Rectangle Fill="Blue" Panel.ZIndex="0"/>
<Rectangle Fill="Red" Margin="20" Panel.ZIndex="1"/>
<!-- Red перекриває Blue, бо ZIndex вищий -->
</Grid>
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Ім'я:" VerticalAlignment="Center" Margin="0,4"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="Іванко" Margin="8,4"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Email:" VerticalAlignment="Center" Margin="0,4"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="ivan@example.com" Margin="8,4"/>
<Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
Content="Зберегти" Margin="0,12,0,0"/>
</Grid>
DependencyProperty.RegisterAttached(), як батьківський елемент їх читає через GetValue(), і як створити власну Attached Property — буде детально розібрано в статті 15. Attached Properties.У XAML є два способи дати елементу ім'я для доступу з code-behind:
<TextBlock x:Name="myLabel" Text="Привіт"/>
<TextBlock Name="myLabel2" Text="Також привіт"/>
Обидва дають доступ до елемента з C#: myLabel.Text і myLabel2.Text. Виглядають схоже. Але між ними є принципова різниця.
NameName — це звичайна CLR-властивість типу string, визначена у класі FrameworkElement:
public class FrameworkElement : UIElement
{
public string Name { get; set; }
}
Коли ви пишете Name="myLabel" у XAML — XAML-парсер робить присвоєння element.Name = "myLabel". Після цього ви можете знайти елемент через FindName("myLabel"). Власне ім'я — як ще одна властивість об'єкта.
Але є обмеження: Name існує тільки у FrameworkElement і його нащадків. Якщо ви намагаєтеся дати ім'я об'єкту, що не є FrameworkElement (наприклад, GradientStop, Style, RowDefinition) — Name там просто немає.
x:Namex:Name — це XAML-директива, що належить самому XAML-парсеру. Вона не є властивістю C#-класу. Вона говорить парсеру: "Згенеруй поле з цим ім'ям у *.g.cs файлі і зв'яжи його з цим об'єктом".
<!-- Дає ім'я GradientStop — неможливо через Name -->
<LinearGradientBrush>
<GradientStop x:Name="firstStop" Color="Blue" Offset="0"/>
</LinearGradientBrush>
Для всіх FrameworkElement-нащадків x:Name і Name — функціонально рівнозначні. На рівні XAML-парсера x:Name фактично синхронізується з Name для них.
x:NameПрактичне правило — завжди використовуйте x:Name. Причини:
FrameworkElementx:Name у правильних контекстах<!-- ✅ Рекомендовано -->
<TextBlock x:Name="statusLabel" Text="Готово"/>
<Button x:Name="submitButton" Content="Надіслати"/>
<Grid x:Name="mainGrid">
<Grid.RowDefinitions>
<RowDefinition x:Name="headerRow" Height="Auto"/>
</Grid.RowDefinitions>
</Grid>
<!-- ⚠️ Працює, але не рекомендується -->
<TextBlock Name="statusLabel" Text="Готово"/>
Ось зведена таблиця всіх XAML-конструкцій та їхніх C#-еквівалентів. Вона стане вашим орієнтиром кожного разу, коли XAML здаватиметься магічним.
| XAML | C#-еквівалент | Механізм |
|---|---|---|
<Button/> | new Button() | Виклик конструктора |
<Button Content="OK"/> | btn.Content = "OK" | Attribute Syntax |
<Button Width="150"/> | btn.Width = 150.0 | TypeConverter: string → double |
<Button Background="Red"/> | btn.Background = Brushes.Red | TypeConverter: string → Brush |
<Button Margin="10,20"/> | btn.Margin = new Thickness(10, 20, 10, 20) | TypeConverter: string → Thickness |
<Button IsEnabled="True"/> | btn.IsEnabled = true | TypeConverter: string → bool |
<Button HorizontalAlignment="Center"/> | btn.HorizontalAlignment = HorizontalAlignment.Center | TypeConverter: string → Enum |
<Button>Text</Button> | btn.Content = "Text" | Content Property |
<Button><Image.../></Button> | btn.Content = new Image(...) | Content Property (складний об'єкт) |
<Button.Background><LinearGradientBrush/></Button.Background> | btn.Background = new LinearGradientBrush(...) | Property Element Syntax |
<StackPanel><Button/><TextBox/></StackPanel> | panel.Children.Add(btn); panel.Children.Add(tb) | Content Property (колекція) |
<Button x:Name="myBtn"/> | internal Button myBtn; (у *.g.cs) | XAML directive → поле класу |
<Button Grid.Row="1"/> | Grid.SetRow(btn, 1) | Attached Property |
<Button Click="Btn_Click"/> | btn.Click += Btn_Click | Event subscription |
<Button Command="{Binding SaveCmd}"/> | btn.SetBinding(Button.CommandProperty, "SaveCmd") | Markup Extension (Binding) |
Кілька рядків у цій таблиці використовують {Binding} — це Markup Extension, окрема тема, яку повністю розберемо у статті 08. Data Binding. Поки просто зафіксуйте: фігурні дужки у значенні атрибута {...} — це Markup Extension, не звичайний рядок.
XAML — це C#
new Type(). Кожен атрибут = присвоєння властивості. XAML — це не HTML, а серіалізований об'єктний граф. Будь-яке XAML можна написати на чистому C# і навпаки.XML — суворий
XamlParseException замість прощення помилок як у браузері HTML.Дві форми запису
Content Property
Button — Content, StackPanel — Children. Завдяки цьому кнопки можуть містити довільний XAML-вміст.Type Converters
"Red" → Brush, "10,20" → Thickness, "Bold" → FontWeight. Це прозоро і автоматично.x:Name для code-behind
x:Name (не Name) для елементів, до яких треба звертатися з C#. Це XAML-директива, яка генерує поле у *.g.cs.Завдання: Перекладіть кожен XAML-фрагмент у C#-код та навпаки. Виконайте письмово або у коментарях.
XAML → C# (перекладіть на C#):
<StackPanel Background="#f0f4ff" Margin="20">
<TextBlock Text="Заголовок" FontSize="24" FontWeight="Bold" Foreground="#1e3a8a"/>
<TextBox x:Name="inputField" Width="300" Padding="8,4"/>
<Button Content="Відправити" Width="120" HorizontalAlignment="Left"/>
</StackPanel>
C# → XAML (перекладіть на XAML):
var border = new Border();
border.CornerRadius = new CornerRadius(8);
border.Padding = new Thickness(16);
border.Background = Brushes.LightYellow;
var label = new TextBlock();
label.Text = "Підказка:";
label.FontWeight = FontWeights.Bold;
border.Child = label;
Перевірка: C#-код компілюється і дає клас Border з TextBlock. XAML-код відображає StackPanel з трьома елементами.
Завдання: Дослідіть Content Property для різних контролів.
ButtonCheckBoxGroupBoxScrollViewerTabItemExpanderToolTipПідказка: Відкрийте у Visual Studio будь-який клас контролу через F12. Подивіться на атрибут [ContentProperty] або спробуйте написати XAML і подивіться, чи з'являється помилка. Документація MSDN також допоможе.
Перевірка: Список з 7 відповідей та хоча б 1 клас без Content Property.
Завдання: Реалізуйте наступний інтерфейс двома способами і порівняйте.
Інтерфейс: Форма реєстрації з:
TextBlock "Реєстрація" (заголовок, FontSize=22, FontWeight=Bold)Label + TextBox для поля "Ім'я"Label + TextBox для поля "Email"Label + PasswordBox для поля "Пароль"CheckBox з текстом "Я погоджуюся з умовами"Button "Зареєструватися" (повна ширина)Спосіб A — лише XAML: Реалізуйте весь інтерфейс у MainWindow.xaml. У MainWindow.xaml.cs тільки InitializeComponent().
Спосіб B — лише C#: Реалізуйте весь інтерфейс виключно в MainWindow.xaml.cs, без жодного елемента у XAML (залиште тільки <Grid x:Name="root"/> у XAML). Всі елементи через new Button(), .Children.Add() тощо.
Що порівняти після виконання:
FontSize заголовка)?Напишіть висновок у 3-5 реченнях.
Перший Avalonia-проєкт: WPF для всіх платформ
Створюємо перший Avalonia-застосунок і порівнюємо його з WPF. Розбираємо структуру файлів, App.axaml, Program.cs, DevTools та міжплатформний запуск.
Fluent UI у WPF — сучасний дизайн Windows 11
Інтеграція Fluent Design System у WPF додатки: PresentationFramework.Fluent, стилізація контролів, кастомізація тем, створення власних стилів з BasedOn