Desktop UI

Fluent UI у WPF — сучасний дизайн Windows 11

Інтеграція Fluent Design System у WPF додатки: PresentationFramework.Fluent, стилізація контролів, кастомізація тем, створення власних стилів з BasedOn

Fluent UI у WPF — сучасний дизайн Windows 11

Коли ви створюєте новий WPF додаток, за замовчуванням він виглядає як додаток з Windows Vista — сірі кнопки, плоскі контроли, застарілий дизайн. Це працює, але виглядає архаїчно порівняно з сучасними додатками Windows 11. Користувачі звикли до Fluent Design System — сучасної дизайн-мови Microsoft з rounded corners, subtle shadows, smooth animations та акриловими ефектами. На щастя, Microsoft надає офіційну бібліотеку PresentationFramework.Fluent, яка дозволяє легко інтегрувати Fluent UI у ваші WPF додатки.

У цій статті ми детально розглянемо, як увімкнути Fluent UI у WPF додатку, як працює система тем, як кастомізувати кольори та стилі, як створювати власні стилі з наслідуванням від Fluent базових стилів через BasedOn, та як правильно організувати ресурси для підтримки світлої та темної теми. Ми також обговоримо обмеження Fluent UI у WPF порівняно з WinUI 3, та як максимально наблизити вигляд WPF додатку до нативних Windows 11 додатків.

Fluent UI — це не просто косметичні зміни. Це цілісна дизайн-система, яка впливає на user experience: правильні відступи, консистентна типографіка, зрозумілі візуальні стани (hover, pressed, disabled), accessibility-friendly контрасти. Інтеграція Fluent UI робить ваш додаток сучасним, професійним та приємним у використанні.

Словник термінів
  • Fluent Design System — дизайн-мова Microsoft для Windows 10/11
  • PresentationFramework.Fluent — офіційна бібліотека Fluent UI для WPF
  • Theme — тема оформлення (Light, Dark, High Contrast)
  • BasedOn — механізм наслідування стилів у WPF
  • ResourceDictionary — словник ресурсів для стилів, шаблонів, кольорів
  • Accent Color — акцентний колір теми (зазвичай синій)
  • Rounded Corners — заокруглені кути, характерна риса Fluent Design
  • Acrylic — напівпрозорий ефект з розмиттям фону

Що таке Fluent Design System?

Перш ніж інтегрувати Fluent UI, важливо зрозуміти, що це таке та чому це важливо.

Історія дизайну Windows

Windows XP (2001-2006): Luna theme — яскраві кольори, 3D ефекти, glossy кнопки. Революційно для свого часу, але швидко застарів.

Windows Vista/7 (2006-2012): Aero theme — прозорість, glass effects, subtle animations. WPF створювався саме для цієї ери.

Windows 8 (2012-2015): Metro/Modern UI — плоский дизайн, яскраві кольори, typography-focused. Радикальна зміна, яка викликала суперечки.

Windows 10 (2015-2021): Fluent Design System v1 — еволюція Metro з додаванням depth (тіні, layers), motion (animations), material (acrylic transparency).

Windows 11 (2021-present): Fluent Design System v2 — rounded corners, soft colors, centered taskbar, consistency across всіх додатків.

Принципи Fluent Design

Fluent Design базується на п'яти принципах (5 L's):

1. Light — використання світла для створення depth та focus. Subtle shadows, highlights на інтерактивних елементах.

2. Depth — створення ієрархії через layering. Елементи на різних "шарах" з різними elevation levels.

3. Motion — smooth, purposeful animations. Не просто "красиво", а для покращення UX (показати зв'язок між елементами, direction of navigation).

4. Material — використання "матеріалів" як acrylic (напівпрозорість з blur), mica (subtle texture). Створює відчуття фізичності у digital UI.

5. Scale — адаптивність до різних розмірів екрану, input methods (mouse, touch, pen), accessibility needs.

Fluent UI у WPF vs WinUI 3

WinUI 3 — це нова UI платформа Microsoft, створена з нуля для Fluent Design. Вона має повну підтримку всіх Fluent features: acrylic, mica, reveal effects, smooth animations.

WPF Fluent UI — це backport Fluent стилів у WPF. Обмеження:

  • Немає справжнього acrylic/mica (WPF не має доступу до compositor API)
  • Обмежені animations (WPF animations повільніші за WinUI)
  • Немає деяких нових контролів (InfoBar, NavigationView, TeachingTip)

Проте WPF Fluent UI дає:

  • Сучасний вигляд без міграції на WinUI
  • Rounded corners, правильні кольори, typography
  • Підтримка Light/Dark теми
  • Консистентність з Windows 11

Для нових проєктів розгляньте WinUI 3. Для існуючих WPF додатків — Fluent UI є чудовим способом модернізувати вигляд.

Увімкнення Fluent UI у WPF

Тепер перейдемо до практики — як увімкнути Fluent UI у вашому WPF додатку.

Крок 1: Перевірка версії .NET

Fluent UI для WPF доступний у:

  • .NET 6 (Windows) — частково
  • .NET 7 (Windows) — покращена підтримка
  • .NET 8 (Windows) — повна підтримка, рекомендовано

Перевірте ваш .csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWPF>true</UseWPF>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

Якщо ви на .NET Framework 4.8 — Fluent UI недоступний. Потрібна міграція на .NET 6+.

Крок 2: Підключення Fluent ResourceDictionary

Fluent стилі знаходяться у PresentationFramework.Fluent.dll, яка входить у .NET runtime. Не потрібно встановлювати додаткові NuGet пакети.

App.xaml:

<Application x:Class="MyWPFApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <!-- Fluent Theme -->
                <ResourceDictionary Source="pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Ось і все! Тепер всі стандартні контроли (Button, TextBox, ComboBox, etc.) автоматично використовують Fluent стилі.

Крок 3: Перевірка результату

Створіть просте вікно для перевірки:

<Window x:Class="MyWPFApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Fluent UI Demo" Height="400" Width="600">
    <StackPanel Margin="20" Spacing="10">
        <TextBlock Text="Fluent UI in WPF" 
                   FontSize="24" 
                   FontWeight="SemiBold" />
        
        <Button Content="Primary Button" 
                Width="200" 
                HorizontalAlignment="Left" />
        
        <Button Content="Disabled Button" 
                Width="200" 
                HorizontalAlignment="Left"
                IsEnabled="False" />
        
        <TextBox Text="Text input with Fluent style" 
                 Width="300" 
                 HorizontalAlignment="Left" />
        
        <ComboBox Width="300" 
                  HorizontalAlignment="Left"
                  SelectedIndex="0">
            <ComboBoxItem Content="Option 1" />
            <ComboBoxItem Content="Option 2" />
            <ComboBoxItem Content="Option 3" />
        </ComboBox>
        
        <CheckBox Content="Fluent CheckBox" />
        <RadioButton Content="Fluent RadioButton" />
        
        <Slider Width="300" 
                HorizontalAlignment="Left"
                Minimum="0" Maximum="100" Value="50" />
    </StackPanel>
</Window>

Запустіть додаток. Ви побачите:

  • Кнопки з rounded corners
  • Subtle hover effects
  • Сучасні кольори (синій accent)
  • Правильні відступи та typography
Порівняння: До і ПісляБез Fluent UI:
  • Сірі прямокутні кнопки
  • Плоскі контроли без depth
  • Застарілий вигляд Windows Vista/7
З Fluent UI:
  • Rounded corners на всіх контролах
  • Subtle shadows для depth
  • Smooth hover/pressed animations
  • Сучасні кольори Windows 11
  • Консистентна typography
Різниця драматична, особливо для користувачів Windows 11.

Світла та темна тема

Fluent UI підтримує світлу (Light) та темну (Dark) теми. За замовчуванням використовується світла тема, але можна перемикати програмно або автоматично слідувати за системною темою.

Ручне перемикання теми

using System.Windows;

public partial class App : Application
{
    public void SetTheme(string themeName)
    {
        var fluentTheme = Resources.MergedDictionaries
            .FirstOrDefault(d => d.Source?.ToString().Contains("Fluent.xaml") == true);
        
        if (fluentTheme != null)
        {
            Resources.MergedDictionaries.Remove(fluentTheme);
        }
        
        var newTheme = new ResourceDictionary
        {
            Source = new Uri($"pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.{themeName}.xaml")
        };
        
        Resources.MergedDictionaries.Add(newTheme);
    }
}

Доступні теми:

  • Fluent.xaml — Light theme (за замовчуванням)
  • Fluent.Dark.xaml — Dark theme
  • Fluent.HC.xaml — High Contrast theme (для accessibility)

Використання:

// У MainWindow або ViewModel
((App)Application.Current).SetTheme("Dark");

Автоматичне слідування за системною темою

Windows дозволяє користувачам обирати світлу або темну тему у Settings. Можна автоматично слідувати за цим вибором:

using Microsoft.Win32;
using System.Windows;

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        
        // Встановити тему при старті
        ApplySystemTheme();
        
        // Слідкувати за змінами системної теми
        SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
    }
    
    private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
    {
        if (e.Category == UserPreferenceCategory.General)
        {
            ApplySystemTheme();
        }
    }
    
    private void ApplySystemTheme()
    {
        var isDarkTheme = IsSystemDarkTheme();
        SetTheme(isDarkTheme ? "Dark" : "Light");
    }
    
    private bool IsSystemDarkTheme()
    {
        try
        {
            using var key = Registry.CurrentUser.OpenSubKey(
                @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
            
            var value = key?.GetValue("AppsUseLightTheme");
            return value is int intValue && intValue == 0;
        }
        catch
        {
            return false; // За замовчуванням світла тема
        }
    }
    
    public void SetTheme(string themeName)
    {
        Dispatcher.Invoke(() =>
        {
            var fluentTheme = Resources.MergedDictionaries
                .FirstOrDefault(d => d.Source?.ToString().Contains("Fluent") == true);
            
            if (fluentTheme != null)
            {
                Resources.MergedDictionaries.Remove(fluentTheme);
            }
            
            var newTheme = new ResourceDictionary
            {
                Source = new Uri($"pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.{themeName}.xaml")
            };
            
            Resources.MergedDictionaries.Insert(0, newTheme);
        });
    }
    
    protected override void OnExit(ExitEventArgs e)
    {
        SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged;
        base.OnExit(e);
    }
}

Тепер додаток автоматично перемикається між світлою та темною темою коли користувач змінює системні налаштування.

UI для перемикання теми

Додайте у вікно можливість ручного перемикання:

<StackPanel Orientation="Horizontal" Margin="10">
    <TextBlock Text="Theme:" VerticalAlignment="Center" Margin="0,0,10,0" />
    <ComboBox x:Name="ThemeComboBox" 
              Width="150"
              SelectionChanged="ThemeComboBox_SelectionChanged">
        <ComboBoxItem Content="Light" Tag="Light" />
        <ComboBoxItem Content="Dark" Tag="Dark" />
        <ComboBoxItem Content="System Default" Tag="System" IsSelected="True" />
    </ComboBox>
</StackPanel>
private void ThemeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (ThemeComboBox.SelectedItem is ComboBoxItem item)
    {
        var tag = item.Tag.ToString();
        
        if (tag == "System")
        {
            ((App)Application.Current).ApplySystemTheme();
        }
        else
        {
            ((App)Application.Current).SetTheme(tag);
        }
    }
}
Чому темна тема важлива?Темна тема — це не просто "модно". Вона має реальні переваги:
  • Зменшення втоми очей при роботі у темряві або при тривалому використанні
  • Економія батареї на OLED екранах (темні пікселі не світяться)
  • Accessibility для користувачів з світлочутливістю
  • Професійний вигляд для creative/developer tools
Підтримка темної теми — це стандарт для сучасних додатків.

Кастомізація кольорів та стилів

Fluent UI надає базові стилі, але часто потрібно кастомізувати кольори під ваш brand або створити власні варіанти контролів.

Структура Fluent ресурсів

Fluent UI визначає кольори через систему ресурсів:

<!-- Приклад ресурсів з Fluent.xaml -->
<SolidColorBrush x:Key="SystemAccentBrush" Color="#0078D4" />
<SolidColorBrush x:Key="SystemAccentBrushSecondary" Color="#005A9E" />
<SolidColorBrush x:Key="SystemAccentBrushTertiary" Color="#004578" />

<SolidColorBrush x:Key="SystemControlBackgroundBaseLowBrush" Color="#F3F3F3" />
<SolidColorBrush x:Key="SystemControlBackgroundBaseMediumBrush" Color="#E6E6E6" />

<SolidColorBrush x:Key="SystemControlForegroundBaseHighBrush" Color="#000000" />
<SolidColorBrush x:Key="SystemControlForegroundBaseMediumBrush" Color="#666666" />

Ці ресурси використовуються у стилях контролів. Щоб змінити кольори, перевизначте ці ресурси.

Зміна accent color

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <!-- Fluent Theme -->
            <ResourceDictionary Source="pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.xaml" />
        </ResourceDictionary.MergedDictionaries>
        
        <!-- Перевизначення accent color -->
        <SolidColorBrush x:Key="SystemAccentBrush" Color="#6B46C1" />
        <SolidColorBrush x:Key="SystemAccentBrushSecondary" Color="#553C9A" />
        <SolidColorBrush x:Key="SystemAccentBrushTertiary" Color="#44307A" />
    </ResourceDictionary>
</Application.Resources>

Тепер всі кнопки, checkboxes, sliders використовують фіолетовий accent замість синього.

Створення власних стилів з BasedOn

Найпотужніша можливість — створення власних стилів з наслідуванням від Fluent базових стилів через BasedOn.

Приклад: Primary та Secondary кнопки

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.xaml" />
        </ResourceDictionary.MergedDictionaries>
        
        <!-- Primary Button (Accent background) -->
        <Style x:Key="PrimaryButtonStyle" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
            <Setter Property="Background" Value="{DynamicResource SystemAccentBrush}" />
            <Setter Property="Foreground" Value="White" />
            <Setter Property="BorderBrush" Value="{DynamicResource SystemAccentBrush}" />
            <Setter Property="Padding" Value="16,8" />
            <Setter Property="FontWeight" Value="SemiBold" />
        </Style>
        
        <!-- Secondary Button (Outline style) -->
        <Style x:Key="SecondaryButtonStyle" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
            <Setter Property="Background" Value="Transparent" />
            <Setter Property="Foreground" Value="{DynamicResource SystemAccentBrush}" />
            <Setter Property="BorderBrush" Value="{DynamicResource SystemAccentBrush}" />
            <Setter Property="BorderThickness" Value="1" />
            <Setter Property="Padding" Value="16,8" />
        </Style>
        
        <!-- Danger Button (Red for destructive actions) -->
        <Style x:Key="DangerButtonStyle" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
            <Setter Property="Background" Value="#D13438" />
            <Setter Property="Foreground" Value="White" />
            <Setter Property="BorderBrush" Value="#D13438" />
            <Setter Property="Padding" Value="16,8" />
        </Style>
    </ResourceDictionary>
</Application.Resources>

Використання:

<StackPanel Orientation="Horizontal" Spacing="10">
    <Button Content="Save" Style="{StaticResource PrimaryButtonStyle}" />
    <Button Content="Cancel" Style="{StaticResource SecondaryButtonStyle}" />
    <Button Content="Delete" Style="{StaticResource DangerButtonStyle}" />
</StackPanel>

Ключовий момент: BasedOn="{StaticResource {x:Type Button}}" наслідує ВСІ Fluent стилі (rounded corners, hover effects, animations), але дозволяє перевизначити конкретні властивості (Background, Foreground).

Чому BasedOn важливий?

Без BasedOn ви втрачаєте всі Fluent стилі:

<!-- ПОГАНО: Втрачаються Fluent стилі -->
<Style x:Key="MyButtonStyle" TargetType="Button">
    <Setter Property="Background" Value="Red" />
</Style>
<!-- Результат: червона кнопка, але БЕЗ rounded corners, БЕЗ hover effects -->
<!-- ДОБРЕ: Зберігаються Fluent стилі -->
<Style x:Key="MyButtonStyle" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
    <Setter Property="Background" Value="Red" />
</Style>
<!-- Результат: червона кнопка З rounded corners, З hover effects -->

BasedOn="{StaticResource {x:Type Button}}" означає "наслідуй стиль за замовчуванням для типу Button", який у нашому випадку є Fluent стилем.

Складніший приклад: Card-style контейнер

<!-- Card Style з Fluent elevation -->
<Style x:Key="CardStyle" TargetType="Border">
    <Setter Property="Background" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
    <Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
    <Setter Property="BorderThickness" Value="1" />
    <Setter Property="CornerRadius" Value="8" />
    <Setter Property="Padding" Value="16" />
    <Setter Property="Effect">
        <Setter.Value>
            <DropShadowEffect Color="Black" 
                              Opacity="0.1" 
                              BlurRadius="8" 
                              ShadowDepth="2" 
                              Direction="270" />
        </Setter.Value>
    </Setter>
</Style>

Використання:

<Border Style="{StaticResource CardStyle}" Width="300" Margin="20">
    <StackPanel>
        <TextBlock Text="Card Title" 
                   FontSize="18" 
                   FontWeight="SemiBold" 
                   Margin="0,0,0,10" />
        <TextBlock Text="This is a card with Fluent-style elevation and rounded corners." 
                   TextWrapping="Wrap" />
        <Button Content="Action" 
                Style="{StaticResource PrimaryButtonStyle}" 
                HorizontalAlignment="Right" 
                Margin="0,10,0,0" />
    </StackPanel>
</Border>

Організація ресурсів для великих проєктів

Для великих проєктів не варто тримати всі стилі в App.xaml. Краще організувати їх у окремі файли.

Структура папок

MyWPFApp/
├── Styles/
│   ├── Colors.xaml          # Кольори та brushes
│   ├── Typography.xaml      # Шрифти та text styles
│   ├── Buttons.xaml         # Стилі кнопок
│   ├── TextBoxes.xaml       # Стилі текстових полів
│   ├── Cards.xaml           # Card-style контейнери
│   └── Theme.xaml           # Головний файл, який об'єднує все
├── Views/
├── ViewModels/
└── App.xaml

Colors.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <!-- Brand Colors -->
    <Color x:Key="BrandPrimaryColor">#6B46C1</Color>
    <Color x:Key="BrandSecondaryColor">#553C9A</Color>
    <Color x:Key="BrandTertiaryColor">#44307A</Color>
    
    <SolidColorBrush x:Key="BrandPrimaryBrush" Color="{StaticResource BrandPrimaryColor}" />
    <SolidColorBrush x:Key="BrandSecondaryBrush" Color="{StaticResource BrandSecondaryColor}" />
    <SolidColorBrush x:Key="BrandTertiaryBrush" Color="{StaticResource BrandTertiaryColor}" />
    
    <!-- Semantic Colors -->
    <SolidColorBrush x:Key="SuccessBrush" Color="#107C10" />
    <SolidColorBrush x:Key="WarningBrush" Color="#FFA500" />
    <SolidColorBrush x:Key="ErrorBrush" Color="#D13438" />
    <SolidColorBrush x:Key="InfoBrush" Color="#0078D4" />
    
    <!-- Override Fluent Accent -->
    <SolidColorBrush x:Key="SystemAccentBrush" Color="{StaticResource BrandPrimaryColor}" />
    <SolidColorBrush x:Key="SystemAccentBrushSecondary" Color="{StaticResource BrandSecondaryColor}" />
    <SolidColorBrush x:Key="SystemAccentBrushTertiary" Color="{StaticResource BrandTertiaryColor}" />
</ResourceDictionary>

Typography.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <!-- Typography Styles -->
    <Style x:Key="TitleTextStyle" TargetType="TextBlock">
        <Setter Property="FontSize" Value="28" />
        <Setter Property="FontWeight" Value="SemiBold" />
        <Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
    </Style>
    
    <Style x:Key="SubtitleTextStyle" TargetType="TextBlock">
        <Setter Property="FontSize" Value="20" />
        <Setter Property="FontWeight" Value="SemiBold" />
        <Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
    </Style>
    
    <Style x:Key="BodyTextStyle" TargetType="TextBlock">
        <Setter Property="FontSize" Value="14" />
        <Setter Property="FontWeight" Value="Normal" />
        <Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
        <Setter Property="TextWrapping" Value="Wrap" />
    </Style>
    
    <Style x:Key="CaptionTextStyle" TargetType="TextBlock">
        <Setter Property="FontSize" Value="12" />
        <Setter Property="FontWeight" Value="Normal" />
        <Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseMediumBrush}" />
    </Style>
</ResourceDictionary>

Buttons.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <!-- Primary Button -->
    <Style x:Key="PrimaryButtonStyle" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
        <Setter Property="Background" Value="{DynamicResource BrandPrimaryBrush}" />
        <Setter Property="Foreground" Value="White" />
        <Setter Property="BorderBrush" Value="{DynamicResource BrandPrimaryBrush}" />
        <Setter Property="Padding" Value="16,8" />
        <Setter Property="FontWeight" Value="SemiBold" />
        <Setter Property="MinWidth" Value="100" />
    </Style>
    
    <!-- Secondary Button -->
    <Style x:Key="SecondaryButtonStyle" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="Foreground" Value="{DynamicResource BrandPrimaryBrush}" />
        <Setter Property="BorderBrush" Value="{DynamicResource BrandPrimaryBrush}" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="Padding" Value="16,8" />
        <Setter Property="MinWidth" Value="100" />
    </Style>
    
    <!-- Icon Button -->
    <Style x:Key="IconButtonStyle" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="BorderThickness" Value="0" />
        <Setter Property="Padding" Value="8" />
        <Setter Property="Width" Value="40" />
        <Setter Property="Height" Value="40" />
    </Style>
</ResourceDictionary>

Theme.xaml (головний файл)

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <ResourceDictionary.MergedDictionaries>
        <!-- Fluent Base Theme -->
        <ResourceDictionary Source="pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.xaml" />
        
        <!-- Custom Styles -->
        <ResourceDictionary Source="Colors.xaml" />
        <ResourceDictionary Source="Typography.xaml" />
        <ResourceDictionary Source="Buttons.xaml" />
        <ResourceDictionary Source="TextBoxes.xaml" />
        <ResourceDictionary Source="Cards.xaml" />
    </ResourceDictionary.MergedDictionaries>
    
</ResourceDictionary>

App.xaml (підключення Theme.xaml)

<Application x:Class="MyWPFApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Styles/Theme.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Тепер всі стилі організовані, легко знайти потрібний файл, та легко підтримувати.

Підтримка Light та Dark теми у власних стилях

Коли ви створюєте власні стилі, важливо щоб вони правильно працювали у обох темах.

Використання DynamicResource

Завжди використовуйте DynamicResource для кольорів, які залежать від теми:

<!-- ПОГАНО: StaticResource не оновлюється при зміні теми -->
<Style x:Key="MyCardStyle" TargetType="Border">
    <Setter Property="Background" Value="{StaticResource SystemControlBackgroundBaseLowBrush}" />
</Style>

<!-- ДОБРЕ: DynamicResource оновлюється при зміні теми -->
<Style x:Key="MyCardStyle" TargetType="Border">
    <Setter Property="Background" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
</Style>

Створення theme-aware кольорів

Якщо потрібні власні кольори, які змінюються залежно від теми:

Colors.Light.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <SolidColorBrush x:Key="CardBackgroundBrush" Color="#FFFFFF" />
    <SolidColorBrush x:Key="CardBorderBrush" Color="#E0E0E0" />
    <SolidColorBrush x:Key="TextPrimaryBrush" Color="#000000" />
    <SolidColorBrush x:Key="TextSecondaryBrush" Color="#666666" />
    
</ResourceDictionary>

Colors.Dark.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <SolidColorBrush x:Key="CardBackgroundBrush" Color="#2D2D2D" />
    <SolidColorBrush x:Key="CardBorderBrush" Color="#3F3F3F" />
    <SolidColorBrush x:Key="TextPrimaryBrush" Color="#FFFFFF" />
    <SolidColorBrush x:Key="TextSecondaryBrush" Color="#B0B0B0" />
    
</ResourceDictionary>

Динамічне завантаження:

public void SetTheme(string themeName)
{
    // Видалити старі ресурси
    var oldFluentTheme = Resources.MergedDictionaries
        .FirstOrDefault(d => d.Source?.ToString().Contains("Fluent") == true);
    var oldColorTheme = Resources.MergedDictionaries
        .FirstOrDefault(d => d.Source?.ToString().Contains("Colors.") == true);
    
    if (oldFluentTheme != null)
        Resources.MergedDictionaries.Remove(oldFluentTheme);
    if (oldColorTheme != null)
        Resources.MergedDictionaries.Remove(oldColorTheme);
    
    // Додати нові ресурси
    var fluentTheme = new ResourceDictionary
    {
        Source = new Uri($"pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.{themeName}.xaml")
    };
    
    var colorTheme = new ResourceDictionary
    {
        Source = new Uri($"pack://application:,,,/MyWPFApp;component/Styles/Colors.{themeName}.xaml", UriKind.Absolute)
    };
    
    Resources.MergedDictionaries.Insert(0, fluentTheme);
    Resources.MergedDictionaries.Insert(1, colorTheme);
}

Тепер ваші власні кольори автоматично змінюються при перемиканні теми.

Обмеження та workarounds

Fluent UI у WPF має деякі обмеження порівняно з WinUI 3. Розглянемо їх та способи обходу.

1. Немає справжнього Acrylic/Mica

WPF не має доступу до Windows Compositor API, тому справжній acrylic/mica неможливий.

Workaround: Імітація через напівпрозорість та blur:

<Window x:Class="MyWPFApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        AllowsTransparency="True"
        WindowStyle="None"
        Background="Transparent">
    
    <Border Background="#F0FFFFFF" 
            CornerRadius="8"
            Margin="10">
        <Border.Effect>
            <BlurEffect Radius="20" />
        </Border.Effect>
        
        <!-- Content -->
    </Border>
</Window>

Це не справжній acrylic, але створює схожий ефект.

2. Повільні animations

WPF animations повільніші за WinUI через застарілу архітектуру.

Workaround: Використовуйте короткі animations (100-200ms) та прості easing functions:

<Style x:Key="FastHoverButtonStyle" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Trigger.EnterActions>
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                         To="0.8"
                                         Duration="0:0:0.1" />
                    </Storyboard>
                </BeginStoryboard>
            </Trigger.EnterActions>
            <Trigger.ExitActions>
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                         To="1.0"
                                         Duration="0:0:0.1" />
                    </Storyboard>
                </BeginStoryboard>
            </Trigger.ExitActions>
        </Trigger>
    </Style.Triggers>
</Style>

3. Відсутні нові контроли

WinUI 3 має нові контроли (InfoBar, NavigationView, TeachingTip), яких немає у WPF.

Workaround: Використовуйте сторонні бібліотеки:

  • ModernWpf — backport WinUI контролів у WPF
  • MahApps.Metro — додаткові контроли та стилі
  • HandyControl — велика колекція custom контролів

Або створіть власні UserControls з Fluent стилями.

Практичні завдання

Рівень 1: Базова інтеграція Fluent UI

Завдання: Створіть WPF додаток з Fluent UI та формою реєстрації.

Вимоги:

  • Підключити Fluent UI
  • Форма з полями: Username, Email, Password, Confirm Password
  • Кнопки: Register (Primary style), Cancel (Secondary style)
  • Всі контроли повинні використовувати Fluent стилі

Критерії успіху:

  • Контроли мають rounded corners
  • Hover effects працюють
  • Форма виглядає сучасно

Рівень 2: Світла та темна тема

Завдання: Додайте підтримку світлої та темної теми з автоматичним слідуванням за системною темою.

Вимоги:

  • Автоматичне визначення системної теми при старті
  • Слідкування за змінами системної теми
  • UI для ручного перемикання теми (Light/Dark/System)
  • Всі власні стилі повинні правильно працювати у обох темах

Критерії успіху:

  • Тема змінюється при зміні системних налаштувань
  • Ручне перемикання працює
  • Немає візуальних артефактів при перемиканні

Рівень 3: Власна система стилів

Завдання: Створіть власну систему стилів з організацією у окремі файли та підтримкою обох тем.

Вимоги:

  • Структура папок: Colors.xaml, Typography.xaml, Buttons.xaml, Cards.xaml
  • Окремі файли для Light та Dark теми (Colors.Light.xaml, Colors.Dark.xaml)
  • Мінімум 5 власних стилів кнопок (Primary, Secondary, Danger, Success, Icon)
  • Card-style контейнери з elevation
  • Typography styles (Title, Subtitle, Body, Caption)
  • Всі стилі з BasedOn від Fluent базових стилів

Критерії успіху:

  • Організована структура файлів
  • Всі стилі працюють у обох темах
  • BasedOn правильно використовується
  • Додаток виглядає професійно та консистентно

Резюме

Fluent UI у WPF — це потужний спосіб модернізувати вигляд вашого додатку без міграції на WinUI 3. Основні висновки:

Інтеграція проста: Один рядок у App.xaml — і всі контроли автоматично отримують Fluent стилі з rounded corners, сучасними кольорами та smooth animations.

BasedOn — ключ до кастомізації: Завжди використовуйте BasedOn="{StaticResource {x:Type ControlType}}" при створенні власних стилів. Це зберігає всі Fluent features (rounded corners, hover effects, animations) та дозволяє перевизначити тільки потрібні властивості.

DynamicResource для тем: Використовуйте DynamicResource замість StaticResource для кольорів, щоб стилі автоматично оновлювалися при зміні теми.

Організація ресурсів: Для великих проєктів організуйте стилі у окремі файли (Colors.xaml, Typography.xaml, Buttons.xaml) для легкої підтримки.

Підтримка тем обов'язкова: Сучасні користувачі очікують підтримку темної теми. Слідкуйте за системною темою через Registry та автоматично перемикайте додаток.

Обмеження WPF: Fluent UI у WPF не має справжнього acrylic/mica, animations повільніші, та відсутні деякі нові контроли. Проте для більшості додатків це не критично — Fluent UI у WPF дає достатньо для сучасного вигляду.

Fluent UI робить ваш WPF додаток сучасним, професійним та приємним у використанні. Це інвестиція у user experience, яка окупається довірою та задоволенням користувачів.

Словник ключових термінів
  • Fluent Design System — дизайн-мова Microsoft для Windows 10/11
  • PresentationFramework.Fluent — офіційна бібліотека Fluent UI для WPF (.NET 6+)
  • BasedOn — механізм наслідування стилів, критично важливий для збереження Fluent features
  • DynamicResource — динамічне посилання на ресурс, оновлюється при зміні теми
  • StaticResource — статичне посилання на ресурс, не оновлюється при зміні теми
  • ResourceDictionary — словник ресурсів для стилів, шаблонів, кольорів
  • MergedDictionaries — механізм об'єднання кількох ResourceDictionary
  • Accent Color — акцентний колір теми (за замовчуванням синій #0078D4)
  • Rounded Corners — заокруглені кути, характерна риса Fluent Design
  • Elevation — візуальна "висота" елемента через тіні (DropShadowEffect)
  • Theme-aware — стилі, які адаптуються до світлої/темної теми

Додаткові ресурси

Fluent Design System

Офіційний сайт Fluent Design System з guidelines та principles

WPF Fluent Theme Source

Вихідний код Fluent теми для WPF на GitHub

WPF Styling Documentation

Офіційна документація Microsoft про стилі та шаблони у WPF

ModernWpf

Альтернативна бібліотека з backport WinUI контролів у WPF

Windows 11 Design Principles

Принципи дизайну Windows 11 для створення нативного вигляду

WPF UI

Стороння бібліотека з Fluent-style контролами та темами для WPF

Попередня стаття: XAML Basics — основи розмітки — базовий синтаксис XAML для створення UI.

Наступна стаття: XAML Namespaces та Resources — робота з просторами імен та ресурсами у XAML.