Desktop UI

HandyControl — велика бібліотека UI контролів для WPF

Інтеграція HandyControl у WPF: 80+ сучасних контролів, Material Design стилі, темна тема, анімації, та повна екосистема для створення красивих додатків

HandyControl — велика бібліотека UI контролів для WPF

Вступ

Уявіть собі бібліотеку, яка надає вам понад 80 готових до використання UI контролів — від простих кнопок з анімаціями до складних компонентів як редактор коду, календар з годинником, порівняння зображень, та навіть waterfall layout панелі. HandyControl — це саме така бібліотека, яка виникла в китайській open-source спільноті та швидко стала однією з найбільших колекцій UI компонентів для WPF.

На відміну від WPF UI, яка фокусується на точному відтворенні Fluent Design System від Microsoft, HandyControl пропонує власний стиль, схожий на Material Design, але з унікальними елементами. Це бібліотека з філософією "Everything you need" — вона намагається покрити максимально широкий спектр сценаріїв використання, від базових форм до складних enterprise додатків.

HandyControl розробляється переважно китайською спільнотою, що має як переваги (активна розробка, багато контролів), так і недоліки (документація переважно китайською мовою). Проте завдяки великій кількості прикладів та demo додатку, навіть без знання китайської мови можна швидко розібратися з основними можливостями.

Бібліотека підтримує як .NET Framework 4.6.2+, так і сучасні версії .NET 6/7/8, що робить її універсальним рішенням для різних типів проектів. Вона включає систему тем з підтримкою світлої та темної теми, велику колекцію іконок, анімації, та навіть деякі базові графіки.

HandyControl vs WPF UI: HandyControl має значно більше контролів (80+ проти 30+), але WPF UI краще інтегрується з Windows 11 завдяки Fluent Design. HandyControl підходить для складних додатків, де потрібна велика різноманітність компонентів, тоді як WPF UI — для додатків, які повинні виглядати нативно на Windows 11.HandyControl vs ModernWpf: ModernWpf фокусується на Windows 10 Fluent Design і має менше контролів. HandyControl пропонує більше можливостей, але з власним стилем, який може не підходити для всіх проектів.HandyControl vs MahApps.Metro: Обидві бібліотеки великі та зрілі, але MahApps.Metro має Metro стиль (Windows 8), тоді як HandyControl — більш сучасний Material-like стиль.

Коли використовувати HandyControl

HandyControl найкраще підходить для:

  • Складних enterprise додатків з великою кількістю різних UI елементів (CRM, ERP, admin панелі)
  • Dashboard та аналітичних додатків, де потрібні графіки, gauge, timeline
  • Внутрішніх корпоративних систем, де важливіша функціональність, ніж точна відповідність Windows 11 дизайну
  • Проектів з обмеженим бюджетом на UI/UX, де потрібно швидко отримати красивий інтерфейс без створення власних контролів

HandyControl може не підійти для:

  • Додатків, які повинні виглядати нативно на Windows 11 — краще використати WPF UI
  • Простих додатків з мінімальним UI — HandyControl додасть зайвий розмір (~5-8 MB)
  • Проектів з жорсткими вимогами до accessibility — HandyControl має обмежену підтримку
  • Команд без знання англійської/китайської — документація може бути складною

Установка та базова інтеграція

Установка NuGet пакету

HandyControl доступний як NuGet пакет і встановлюється стандартним способом. Бібліотека підтримує як .NET Framework 4.6.2 і вище, так і .NET 6/7/8.

Встановлення через Package Manager Console:

Install-Package HandyControl

Або через .NET CLI:

dotnet add package HandyControl

Або через NuGet Package Manager в Visual Studio: шукайте "HandyControl" та встановіть останню стабільну версію.

На момент написання статті актуальна версія HandyControl — 3.5.x. Перевіряйте сумісність з вашою версією .NET:
  • .NET Framework 4.6.2+ — всі версії HandyControl
  • .NET 6/7/8 — HandyControl 3.4+
  • .NET Core 3.1 — HandyControl 3.0+
Для production проектів рекомендується використовувати стабільні релізи, а не preview версії.

Підключення тем у App.xaml

Після установки пакету необхідно підключити теми HandyControl у файлі App.xaml. HandyControl використовує систему ResourceDictionary для завантаження стилів та тем.

Базова конфігурація App.xaml:

<Application x:Class="HandyControlDemo.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:hc="https://handyorg.github.io/handycontrol"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <!-- Основна тема HandyControl -->
                <hc:ThemeResources/>
                <!-- Тема для контролів -->
                <hc:Theme/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Це мінімальна конфігурація, яка завантажує стандартну світлу тему HandyControl. Компонент ThemeResources завантажує кольори та ресурси теми, а Theme — стилі для всіх контролів.

Якщо ви хочете використовувати темну тему за замовчуванням:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <hc:ThemeResources RequestedTheme="Dark"/>
            <hc:Theme/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Параметр RequestedTheme може приймати значення Light або Dark.

Перше вікно з HandyControl

Після підключення тем можна створити перше вікно з використанням HandyControl. Навіть базове вікно автоматично отримає покращений вигляд завдяки стилям бібліотеки.

Простий приклад MainWindow.xaml:

<hc:Window x:Class="HandyControlDemo.MainWindow"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:hc="https://handyorg.github.io/handycontrol"
           Title="HandyControl Demo" 
           Height="450" 
           Width="800">
    <Grid Margin="20">
        <StackPanel>
            <TextBlock Text="Ласкаво просимо до HandyControl!" 
                       FontSize="24" 
                       FontWeight="Bold"
                       Margin="0,0,0,20"/>
            
            <hc:TextBox hc:InfoElement.Title="Ім'я користувача"
                        hc:InfoElement.Placeholder="Введіть ваше ім'я"
                        Margin="0,0,0,10"/>
            
            <hc:PasswordBox hc:InfoElement.Title="Пароль"
                            hc:InfoElement.Placeholder="Введіть пароль"
                            Margin="0,0,0,20"/>
            
            <hc:Button Content="Увійти" 
                       Style="{StaticResource ButtonPrimary}"
                       HorizontalAlignment="Left"/>
        </StackPanel>
    </Grid>
</hc:Window>

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Зверніть увагу на кілька ключових моментів:

  1. hc:Window замість Window — HandyControl надає власний контрол вікна з покращеним title bar, кнопками мінімізації/максимізації/закриття, та підтримкою тем
  2. hc:InfoElement attached properties — дозволяють додавати заголовки та placeholder до input контролів без додаткових TextBlock
  3. Style="{StaticResource ButtonPrimary}" — HandyControl надає набір готових стилів для різних типів кнопок

Налаштування мови

HandyControl підтримує багатомовність і за замовчуванням використовує англійську мову для системних повідомлень (діалоги, валідація, тощо). Бібліотека має вбудовану підтримку китайської та англійської мов.

Для зміни мови додайте в App.xaml.cs:

using HandyControl.Tools;

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        
        // Встановлення англійської мови
        ConfigHelper.Instance.SetLang("en");
        
        // Або китайської
        // ConfigHelper.Instance.SetLang("zh-cn");
    }
}

На жаль, HandyControl не має вбудованої підтримки української мови, але ви можете створити власні ресурси локалізації для вашого додатку, використовуючи стандартні механізми WPF.

Система тем та стилізація

Вбудовані теми

HandyControl надає три основні теми з коробки: Default (синя), Violet (фіолетова), та Dark (темна). Кожна тема включає повний набір кольорів для всіх контролів, забезпечуючи консистентний вигляд додатку.

Default тема використовує синій accent color (#1E90FF) і світлий фон. Це стандартна тема, яка завантажується за замовчуванням і підходить для більшості бізнес-додатків.

Violet тема використовує фіолетовий accent color (#9B59B6) і також має світлий фон. Ця тема підходить для креативних додатків або коли потрібно виділитися серед стандартних синіх інтерфейсів.

Dark тема використовує темний фон (#252526) з білим текстом та синім accent color. Темна тема зменшує навантаження на очі при роботі в умовах низького освітлення та популярна серед розробників.

Для програмного перемикання теми використовується клас ThemeManager:

using HandyControl.Themes;
using HandyControl.Tools;

// Перемикання на темну тему
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;

// Перемикання на світлу тему
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;

// Зміна accent color
ThemeManager.Current.AccentColor = Colors.Purple;

Приклад вікна з перемикачем теми:

<hc:Window x:Class="ThemeDemo.MainWindow"
           xmlns:hc="https://handyorg.github.io/handycontrol">
    <Grid>
        <StackPanel Margin="20">
            <TextBlock Text="Налаштування теми" 
                       FontSize="20" 
                       FontWeight="Bold"
                       Margin="0,0,0,20"/>
            
            <hc:ComboBox hc:InfoElement.Title="Виберіть тему"
                         SelectionChanged="ThemeComboBox_SelectionChanged"
                         SelectedIndex="0">
                <ComboBoxItem Content="Світла"/>
                <ComboBoxItem Content="Темна"/>
            </hc:ComboBox>
        </StackPanel>
    </Grid>
</hc:Window>

Code-behind для перемикання:

private void ThemeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var comboBox = sender as ComboBox;
    if (comboBox?.SelectedIndex == 0)
    {
        ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
    }
    else
    {
        ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
    }
}

Створення власної теми

HandyControl дозволяє створювати власні теми шляхом перевизначення кольорів у ResourceDictionary. Система тем базується на наборі ключових кольорів, які використовуються всіма контролами.

Основні кольори теми:

  • PrimaryBrush — основний accent color (кнопки, посилання, активні елементи)
  • DangerBrush — колір для небезпечних дій (видалення, помилки)
  • WarningBrush — колір для попереджень
  • InfoBrush — колір для інформаційних повідомлень
  • SuccessBrush — колір для успішних операцій
  • BackgroundBrush — фон додатку
  • RegionBrush — фон контейнерів та панелей
  • BorderBrush — колір рамок
  • TextIconBrush — колір тексту та іконок

Створення власної теми в окремому файлі CustomTheme.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <!-- Власний accent color (помаранчевий) -->
    <Color x:Key="PrimaryColor">#FF6B35</Color>
    <SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}"/>
    
    <!-- Темніший відтінок для hover -->
    <Color x:Key="PrimaryDarkColor">#E55A2B</Color>
    <SolidColorBrush x:Key="PrimaryDarkBrush" Color="{StaticResource PrimaryDarkColor}"/>
    
    <!-- Світліший відтінок для фону -->
    <Color x:Key="PrimaryLightColor">#FFE5D9</Color>
    <SolidColorBrush x:Key="PrimaryLightBrush" Color="{StaticResource PrimaryLightColor}"/>
    
    <!-- Інші кольори за потребою -->
    <Color x:Key="DangerColor">#E74C3C</Color>
    <SolidColorBrush x:Key="DangerBrush" Color="{StaticResource DangerColor}"/>
    
    <Color x:Key="SuccessColor">#27AE60</Color>
    <SolidColorBrush x:Key="SuccessBrush" Color="{StaticResource SuccessColor}"/>
    
</ResourceDictionary>

Підключення власної теми в App.xaml:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <hc:ThemeResources/>
            <hc:Theme/>
            <!-- Власна тема перевизначає кольори -->
            <ResourceDictionary Source="Themes/CustomTheme.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>
Власна тема повинна завантажуватися ПІСЛЯ hc:Theme, щоб перевизначити стандартні кольори. Якщо завантажити її раніше, HandyControl перезапише ваші кольори своїми стандартними значеннями.

Динамічна зміна теми

Для збереження вибору користувача між сесіями можна використовувати ConfigHelper від HandyControl або стандартні Settings WPF.

Приклад збереження теми в Settings:

// Properties/Settings.settings
// Додайте налаштування: Name="Theme", Type="string", Scope="User", Value="Light"

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        
        // Завантаження збереженої теми
        var savedTheme = Properties.Settings.Default.Theme;
        if (savedTheme == "Dark")
        {
            ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
        }
        else
        {
            ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
        }
    }
    
    protected override void OnExit(ExitEventArgs e)
    {
        // Збереження поточної теми
        Properties.Settings.Default.Theme = 
            ThemeManager.Current.ApplicationTheme == ApplicationTheme.Dark ? "Dark" : "Light";
        Properties.Settings.Default.Save();
        
        base.OnExit(e);
    }
}

Альтернативно, HandyControl надає власний ConfigHelper для збереження налаштувань:

using HandyControl.Tools;

// Збереження теми
ConfigHelper.Instance.SetConfig("Theme", "Dark");

// Завантаження теми
var theme = ConfigHelper.Instance.GetConfig<string>("Theme", "Light");

Skin система

HandyControl також підтримує концепцію "Skin" — це розширена версія тем, яка може включати не тільки кольори, але й зміни в layout, розмірах, шрифтах, та інших аспектах UI.

Skin — це ResourceDictionary, який може перевизначати будь-які ресурси HandyControl. На відміну від простої зміни кольорів, Skin може змінювати:

  • Розміри контролів (висота кнопок, padding)
  • Шрифти (FontFamily, FontSize)
  • Форми (CornerRadius для заокруглених кутів)
  • Анімації (тривалість, easing functions)
  • Layout (margins, spacing)

Приклад створення Skin з великими кнопками та заокругленими кутами:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <!-- Збільшені розміри -->
    <system:Double x:Key="DefaultControlHeight">40</system:Double>
    <Thickness x:Key="DefaultControlPadding">16,8</Thickness>
    
    <!-- Більш заокруглені кути -->
    <CornerRadius x:Key="DefaultCornerRadius">8</CornerRadius>
    
    <!-- Більший шрифт -->
    <system:Double x:Key="TextFontSize">15</system:Double>
    
    <!-- Власний стиль для кнопок -->
    <Style TargetType="Button" BasedOn="{StaticResource ButtonBaseStyle}">
        <Setter Property="Height" Value="{StaticResource DefaultControlHeight}"/>
        <Setter Property="Padding" Value="{StaticResource DefaultControlPadding}"/>
        <Setter Property="FontSize" Value="{StaticResource TextFontSize}"/>
    </Style>
    
</ResourceDictionary>

Skin дозволяє створювати радикально різні вигляди додатку без зміни XAML коду, просто перемикаючи ResourceDictionary.

Базові контроли (покращені версії стандартних)

HandyControl надає покращені версії всіх стандартних WPF контролів з додатковими можливостями, анімаціями, та кращим виглядом. Розглянемо найважливіші з них.

Button та його варіанти

HandyControl пропонує кілька типів кнопок для різних сценаріїв використання.

Button — стандартна кнопка з анімаціями при hover та click. HandyControl автоматично застосовує стилі до всіх кнопок, додаючи ripple ефект та плавні переходи.

<!-- Стандартна кнопка -->
<hc:Button Content="Стандартна кнопка"/>

<!-- Primary кнопка (accent color) -->
<hc:Button Content="Primary" Style="{StaticResource ButtonPrimary}"/>

<!-- Success кнопка (зелена) -->
<hc:Button Content="Success" Style="{StaticResource ButtonSuccess}"/>

<!-- Danger кнопка (червона) -->
<hc:Button Content="Danger" Style="{StaticResource ButtonDanger}"/>

<!-- Warning кнопка (жовта) -->
<hc:Button Content="Warning" Style="{StaticResource ButtonWarning}"/>

<!-- Info кнопка (синя) -->
<hc:Button Content="Info" Style="{StaticResource ButtonInfo}"/>

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

IconButton — кнопка з іконкою. HandyControl використовує власну систему іконок на базі Geometry.

<hc:Button Style="{StaticResource ButtonIcon}">
    <Path Data="{StaticResource SaveGeometry}" 
          Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"
          Width="16" Height="16"/>
</hc:Button>

<!-- Або з використанням attached property -->
<hc:Button Content="Зберегти" 
           Style="{StaticResource ButtonPrimary}"
           hc:IconElement.Geometry="{StaticResource SaveGeometry}"/>

LoadingButton — кнопка з індикатором завантаження, корисна для асинхронних операцій.

<hc:LoadingButton Content="Завантажити дані" 
                  IsLoading="{Binding IsLoading}"
                  Style="{StaticResource ButtonPrimary}"
                  Command="{Binding LoadDataCommand}"/>

У ViewModel:

private bool _isLoading;
public bool IsLoading
{
    get => _isLoading;
    set => SetProperty(ref _isLoading, value);
}

private async Task LoadDataAsync()
{
    IsLoading = true;
    try
    {
        await Task.Delay(2000); // Симуляція завантаження
        // Завантаження даних...
    }
    finally
    {
        IsLoading = false;
    }
}

ToggleButton — перемикач з покращеним виглядом та анімаціями.

<hc:ToggleButton Content="Увімкнути сповіщення" 
                 IsChecked="{Binding NotificationsEnabled}"/>

<!-- Стиль як switch -->
<hc:ToggleButton Style="{StaticResource ToggleButtonSwitch}"
                 IsChecked="{Binding IsDarkTheme}"/>

RadioButton та CheckBox — покращені версії з анімаціями та кращим виглядом.

<StackPanel>
    <TextBlock Text="Виберіть мову:" Margin="0,0,0,10"/>
    <hc:RadioButton Content="Українська" GroupName="Language" IsChecked="True"/>
    <hc:RadioButton Content="English" GroupName="Language"/>
    <hc:RadioButton Content="中文" GroupName="Language"/>
    
    <TextBlock Text="Налаштування:" Margin="0,20,0,10"/>
    <hc:CheckBox Content="Автоматичне оновлення"/>
    <hc:CheckBox Content="Показувати сповіщення"/>
    <hc:CheckBox Content="Зберігати історію"/>
</StackPanel>
HandyControl надає понад 10 різних стилів для кнопок:
  • ButtonPrimary, ButtonSuccess, ButtonDanger, ButtonWarning, ButtonInfo — кольорові кнопки
  • ButtonIcon — кнопка-іконка без тексту
  • ButtonCustom — базовий стиль для створення власних варіантів
  • ButtonDefault — стандартний стиль (застосовується автоматично)
Всі стилі підтримують ripple ефект при кліку та плавні анімації при hover.

TextBox та Input контроли

HandyControl значно покращує стандартні input контроли, додаючи watermark, іконки, кнопки очищення, та валідацію.

TextBox з заголовком та placeholder:

<hc:TextBox hc:InfoElement.Title="Email"
            hc:InfoElement.Placeholder="example@email.com"
            hc:InfoElement.Necessary="True"
            Text="{Binding Email, UpdateSourceTrigger=PropertyChanged}"/>

Attached property InfoElement додає додаткову інформацію до контролу:

  • Title — заголовок над полем
  • Placeholder — текст-підказка в порожньому полі
  • Necessary — позначка обов'язкового поля (червона зірочка)

TextBox з іконкою та кнопкою очищення:

<hc:TextBox hc:InfoElement.Title="Пошук"
            hc:InfoElement.Placeholder="Введіть запит..."
            Style="{StaticResource TextBoxExtend}"
            hc:IconElement.Geometry="{StaticResource SearchGeometry}"
            hc:TitleElement.TitlePlacement="Left"/>

PasswordBox з можливістю показу/приховування пароля:

<hc:PasswordBox hc:InfoElement.Title="Пароль"
                hc:InfoElement.Placeholder="Мінімум 8 символів"
                hc:InfoElement.Necessary="True"
                ShowEyeButton="True"
                Password="{Binding Password, Mode=TwoWay}"/>

Властивість ShowEyeButton="True" додає кнопку-іконку ока для показу/приховування пароля.

NumericUpDown — числовий input зі стрілками:

<hc:NumericUpDown hc:InfoElement.Title="Кількість"
                  Value="{Binding Quantity}"
                  Minimum="1"
                  Maximum="100"
                  Increment="1"/>

SearchBar — спеціалізований контрол для пошуку:

<hc:SearchBar hc:InfoElement.Placeholder="Пошук..."
              Style="{StaticResource SearchBarPlus}"
              SearchStarted="SearchBar_SearchStarted"/>

Code-behind:

private void SearchBar_SearchStarted(object sender, HandyControl.Data.FunctionEventArgs<string> e)
{
    var searchText = e.Info;
    // Виконати пошук...
}

AutoCompleteTextBox — текстове поле з автодоповненням:

<hc:AutoCompleteTextBox hc:InfoElement.Title="Країна"
                        hc:InfoElement.Placeholder="Почніть вводити..."
                        ItemsSource="{Binding Countries}"
                        DisplayMemberPath="Name"/>

У ViewModel:

public ObservableCollection<Country> Countries { get; set; }

public MainViewModel()
{
    Countries = new ObservableCollection<Country>
    {
        new Country { Name = "Україна" },
        new Country { Name = "США" },
        new Country { Name = "Великобританія" },
        // ...
    };
}
HandyControl підтримує стандартну WPF валідацію через IDataErrorInfo або INotifyDataErrorInfo. При помилці валідації контрол автоматично підсвічується червоним, а повідомлення про помилку відображається під полем.Приклад з IDataErrorInfo:
public class UserViewModel : INotifyPropertyChanged, IDataErrorInfo
{
    private string _email;
    public string Email
    {
        get => _email;
        set { _email = value; OnPropertyChanged(); }
    }
    
    public string this[string columnName]
    {
        get
        {
            if (columnName == nameof(Email))
            {
                if (string.IsNullOrEmpty(Email))
                    return "Email обов'язковий";
                if (!Email.Contains("@"))
                    return "Невірний формат email";
            }
            return null;
        }
    }
    
    public string Error => null;
}

ComboBox та Picker контроли

HandyControl покращує контроли вибору, додаючи кращий вигляд, анімації, та додаткові можливості.

ComboBox — покращений dropdown з підтримкою пошуку:

<hc:ComboBox hc:InfoElement.Title="Місто"
             hc:InfoElement.Placeholder="Виберіть місто"
             ItemsSource="{Binding Cities}"
             SelectedItem="{Binding SelectedCity}"
             IsEditable="True"/>

DatePicker — вибір дати з покращеним календарем:

<hc:DatePicker hc:InfoElement.Title="Дата народження"
               hc:InfoElement.Placeholder="Виберіть дату"
               SelectedDate="{Binding BirthDate}"
               DisplayDateStart="1900-01-01"
               DisplayDateEnd="{x:Static system:DateTime.Now}"/>

TimePicker — вибір часу:

<hc:TimePicker hc:InfoElement.Title="Час зустрічі"
               SelectedTime="{Binding MeetingTime}"
               TimeFormat="HH:mm"/>

DateTimePicker — комбінований вибір дати та часу:

<hc:DateTimePicker hc:InfoElement.Title="Дата та час події"
                   DateTime="{Binding EventDateTime}"/>

ColorPicker — вибір кольору з різними режимами:

<hc:ColorPicker hc:InfoElement.Title="Колір фону"
                SelectedBrush="{Binding BackgroundColor}"/>

ColorPicker підтримує кілька режимів вибору:

  • RGB sliders
  • HSV color wheel
  • Hex input
  • Палітра попередньо визначених кольорів

Slider та Progress контроли

Slider з tooltip та custom marks:

<hc:Slider hc:InfoElement.Title="Гучність"
           Minimum="0"
           Maximum="100"
           Value="{Binding Volume}"
           IsSnapToTickEnabled="True"
           TickFrequency="10"
           AutoToolTipPlacement="TopLeft"/>

RangeSlider — вибір діапазону значень:

<hc:RangeSlider hc:InfoElement.Title="Діапазон цін"
                Minimum="0"
                Maximum="10000"
                ValueStart="{Binding MinPrice}"
                ValueEnd="{Binding MaxPrice}"
                TickFrequency="1000"/>

RangeSlider корисний для фільтрів, де потрібно вибрати діапазон (ціна від-до, вік від-до, тощо).

ProgressBar з анімаціями:

<!-- Стандартний прогрес -->
<hc:ProgressBar Value="{Binding Progress}" 
                Maximum="100"
                Height="20"/>

<!-- Невизначений прогрес (indeterminate) -->
<hc:ProgressBar IsIndeterminate="True" Height="20"/>

CircleProgressBar — круговий індикатор прогресу:

<hc:CircleProgressBar Value="{Binding Progress}"
                      Maximum="100"
                      Width="100"
                      Height="100"
                      ShowText="True"
                      ArcThickness="10"/>

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

WaveProgressBar — прогрес з wave ефектом (анімована хвиля):

<hc:WaveProgressBar Value="{Binding Progress}"
                    Maximum="100"
                    Width="200"
                    Height="200"
                    ShowText="True"
                    WaveThickness="5"
                    WaveSpeed="5"/>

WaveProgressBar створює ефект води, що наповнює контейнер — це унікальний контрол, якого немає в інших WPF бібліотеках.

Layout та Container контроли

HandyControl надає спеціалізовані панелі для складних layout сценаріїв.

Panel контроли

UniformSpacingPanel — панель з рівномірними відступами між елементами:

<hc:UniformSpacingPanel Spacing="10" 
                        Orientation="Horizontal"
                        HorizontalAlignment="Center">
    <Button Content="Кнопка 1"/>
    <Button Content="Кнопка 2"/>
    <Button Content="Кнопка 3"/>
    <Button Content="Кнопка 4"/>
</hc:UniformSpacingPanel>

На відміну від StackPanel, де потрібно встановлювати Margin для кожного елемента, UniformSpacingPanel автоматично додає однакові відступи.

FlexPanel — flexbox-like layout для WPF:

<hc:FlexPanel Direction="Row" 
              Wrap="Wrap"
              JustifyContent="SpaceBetween"
              AlignItems="Center">
    <Button Content="Item 1" Width="100"/>
    <Button Content="Item 2" Width="150"/>
    <Button Content="Item 3" Width="120"/>
    <Button Content="Item 4" Width="100"/>
</hc:FlexPanel>

FlexPanel підтримує:

  • Direction — Row, Column, RowReverse, ColumnReverse
  • Wrap — NoWrap, Wrap, WrapReverse
  • JustifyContent — FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround
  • AlignItems — FlexStart, FlexEnd, Center, Stretch

WaterfallPanel — waterfall/masonry layout (як Pinterest):

<hc:WaterfallPanel Groups="3" 
                   DesiredLength="200"
                   AutoGroup="True">
    <Border Background="LightBlue" Height="150">
        <TextBlock Text="Item 1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Border>
    <Border Background="LightGreen" Height="200">
        <TextBlock Text="Item 2" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Border>
    <Border Background="LightCoral" Height="180">
        <TextBlock Text="Item 3" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Border>
    <!-- Більше елементів... -->
</hc:WaterfallPanel>

WaterfallPanel автоматично розподіляє елементи по колонках, мінімізуючи порожній простір.

RunCanvas — canvas для drag-and-drop сценаріїв:

<hc:RunCanvas Background="WhiteSmoke">
    <Button Content="Перетягни мене" 
            Canvas.Left="50" 
            Canvas.Top="50"
            hc:DragElement.IsDraggable="True"/>
</hc:RunCanvas>

Card та Group контроли

Card — контейнер з elevation (тінь) для групування контенту:

<hc:SimplePanel Margin="20">
    <hc:Card Effect="{StaticResource EffectShadow2}" 
             Padding="20"
             Margin="10">
        <StackPanel>
            <TextBlock Text="Статистика" 
                       FontSize="18" 
                       FontWeight="Bold"
                       Margin="0,0,0,10"/>
            <TextBlock Text="Користувачів: 1,234"/>
            <TextBlock Text="Активних: 567"/>
            <TextBlock Text="Нових сьогодні: 89"/>
        </StackPanel>
    </hc:Card>
</hc:SimplePanel>

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

HandyControl надає кілька рівнів elevation через ефекти тіні:

  • EffectShadow1 — легка тінь
  • EffectShadow2 — середня тінь
  • EffectShadow3 — сильна тінь
  • EffectShadow4 — дуже сильна тінь
  • EffectShadow5 — максимальна тінь

GroupBox — покращена група з кращим виглядом:

<hc:GroupBox Header="Налаштування профілю" Margin="10">
    <StackPanel Margin="10">
        <hc:TextBox hc:InfoElement.Title="Ім'я" Margin="0,0,0,10"/>
        <hc:TextBox hc:InfoElement.Title="Email" Margin="0,0,0,10"/>
        <hc:DatePicker hc:InfoElement.Title="Дата народження"/>
    </StackPanel>
</hc:GroupBox>

Expander — розгортаємий контейнер:

<hc:Expander Header="Додаткові налаштування" 
             IsExpanded="False"
             Margin="10">
    <StackPanel Margin="10">
        <hc:CheckBox Content="Увімкнути debug режим"/>
        <hc:CheckBox Content="Показувати технічну інформацію"/>
        <hc:NumericUpDown hc:InfoElement.Title="Timeout (секунди)" Value="30"/>
    </StackPanel>
</hc:Expander>

Expander корисний для приховування рідко використовуваних налаштувань або додаткової інформації.

ScrollViewer покращення

HandyControl покращує стандартний ScrollViewer, додаючи smooth scrolling та кращий вигляд scrollbar.

<hc:ScrollViewer Height="300">
    <StackPanel>
        <!-- Багато контенту... -->
    </StackPanel>
</hc:ScrollViewer>

Smooth scrolling автоматично застосовується до всіх ScrollViewer у додатку після підключення HandyControl тем.

ScrollViewerAttach — attached behaviors для додаткових можливостей:

<ScrollViewer hc:ScrollViewerAttach.AutoHide="True"
              hc:ScrollViewerAttach.Orientation="Vertical">
    <!-- Контент -->
</ScrollViewer>
  • AutoHide="True" — scrollbar автоматично ховається, коли не використовується
  • Orientation — напрямок scroll

Навігація та Window контроли

Window контроли

HandyControl надає кілька типів вікон з різними ефектами та можливостями.

Window — базове покращене вікно:

<hc:Window x:Class="MyApp.MainWindow"
           xmlns:hc="https://handyorg.github.io/handycontrol"
           Title="Мій додаток"
           Height="600"
           Width="900"
           WindowStartupLocation="CenterScreen"
           Icon="Assets/icon.ico">
    <!-- Контент -->
</hc:Window>

HandyControl Window автоматично додає:

  • Власний title bar з кнопками мінімізації/максимізації/закриття
  • Підтримку тем (світла/темна)
  • Можливість перетягування вікна
  • Resize handles

GlowWindow — вікно з glow ефектом (світіння навколо вікна):

<hc:GlowWindow x:Class="MyApp.MainWindow"
               xmlns:hc="https://handyorg.github.io/handycontrol"
               Title="Glow Window"
               GlowColor="#1E90FF"
               ActiveGlowColor="#FF6B35"
               NonActiveGlowColor="#CCCCCC">
    <!-- Контент -->
</hc:GlowWindow>

Glow ефект створює кольорове світіння навколо вікна, яке змінюється залежно від стану (активне/неактивне).

BlurWindow — вікно з blur фоном (розмиття):

<hc:BlurWindow x:Class="MyApp.SplashWindow"
               xmlns:hc="https://handyorg.github.io/handycontrol"
               Title="Splash"
               Height="400"
               Width="600"
               WindowStyle="None"
               AllowsTransparency="True"
               Background="Transparent">
    <Border Background="#CC000000" CornerRadius="10">
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock Text="Завантаження..." 
                       Foreground="White" 
                       FontSize="24"
                       Margin="0,0,0,20"/>
            <hc:LoadingCircle/>
        </StackPanel>
    </Border>
</hc:BlurWindow>

BlurWindow розмиває фон за вікном, створюючи ефект "frosted glass" (матового скла).

ImageBrowser — переглядач зображень з можливістю zoom, pan, rotate:

// Відкриття ImageBrowser програмно
var imageBrowser = new ImageBrowser(new Uri("path/to/image.jpg"));
imageBrowser.ShowDialog();

// Або з колекцією зображень
var images = new List<Uri>
{
    new Uri("image1.jpg", UriKind.Relative),
    new Uri("image2.jpg", UriKind.Relative),
    new Uri("image3.jpg", UriKind.Relative)
};
var imageBrowser = new ImageBrowser(images, 0); // 0 - індекс початкового зображення
imageBrowser.ShowDialog();

Навігація

TabControl — покращені вкладки з анімаціями:

<hc:TabControl Style="{StaticResource TabControlCapsule}">
    <TabItem Header="Головна">
        <TextBlock Text="Контент головної вкладки" Margin="20"/>
    </TabItem>
    <TabItem Header="Налаштування">
        <TextBlock Text="Контент налаштувань" Margin="20"/>
    </TabItem>
    <TabItem Header="Про програму">
        <TextBlock Text="Інформація про програму" Margin="20"/>
    </TabItem>
</hc:TabControl>

HandyControl надає кілька стилів для TabControl:

  • TabControlCapsule — вкладки у вигляді капсул
  • TabControlSliding — вкладки з анімацією ковзання
  • TabControlPivot — стиль pivot (як у Windows Phone)

SideMenu — бічне меню для навігації:

<hc:SideMenu AutoSelect="True" 
             ExpandMode="Freedom"
             SelectionChanged="SideMenu_SelectionChanged">
    <hc:SideMenuItem Header="Dashboard" 
                     hc:IconElement.Geometry="{StaticResource DashboardGeometry}"/>
    <hc:SideMenuItem Header="Користувачі" 
                     hc:IconElement.Geometry="{StaticResource UsersGeometry}"/>
    <hc:SideMenuItem Header="Налаштування" 
                     hc:IconElement.Geometry="{StaticResource SettingsGeometry}">
        <hc:SideMenuItem Header="Загальні"/>
        <hc:SideMenuItem Header="Безпека"/>
        <hc:SideMenuItem Header="Сповіщення"/>
    </hc:SideMenuItem>
</hc:SideMenu>

SideMenu підтримує вкладені елементи та різні режими розгортання:

  • Freedom — кожен елемент розгортається незалежно
  • Accordion — тільки один елемент може бути розгорнутим

Pagination — пагінація для великих списків:

<hc:Pagination PageIndex="{Binding CurrentPage, Mode=TwoWay}"
               MaxPageCount="{Binding TotalPages}"
               DataCountPerPage="20"
               PageUpdated="Pagination_PageUpdated"/>

Code-behind:

private void Pagination_PageUpdated(object sender, HandyControl.Data.FunctionEventArgs<int> e)
{
    var newPage = e.Info;
    // Завантажити дані для нової сторінки
    LoadPageData(newPage);
}

Dialogs та Popups

Dialog — модальні діалоги з різними типами:

// Простий діалог з повідомленням
Dialog.Show("Операція виконана успішно!");

// Діалог з підтвердженням
var result = Dialog.Show(new MessageBoxInfo
{
    Message = "Ви впевнені, що хочете видалити цей елемент?",
    Caption = "Підтвердження",
    Button = MessageBoxButton.YesNo,
    IconBrushKey = ResourceToken.AccentBrush,
    IconKey = ResourceToken.AskGeometry
});

if (result == MessageBoxResult.Yes)
{
    // Видалити елемент
}

// Власний діалог з custom контентом
Dialog.Show(new CustomDialogView());

Drawer — висувна панель (як у Material Design):

<hc:Drawer Name="DrawerLeft" 
           Dock="Left"
           ShowMode="Push">
    <Border Background="{DynamicResource RegionBrush}" 
            Width="300"
            BorderThickness="0,0,1,0"
            BorderBrush="{DynamicResource BorderBrush}">
        <StackPanel Margin="20">
            <TextBlock Text="Меню" FontSize="20" FontWeight="Bold" Margin="0,0,0,20"/>
            <Button Content="Пункт 1" Style="{StaticResource ButtonDefault}" Margin="0,0,0,10"/>
            <Button Content="Пункт 2" Style="{StaticResource ButtonDefault}" Margin="0,0,0,10"/>
            <Button Content="Пункт 3" Style="{StaticResource ButtonDefault}"/>
        </StackPanel>
    </Border>
</hc:Drawer>

<!-- Кнопка для відкриття Drawer -->
<Button Content="Відкрити меню" 
        Command="hc:ControlCommands.Open"
        CommandParameter="{Binding ElementName=DrawerLeft}"/>

Drawer підтримує кілька режимів:

  • Push — контент зсувається
  • Cover — drawer накриває контент
  • Press — контент стискається

Popover — спливаюче вікно з контентом:

<Button Content="Показати інфо">
    <hc:Interaction.Triggers>
        <hc:EventTrigger EventName="Click">
            <hc:PopupAction>
                <hc:PopupAction.PopupElement>
                    <Border Background="{DynamicResource RegionBrush}" 
                            Padding="10"
                            CornerRadius="4"
                            Effect="{StaticResource EffectShadow2}">
                        <TextBlock Text="Це додаткова інформація"/>
                    </Border>
                </hc:PopupAction.PopupElement>
            </hc:PopupAction>
        </hc:EventTrigger>
    </hc:Interaction.Triggers>
</Button>

Data Display контроли

List та Grid контроли

DataGrid — покращена таблиця з кращим виглядом:

<hc:DataGrid ItemsSource="{Binding Users}"
             AutoGenerateColumns="False"
             CanUserAddRows="False"
             HeadersVisibility="All"
             RowHeaderWidth="60">
    <DataGrid.Columns>
        <DataGridTextColumn Header="ID" Binding="{Binding Id}" Width="80"/>
        <DataGridTextColumn Header="Ім'я" Binding="{Binding Name}" Width="*"/>
        <DataGridTextColumn Header="Email" Binding="{Binding Email}" Width="*"/>
        <DataGridTextColumn Header="Роль" Binding="{Binding Role}" Width="120"/>
        <DataGridTemplateColumn Header="Дії" Width="150">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Button Content="Редагувати" 
                                Style="{StaticResource ButtonPrimary}"
                                Margin="0,0,5,0"
                                Padding="10,5"/>
                        <Button Content="Видалити" 
                                Style="{StaticResource ButtonDanger}"
                                Padding="10,5"/>
                    </StackPanel>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</hc:DataGrid>

HandyControl DataGrid автоматично застосовує стилі для:

  • Заголовків колонок
  • Рядків (з hover ефектом)
  • Виділення вибраного рядка
  • Сортування (іконки стрілок)

ListBox — покращений список:

<hc:ListBox ItemsSource="{Binding Items}"
            Style="{StaticResource ListBoxCustom}">
    <hc:ListBox.ItemTemplate>
        <DataTemplate>
            <Border Padding="10" 
                    BorderThickness="0,0,0,1" 
                    BorderBrush="{DynamicResource BorderBrush}">
                <StackPanel>
                    <TextBlock Text="{Binding Title}" 
                               FontWeight="Bold" 
                               FontSize="14"/>
                    <TextBlock Text="{Binding Description}" 
                               Foreground="{DynamicResource ThirdlyTextBrush}"
                               TextWrapping="Wrap"
                               Margin="0,5,0,0"/>
                </StackPanel>
            </Border>
        </DataTemplate>
    </hc:ListBox.ItemTemplate>
</hc:ListBox>

TreeView — покращене дерево:

<hc:TreeView ItemsSource="{Binding FolderStructure}">
    <hc:TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal">
                <Path Data="{StaticResource FolderGeometry}" 
                      Fill="{DynamicResource PrimaryBrush}"
                      Width="16" Height="16"
                      Margin="0,0,5,0"/>
                <TextBlock Text="{Binding Name}"/>
            </StackPanel>
        </HierarchicalDataTemplate>
    </hc:TreeView.ItemTemplate>
</hc:TreeView>

PropertyGrid — для редагування властивостей об'єкта:

<hc:PropertyGrid SelectedObject="{Binding CurrentItem}"
                 MaxTitleWidth="150"
                 MinTitleWidth="100"/>

PropertyGrid автоматично генерує UI для редагування властивостей об'єкта на основі його типу та атрибутів.

public class UserSettings
{
    [Category("Загальні")]
    [DisplayName("Ім'я користувача")]
    [Description("Ваше повне ім'я")]
    public string UserName { get; set; }
    
    [Category("Загальні")]
    [DisplayName("Email")]
    public string Email { get; set; }
    
    [Category("Інтерфейс")]
    [DisplayName("Темна тема")]
    public bool DarkTheme { get; set; }
    
    [Category("Інтерфейс")]
    [DisplayName("Мова")]
    public string Language { get; set; }
}

Badge та Tag контроли

Badge — значок з числом (для сповіщень):

<Button Content="Повідомлення" 
        hc:BadgeElement.Value="5"
        hc:BadgeElement.ShowBadge="True"/>

<!-- Або як окремий контрол -->
<hc:Badge Value="99+" 
          BadgeMargin="0,-10,-10,0"
          Status="Danger">
    <Border Width="50" Height="50" 
            Background="{DynamicResource PrimaryBrush}"
            CornerRadius="25"/>
</hc:Badge>

Badge може відображати:

  • Числа (з автоматичним скороченням: 99+)
  • Статус (Success, Danger, Warning, Info)
  • Dot (просто крапка без числа)

Tag — тег з можливістю видалення:

<hc:Tag Content="C#" 
        ShowCloseButton="True"
        Closed="Tag_Closed"/>

<!-- Список тегів -->
<ItemsControl ItemsSource="{Binding Tags}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <hc:UniformSpacingPanel Spacing="5" Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <hc:Tag Content="{Binding Name}" 
                    ShowCloseButton="True"
                    Style="{StaticResource TagPrimary}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Shield — shield badge (як на GitHub):

<hc:Shield Subject="Build" 
           Status="Passing" 
           Color="Success"/>

<hc:Shield Subject="Version" 
           Status="1.2.3" 
           Color="Info"/>

<hc:Shield Subject="License" 
           Status="MIT" 
           Color="Primary"/>

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Timeline та Steps

Timeline — часова шкала для відображення історії подій:

<hc:Timeline ItemsSource="{Binding Events}">
    <hc:Timeline.ItemTemplate>
        <DataTemplate>
            <Border Padding="10" Margin="0,0,0,20">
                <StackPanel>
                    <TextBlock Text="{Binding Title}" 
                               FontWeight="Bold" 
                               FontSize="14"/>
                    <TextBlock Text="{Binding Date, StringFormat='dd.MM.yyyy HH:mm'}" 
                               Foreground="{DynamicResource ThirdlyTextBrush}"
                               FontSize="12"
                               Margin="0,5,0,5"/>
                    <TextBlock Text="{Binding Description}" 
                               TextWrapping="Wrap"/>
                </StackPanel>
            </Border>
        </DataTemplate>
    </hc:Timeline.ItemTemplate>
</hc:Timeline>

StepBar — покрокова навігація:

<hc:StepBar StepIndex="{Binding CurrentStep}">
    <hc:StepBarItem Content="Крок 1: Реєстрація"/>
    <hc:StepBarItem Content="Крок 2: Підтвердження"/>
    <hc:StepBarItem Content="Крок 3: Налаштування"/>
    <hc:StepBarItem Content="Крок 4: Завершення"/>
</hc:StepBar>

StepBar автоматично відображає:

  • Завершені кроки (зелена галочка)
  • Поточний крок (синій кружок)
  • Майбутні кроки (сірий кружок)

Transfer — перенесення елементів між двома списками:

<hc:Transfer ItemsSource="{Binding AllItems}"
             TargetItemsSource="{Binding SelectedItems}"
             DisplayMemberPath="Name"/>

Transfer корисний для вибору елементів зі списку (наприклад, вибір користувачів для групи, вибір прав доступу, тощо).

Chart контроли (базові)

HandyControl надає базові можливості для відображення графіків, хоча для складних сценаріїв краще використовувати спеціалізовані бібліотеки як LiveCharts або OxyPlot.

Gauge — датчик/спідометр:

<hc:CircleProgressBar Value="{Binding CpuUsage}"
                      Maximum="100"
                      Width="150"
                      Height="150"
                      ShowText="True"
                      ArcThickness="15"
                      Style="{StaticResource CircleProgressBarGauge}"/>

Gauge можна використовувати для відображення:

  • Використання CPU/RAM
  • Прогрес виконання
  • Рівень заповнення
  • Швидкість/температура

Media та Image контроли

Image контроли

Gravatar — аватар з Gravatar сервісу:

<hc:Gravatar Id="{Binding UserEmail}" 
             Width="80" 
             Height="80"
             CornerRadius="40"/>

Gravatar автоматично завантажує аватар користувача з gravatar.com на основі email.

ImageBlock — зображення з placeholder:

<hc:ImageBlock Source="{Binding ImageUrl}"
               Width="200"
               Height="200"
               Stretch="UniformToFill"
               CornerRadius="4"/>

Якщо зображення не завантажилося, ImageBlock показує placeholder.

ImageSelector — вибір зображення з попереднім переглядом:

<hc:ImageSelector Uri="{Binding SelectedImagePath, Mode=TwoWay}"
                  Width="200"
                  Height="200"
                  HasClearButton="True"/>

ImageSelector дозволяє:

  • Вибрати зображення з файлової системи
  • Показати попередній перегляд
  • Очистити вибране зображення

Icon контроли

HandyControl використовує векторні іконки на базі Geometry, що дозволяє масштабувати їх без втрати якості.

GeometryIcon — векторна іконка:

<hc:GeometryIcon Data="{StaticResource SaveGeometry}" 
                 Width="24" 
                 Height="24"
                 Foreground="{DynamicResource PrimaryBrush}"/>

HandyControl включає велику колекцію готових іконок (понад 500), доступних через StaticResource.

PathIcon — іконка з Path:

<hc:PathIcon Data="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"
             Width="24"
             Height="24"/>

Media контроли

GifImage — відображення анімованих GIF:

<hc:GifImage Uri="/Assets/loading.gif" 
             Width="100" 
             Height="100"
             AutoPlay="True"/>

Стандартний WPF Image не підтримує анімацію GIF, тому GifImage — корисний контрол для відображення анімованих зображень.

Notification та Feedback контроли

Notification системи

Growl — toast notifications:

// Успішне повідомлення
Growl.Success("Дані збережено успішно!");

// Інформаційне повідомлення
Growl.Info("Нова версія доступна для завантаження");

// Попередження
Growl.Warning("Диск майже заповнений");

// Помилка
Growl.Error("Не вдалося підключитися до сервера");

// Запитання
Growl.Ask("Зберегти зміни перед виходом?", isConfirmed =>
{
    if (isConfirmed)
    {
        SaveChanges();
    }
});

Growl показує повідомлення у правому верхньому куті екрану з автоматичним зникненням через кілька секунд.

Для показу Growl у конкретному контейнері:

<Grid hc:Growl.GrowlParent="True">
    <!-- Контент -->
</Grid>
Growl.SuccessGlobal("Повідомлення для всього додатку");
// або
Growl.Success("Повідомлення для конкретного контейнера", "ContainerToken");

NotificationArea — область для сповіщень:

<hc:NotificationArea Name="NotificationArea" 
                     MaxCount="5"
                     Position="TopRight"/>
NotificationArea.Show(new NotificationInfo
{
    Title = "Нове повідомлення",
    Message = "У вас є нове повідомлення від користувача",
    Type = NotificationType.Info,
    ShowDateTime = true
});

Loading та Progress

LoadingCircle — круговий індикатор завантаження:

<hc:LoadingCircle Width="50" 
                  Height="50"
                  IsRunning="{Binding IsLoading}"/>

LoadingLine — лінійний індикатор завантаження:

<hc:LoadingLine IsRunning="{Binding IsLoading}"
                Height="4"
                Foreground="{DynamicResource PrimaryBrush}"/>

LoadingLine зазвичай розміщується у верхній частині вікна або контейнера для індикації фонового завантаження.

Rate — рейтинг зірками:

<hc:Rate Value="{Binding Rating, Mode=TwoWay}"
         Count="5"
         AllowHalf="True"
         IsReadOnly="False"/>

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Info контроли

Placeholder — placeholder для порожніх станів:

<hc:Placeholder Visibility="{Binding HasData, Converter={StaticResource Boolean2VisibilityReConverter}}">
    <hc:Placeholder.Content>
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <Path Data="{StaticResource EmptyGeometry}" 
                  Fill="{DynamicResource ThirdlyTextBrush}"
                  Width="100" Height="100"
                  Margin="0,0,0,20"/>
            <TextBlock Text="Немає даних для відображення" 
                       FontSize="16"
                       Foreground="{DynamicResource ThirdlyTextBrush}"/>
        </StackPanel>
    </hc:Placeholder.Content>
</hc:Placeholder>

Placeholder показується, коли немає даних для відображення (порожній список, відсутні результати пошуку, тощо).

Advanced контроли

Editor контроли

RichTextBox — покращений rich text editor:

<hc:RichTextBox Height="300"
                ShowLineNumber="True"
                AcceptsReturn="True"
                AcceptsTab="True"/>

HandyControl RichTextBox додає:

  • Нумерацію рядків
  • Підсвічування синтаксису (базове)
  • Кращий вигляд scrollbar

Calendar та Scheduler

CalendarWithClock — календар з годинником:

<hc:CalendarWithClock SelectedDateTime="{Binding SelectedDateTime, Mode=TwoWay}"/>

CalendarWithClock комбінує календар та аналоговий годинник для вибору дати та часу.

Clock — аналоговий годинник:

<hc:Clock Width="200" 
          Height="200"
          ShowSecondHand="True"
          DisplayTime="{Binding CurrentTime}"/>

Comparison та Diff

CompareSlider — порівняння двох зображень:

<hc:CompareSlider SourceLeft="/Assets/before.jpg"
                  SourceRight="/Assets/after.jpg"
                  Width="600"
                  Height="400"/>

CompareSlider дозволяє перетягувати повзунок для порівняння двох зображень (до/після, оригінал/відредаговане, тощо).

Magnifier — лупа для зображень:

<hc:Magnifier Target="{Binding ElementName=TargetImage}"
              Width="150"
              Height="150"
              Scale="2"/>

Special контроли

ChatBubble — бульбашка чату:

<hc:ChatBubble Role="Sender" 
               Content="Привіт! Як справи?"
               Time="14:30"/>

<hc:ChatBubble Role="Receiver" 
               Content="Все добре, дякую!"
               Time="14:32"/>

ChatBubble автоматично вирівнює повідомлення (відправник праворуч, отримувач ліворуч) та додає час.

FloatingBlock — плаваючий блок:

<hc:FloatingBlock Duration="0:0:5"
                  ToX="100"
                  ToY="-100"
                  IsRunning="True">
    <TextBlock Text="+10 балів" 
               FontSize="20" 
               FontWeight="Bold"
               Foreground="Green"/>
</hc:FloatingBlock>

FloatingBlock створює анімацію плаваючого елемента (корисно для ігор, gamification, тощо).

Utilities та Helpers

HandyControl надає набір attached properties, behaviors, converters та extensions для спрощення розробки.

Attached Properties

InfoElement — додає додаткову інформацію до контролів:

<hc:TextBox hc:InfoElement.Title="Назва поля"
            hc:InfoElement.Placeholder="Введіть значення"
            hc:InfoElement.Necessary="True"
            hc:InfoElement.Symbol="★"/>

BorderElement — для стилізації рамок:

<Border hc:BorderElement.Circular="True"
        hc:BorderElement.CornerRadius="10"
        Width="100" Height="100"/>

TitleElement — для додавання заголовків:

<GroupBox hc:TitleElement.Title="Налаштування"
          hc:TitleElement.TitlePlacement="Left"/>

IconElement — для додавання іконок:

<Button Content="Зберегти"
        hc:IconElement.Geometry="{StaticResource SaveGeometry}"
        hc:IconElement.Width="16"
        hc:IconElement.Height="16"/>

Behaviors

DragElement — drag and drop функціональність:

<Border hc:DragElement.IsDraggable="True"
        Background="LightBlue"
        Width="100" Height="100">
    <TextBlock Text="Перетягни мене" 
               HorizontalAlignment="Center" 
               VerticalAlignment="Center"/>
</Border>

Converters

HandyControl включає набір корисних конвертерів:

<!-- Boolean to Visibility -->
<TextBlock Text="Видимий" 
           Visibility="{Binding IsVisible, Converter={StaticResource Boolean2VisibilityConverter}}"/>

<!-- Inverse Boolean -->
<CheckBox IsChecked="{Binding IsEnabled, Converter={StaticResource Boolean2BooleanReConverter}}"/>

<!-- String to Visibility -->
<TextBlock Text="{Binding Message}" 
           Visibility="{Binding Message, Converter={StaticResource String2VisibilityConverter}}"/>

<!-- Number comparison -->
<Button IsEnabled="{Binding Count, Converter={StaticResource Number2BooleanConverter}, ConverterParameter='0'}"/>

Практичні приклади

Приклад 1: Dashboard додаток

Створимо dashboard з бічним меню, статистичними картками, та графіками.

MainWindow.xaml:

<hc:Window x:Class="DashboardApp.MainWindow"
           xmlns:hc="https://handyorg.github.io/handycontrol"
           Title="Dashboard" 
           Height="700" 
           Width="1200">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="250"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        
        <!-- Бічне меню -->
        <Border Grid.Column="0" 
                Background="{DynamicResource RegionBrush}"
                BorderThickness="0,0,1,0"
                BorderBrush="{DynamicResource BorderBrush}">
            <StackPanel>
                <TextBlock Text="Dashboard" 
                           FontSize="24" 
                           FontWeight="Bold"
                           Margin="20"/>
                
                <hc:SideMenu AutoSelect="True" 
                             ExpandMode="Accordion"
                             Margin="10">
                    <hc:SideMenuItem Header="Головна" 
                                     hc:IconElement.Geometry="{StaticResource HomeGeometry}"
                                     IsSelected="True"/>
                    <hc:SideMenuItem Header="Статистика" 
                                     hc:IconElement.Geometry="{StaticResource ChartGeometry}"/>
                    <hc:SideMenuItem Header="Користувачі" 
                                     hc:IconElement.Geometry="{StaticResource UsersGeometry}"/>
                    <hc:SideMenuItem Header="Налаштування" 
                                     hc:IconElement.Geometry="{StaticResource SettingsGeometry}"/>
                </hc:SideMenu>
            </StackPanel>
        </Border>
        
        <!-- Основний контент -->
        <ScrollViewer Grid.Column="1">
            <StackPanel Margin="30">
                <TextBlock Text="Огляд" 
                           FontSize="28" 
                           FontWeight="Bold"
                           Margin="0,0,0,20"/>
                
                <!-- Статистичні картки -->
                <hc:UniformSpacingPanel Spacing="20" 
                                        Orientation="Horizontal"
                                        Margin="0,0,0,30">
                    <!-- Картка 1: Користувачі -->
                    <hc:Card Effect="{StaticResource EffectShadow2}" 
                             Width="250" 
                             Height="150"
                             Padding="20">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>
                            
                            <TextBlock Text="Користувачі" 
                                       FontSize="14"
                                       Foreground="{DynamicResource ThirdlyTextBrush}"/>
                            
                            <TextBlock Grid.Row="1" 
                                       Text="1,234" 
                                       FontSize="36"
                                       FontWeight="Bold"
                                       VerticalAlignment="Center"/>
                            
                            <StackPanel Grid.Row="2" Orientation="Horizontal">
                                <Path Data="{StaticResource UpGeometry}" 
                                      Fill="Green" 
                                      Width="12" Height="12"
                                      Margin="0,0,5,0"/>
                                <TextBlock Text="+12% від минулого місяця" 
                                           FontSize="12"
                                           Foreground="Green"/>
                            </StackPanel>
                        </Grid>
                    </hc:Card>
                    
                    <!-- Картка 2: Дохід -->
                    <hc:Card Effect="{StaticResource EffectShadow2}" 
                             Width="250" 
                             Height="150"
                             Padding="20">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>
                            
                            <TextBlock Text="Дохід" 
                                       FontSize="14"
                                       Foreground="{DynamicResource ThirdlyTextBrush}"/>
                            
                            <TextBlock Grid.Row="1" 
                                       Text="$45,678" 
                                       FontSize="36"
                                       FontWeight="Bold"
                                       VerticalAlignment="Center"/>
                            
                            <StackPanel Grid.Row="2" Orientation="Horizontal">
                                <Path Data="{StaticResource UpGeometry}" 
                                      Fill="Green" 
                                      Width="12" Height="12"
                                      Margin="0,0,5,0"/>
                                <TextBlock Text="+8% від минулого місяця" 
                                           FontSize="12"
                                           Foreground="Green"/>
                            </StackPanel>
                        </Grid>
                    </hc:Card>
                    
                    <!-- Картка 3: Замовлення -->
                    <hc:Card Effect="{StaticResource EffectShadow2}" 
                             Width="250" 
                             Height="150"
                             Padding="20">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>
                            
                            <TextBlock Text="Замовлення" 
                                       FontSize="14"
                                       Foreground="{DynamicResource ThirdlyTextBrush}"/>
                            
                            <TextBlock Grid.Row="1" 
                                       Text="567" 
                                       FontSize="36"
                                       FontWeight="Bold"
                                       VerticalAlignment="Center"/>
                            
                            <StackPanel Grid.Row="2" Orientation="Horizontal">
                                <Path Data="{StaticResource DownGeometry}" 
                                      Fill="Red" 
                                      Width="12" Height="12"
                                      Margin="0,0,5,0"/>
                                <TextBlock Text="-3% від минулого місяця" 
                                           FontSize="12"
                                           Foreground="Red"/>
                            </StackPanel>
                        </Grid>
                    </hc:Card>
                </hc:UniformSpacingPanel>
                
                <!-- Графік активності -->
                <hc:Card Effect="{StaticResource EffectShadow2}" 
                         Padding="20"
                         Margin="0,0,0,20">
                    <StackPanel>
                        <TextBlock Text="Активність користувачів" 
                                   FontSize="18" 
                                   FontWeight="Bold"
                                   Margin="0,0,0,20"/>
                        
                        <!-- Тут можна інтегрувати LiveCharts або інший графік -->
                        <Border Height="300" 
                                Background="{DynamicResource RegionBrush}"
                                CornerRadius="4">
                            <TextBlock Text="[Графік активності]" 
                                       HorizontalAlignment="Center" 
                                       VerticalAlignment="Center"
                                       Foreground="{DynamicResource ThirdlyTextBrush}"/>
                        </Border>
                    </StackPanel>
                </hc:Card>
                
                <!-- Останні транзакції -->
                <hc:Card Effect="{StaticResource EffectShadow2}" 
                         Padding="20">
                    <StackPanel>
                        <TextBlock Text="Останні транзакції" 
                                   FontSize="18" 
                                   FontWeight="Bold"
                                   Margin="0,0,0,20"/>
                        
                        <hc:DataGrid ItemsSource="{Binding RecentTransactions}"
                                     AutoGenerateColumns="False"
                                     CanUserAddRows="False"
                                     HeadersVisibility="All"
                                     Height="300">
                            <DataGrid.Columns>
                                <DataGridTextColumn Header="ID" Binding="{Binding Id}" Width="80"/>
                                <DataGridTextColumn Header="Користувач" Binding="{Binding UserName}" Width="*"/>
                                <DataGridTextColumn Header="Сума" Binding="{Binding Amount, StringFormat=C}" Width="120"/>
                                <DataGridTextColumn Header="Дата" Binding="{Binding Date, StringFormat='dd.MM.yyyy'}" Width="120"/>
                                <DataGridTemplateColumn Header="Статус" Width="120">
                                    <DataGridTemplateColumn.CellTemplate>
                                        <DataTemplate>
                                            <hc:Tag Content="{Binding Status}" 
                                                    Style="{StaticResource TagSuccess}"/>
                                        </DataTemplate>
                                    </DataGridTemplateColumn.CellTemplate>
                                </DataGridTemplateColumn>
                            </DataGrid.Columns>
                        </hc:DataGrid>
                    </StackPanel>
                </hc:Card>
            </StackPanel>
        </ScrollViewer>
    </Grid>
</hc:Window>

MainViewModel.cs:

public class MainViewModel : INotifyPropertyChanged
{
    public ObservableCollection<Transaction> RecentTransactions { get; set; }
    
    public MainViewModel()
    {
        LoadData();
    }
    
    private void LoadData()
    {
        RecentTransactions = new ObservableCollection<Transaction>
        {
            new Transaction { Id = 1, UserName = "Іван Петренко", Amount = 1250.00m, Date = DateTime.Now.AddDays(-1), Status = "Завершено" },
            new Transaction { Id = 2, UserName = "Марія Коваленко", Amount = 890.50m, Date = DateTime.Now.AddDays(-2), Status = "Завершено" },
            new Transaction { Id = 3, UserName = "Олег Сидоренко", Amount = 2100.00m, Date = DateTime.Now.AddDays(-3), Status = "В обробці" },
            // Більше транзакцій...
        };
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
}

public class Transaction
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public decimal Amount { get; set; }
    public DateTime Date { get; set; }
    public string Status { get; set; }
}

Приклад 2: Chat додаток

Створимо простий chat додаток з використанням ChatBubble та інших контролів HandyControl.

ChatWindow.xaml:

<hc:Window x:Class="ChatApp.ChatWindow"
           xmlns:hc="https://handyorg.github.io/handycontrol"
           Title="Chat" 
           Height="600" 
           Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        
        <!-- Header -->
        <Border Grid.Row="0" 
                Background="{DynamicResource PrimaryBrush}"
                Padding="20,15">
            <StackPanel Orientation="Horizontal">
                <hc:Gravatar Id="user@example.com" 
                             Width="40" 
                             Height="40"
                             CornerRadius="20"
                             Margin="0,0,15,0"/>
                <StackPanel VerticalAlignment="Center">
                    <TextBlock Text="Іван Петренко" 
                               Foreground="White" 
                               FontSize="16"
                               FontWeight="Bold"/>
                    <TextBlock Text="Онлайн" 
                               Foreground="White" 
                               FontSize="12"
                               Opacity="0.8"/>
                </StackPanel>
            </StackPanel>
        </Border>
        
        <!-- Messages area -->
        <hc:ScrollViewer Grid.Row="1" 
                         Name="MessagesScrollViewer"
                         Padding="20"
                         Background="{DynamicResource RegionBrush}">
            <ItemsControl ItemsSource="{Binding Messages}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Grid Margin="0,0,0,15">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            
                            <!-- Повідомлення від отримувача (ліворуч) -->
                            <StackPanel Grid.Column="0" 
                                        Visibility="{Binding IsReceived, Converter={StaticResource Boolean2VisibilityConverter}}">
                                <Border Background="White" 
                                        Padding="15,10"
                                        CornerRadius="10,10,10,0"
                                        MaxWidth="400"
                                        HorizontalAlignment="Left"
                                        Effect="{StaticResource EffectShadow1}">
                                    <TextBlock Text="{Binding Text}" 
                                               TextWrapping="Wrap"/>
                                </Border>
                                <TextBlock Text="{Binding Time, StringFormat='HH:mm'}" 
                                           FontSize="11"
                                           Foreground="{DynamicResource ThirdlyTextBrush}"
                                           Margin="10,5,0,0"/>
                            </StackPanel>
                            
                            <!-- Повідомлення від відправника (праворуч) -->
                            <StackPanel Grid.Column="2" 
                                        Visibility="{Binding IsSent, Converter={StaticResource Boolean2VisibilityConverter}}">
                                <Border Background="{DynamicResource PrimaryBrush}" 
                                        Padding="15,10"
                                        CornerRadius="10,10,0,10"
                                        MaxWidth="400"
                                        HorizontalAlignment="Right"
                                        Effect="{StaticResource EffectShadow1}">
                                    <TextBlock Text="{Binding Text}" 
                                               Foreground="White"
                                               TextWrapping="Wrap"/>
                                </Border>
                                <TextBlock Text="{Binding Time, StringFormat='HH:mm'}" 
                                           FontSize="11"
                                           Foreground="{DynamicResource ThirdlyTextBrush}"
                                           Margin="0,5,10,0"
                                           HorizontalAlignment="Right"/>
                            </StackPanel>
                        </Grid>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </hc:ScrollViewer>
        
        <!-- Input area -->
        <Border Grid.Row="2" 
                Background="{DynamicResource RegionBrush}"
                BorderThickness="0,1,0,0"
                BorderBrush="{DynamicResource BorderBrush}"
                Padding="20,15">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                
                <hc:TextBox Grid.Column="0" 
                            Name="MessageTextBox"
                            hc:InfoElement.Placeholder="Введіть повідомлення..."
                            AcceptsReturn="False"
                            MaxHeight="100"
                            VerticalScrollBarVisibility="Auto"
                            KeyDown="MessageTextBox_KeyDown"/>
                
                <hc:Button Grid.Column="1" 
                           Content="Надіслати" 
                           Style="{StaticResource ButtonPrimary}"
                           Margin="10,0,0,0"
                           Padding="20,8"
                           Command="{Binding SendMessageCommand}"/>
            </Grid>
        </Border>
    </Grid>
</hc:Window>

ChatViewModel.cs:

public class ChatViewModel : INotifyPropertyChanged
{
    public ObservableCollection<ChatMessage> Messages { get; set; }
    public ICommand SendMessageCommand { get; }
    
    private string _messageText;
    public string MessageText
    {
        get => _messageText;
        set
        {
            _messageText = value;
            OnPropertyChanged();
        }
    }
    
    public ChatViewModel()
    {
        Messages = new ObservableCollection<ChatMessage>();
        SendMessageCommand = new RelayCommand(SendMessage);
        
        // Завантаження історії повідомлень
        LoadMessages();
    }
    
    private void LoadMessages()
    {
        Messages.Add(new ChatMessage 
        { 
            Text = "Привіт! Як справи?", 
            Time = DateTime.Now.AddMinutes(-10), 
            IsReceived = true 
        });
        Messages.Add(new ChatMessage 
        { 
            Text = "Все добре, дякую! А у тебе?", 
            Time = DateTime.Now.AddMinutes(-9), 
            IsSent = true 
        });
        Messages.Add(new ChatMessage 
        { 
            Text = "Теж добре. Працюю над новим проектом.", 
            Time = DateTime.Now.AddMinutes(-8), 
            IsReceived = true 
        });
    }
    
    private void SendMessage()
    {
        if (string.IsNullOrWhiteSpace(MessageText))
            return;
        
        Messages.Add(new ChatMessage
        {
            Text = MessageText,
            Time = DateTime.Now,
            IsSent = true
        });
        
        MessageText = string.Empty;
        
        // Симуляція відповіді
        Task.Delay(1000).ContinueWith(_ =>
        {
            Application.Current.Dispatcher.Invoke(() =>
            {
                Messages.Add(new ChatMessage
                {
                    Text = "Отримав твоє повідомлення!",
                    Time = DateTime.Now,
                    IsReceived = true
                });
            });
        });
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class ChatMessage
{
    public string Text { get; set; }
    public DateTime Time { get; set; }
    public bool IsSent { get; set; }
    public bool IsReceived { get; set; }
}

Приклад 3: Admin panel з CRUD операціями

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

UsersManagementWindow.xaml:

<hc:Window x:Class="AdminPanel.UsersManagementWindow"
           xmlns:hc="https://handyorg.github.io/handycontrol"
           Title="Управління користувачами" 
           Height="700" 
           Width="1000">
    <Grid Margin="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        
        <!-- Header -->
        <TextBlock Grid.Row="0" 
                   Text="Управління користувачами" 
                   FontSize="28" 
                   FontWeight="Bold"
                   Margin="0,0,0,20"/>
        
        <!-- Filters and Actions -->
        <Grid Grid.Row="1" Margin="0,0,0,20">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            
            <hc:SearchBar Grid.Column="0" 
                          hc:InfoElement.Placeholder="Пошук за ім'ям або email..."
                          Style="{StaticResource SearchBarPlus}"
                          SearchStarted="SearchBar_SearchStarted"
                          Margin="0,0,10,0"/>
            
            <hc:ComboBox Grid.Column="1" 
                         hc:InfoElement.Title="Роль"
                         Width="150"
                         SelectedIndex="0"
                         SelectionChanged="RoleFilter_SelectionChanged"
                         Margin="0,0,10,0">
                <ComboBoxItem Content="Всі ролі"/>
                <ComboBoxItem Content="Адміністратор"/>
                <ComboBoxItem Content="Користувач"/>
                <ComboBoxItem Content="Гість"/>
            </hc:ComboBox>
            
            <hc:Button Grid.Column="2" 
                       Content="Додати користувача" 
                       Style="{StaticResource ButtonPrimary}"
                       hc:IconElement.Geometry="{StaticResource AddGeometry}"
                       Command="{Binding AddUserCommand}"/>
        </Grid>
        
        <!-- Users DataGrid -->
        <hc:Card Grid.Row="2" 
                 Effect="{StaticResource EffectShadow2}" 
                 Padding="0">
            <hc:DataGrid ItemsSource="{Binding FilteredUsers}"
                         SelectedItem="{Binding SelectedUser}"
                         AutoGenerateColumns="False"
                         CanUserAddRows="False"
                         HeadersVisibility="All"
                         RowHeaderWidth="60">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="ID" 
                                        Binding="{Binding Id}" 
                                        Width="80"
                                        IsReadOnly="True"/>
                    
                    <DataGridTemplateColumn Header="Аватар" Width="80">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <hc:Gravatar Id="{Binding Email}" 
                                             Width="40" 
                                             Height="40"
                                             CornerRadius="20"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    
                    <DataGridTextColumn Header="Ім'я" 
                                        Binding="{Binding Name}" 
                                        Width="*"/>
                    
                    <DataGridTextColumn Header="Email" 
                                        Binding="{Binding Email}" 
                                        Width="*"/>
                    
                    <DataGridTemplateColumn Header="Роль" Width="150">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <hc:Tag Content="{Binding Role}" 
                                        Style="{StaticResource TagPrimary}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    
                    <DataGridTemplateColumn Header="Статус" Width="120">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <hc:Tag Content="{Binding Status}" 
                                        Style="{Binding Status, Converter={StaticResource StatusToStyleConverter}}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    
                    <DataGridTextColumn Header="Дата реєстрації" 
                                        Binding="{Binding RegisteredDate, StringFormat='dd.MM.yyyy'}" 
                                        Width="150"
                                        IsReadOnly="True"/>
                    
                    <DataGridTemplateColumn Header="Дії" Width="200">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <hc:UniformSpacingPanel Spacing="5" Orientation="Horizontal">
                                    <hc:Button Content="Редагувати" 
                                               Style="{StaticResource ButtonInfo}"
                                               Padding="10,5"
                                               Command="{Binding DataContext.EditUserCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
                                               CommandParameter="{Binding}"/>
                                    
                                    <hc:Button Content="Видалити" 
                                               Style="{StaticResource ButtonDanger}"
                                               Padding="10,5"
                                               Command="{Binding DataContext.DeleteUserCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
                                               CommandParameter="{Binding}"/>
                                </hc:UniformSpacingPanel>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </hc:DataGrid>
        </hc:Card>
        
        <!-- Pagination -->
        <hc:Pagination Grid.Row="3" 
                       PageIndex="{Binding CurrentPage, Mode=TwoWay}"
                       MaxPageCount="{Binding TotalPages}"
                       DataCountPerPage="20"
                       PageUpdated="Pagination_PageUpdated"
                       Margin="0,20,0,0"
                       HorizontalAlignment="Center"/>
    </Grid>
</hc:Window>

UsersManagementViewModel.cs:

public class UsersManagementViewModel : INotifyPropertyChanged
{
    private ObservableCollection<User> _allUsers;
    private ObservableCollection<User> _filteredUsers;
    private User _selectedUser;
    private int _currentPage = 1;
    private int _totalPages;
    private string _searchText;
    private string _roleFilter;
    
    public ObservableCollection<User> FilteredUsers
    {
        get => _filteredUsers;
        set
        {
            _filteredUsers = value;
            OnPropertyChanged();
        }
    }
    
    public User SelectedUser
    {
        get => _selectedUser;
        set
        {
            _selectedUser = value;
            OnPropertyChanged();
        }
    }
    
    public int CurrentPage
    {
        get => _currentPage;
        set
        {
            _currentPage = value;
            OnPropertyChanged();
            LoadPage();
        }
    }
    
    public int TotalPages
    {
        get => _totalPages;
        set
        {
            _totalPages = value;
            OnPropertyChanged();
        }
    }
    
    public ICommand AddUserCommand { get; }
    public ICommand EditUserCommand { get; }
    public ICommand DeleteUserCommand { get; }
    
    public UsersManagementViewModel()
    {
        AddUserCommand = new RelayCommand(AddUser);
        EditUserCommand = new RelayCommand<User>(EditUser);
        DeleteUserCommand = new RelayCommand<User>(DeleteUser);
        
        LoadData();
    }
    
    private void LoadData()
    {
        // Симуляція завантаження даних
        _allUsers = new ObservableCollection<User>();
        for (int i = 1; i <= 100; i++)
        {
            _allUsers.Add(new User
            {
                Id = i,
                Name = $"Користувач {i}",
                Email = $"user{i}@example.com",
                Role = i % 3 == 0 ? "Адміністратор" : i % 2 == 0 ? "Користувач" : "Гість",
                Status = i % 5 == 0 ? "Неактивний" : "Активний",
                RegisteredDate = DateTime.Now.AddDays(-i * 10)
            });
        }
        
        ApplyFilters();
    }
    
    private void ApplyFilters()
    {
        var filtered = _allUsers.AsEnumerable();
        
        // Фільтр за пошуком
        if (!string.IsNullOrWhiteSpace(_searchText))
        {
            filtered = filtered.Where(u => 
                u.Name.Contains(_searchText, StringComparison.OrdinalIgnoreCase) ||
                u.Email.Contains(_searchText, StringComparison.OrdinalIgnoreCase));
        }
        
        // Фільтр за роллю
        if (!string.IsNullOrWhiteSpace(_roleFilter) && _roleFilter != "Всі ролі")
        {
            filtered = filtered.Where(u => u.Role == _roleFilter);
        }
        
        var filteredList = filtered.ToList();
        TotalPages = (int)Math.Ceiling(filteredList.Count / 20.0);
        
        // Пагінація
        FilteredUsers = new ObservableCollection<User>(
            filteredList.Skip((CurrentPage - 1) * 20).Take(20));
    }
    
    private void LoadPage()
    {
        ApplyFilters();
    }
    
    public void Search(string searchText)
    {
        _searchText = searchText;
        CurrentPage = 1;
        ApplyFilters();
    }
    
    public void FilterByRole(string role)
    {
        _roleFilter = role;
        CurrentPage = 1;
        ApplyFilters();
    }
    
    private void AddUser()
    {
        var dialog = new UserEditDialog();
        if (dialog.ShowDialog() == true)
        {
            var newUser = dialog.User;
            newUser.Id = _allUsers.Max(u => u.Id) + 1;
            _allUsers.Add(newUser);
            ApplyFilters();
            
            Growl.Success($"Користувача {newUser.Name} додано успішно!");
        }
    }
    
    private void EditUser(User user)
    {
        var dialog = new UserEditDialog(user);
        if (dialog.ShowDialog() == true)
        {
            var index = _allUsers.IndexOf(user);
            _allUsers[index] = dialog.User;
            ApplyFilters();
            
            Growl.Success($"Користувача {user.Name} оновлено успішно!");
        }
    }
    
    private void DeleteUser(User user)
    {
        var result = HandyControl.Controls.MessageBox.Show(
            $"Ви впевнені, що хочете видалити користувача {user.Name}?",
            "Підтвердження видалення",
            MessageBoxButton.YesNo,
            MessageBoxImage.Warning);
        
        if (result == MessageBoxResult.Yes)
        {
            _allUsers.Remove(user);
            ApplyFilters();
            
            Growl.Success($"Користувача {user.Name} видалено успішно!");
        }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Role { get; set; }
    public string Status { get; set; }
    public DateTime RegisteredDate { get; set; }
}

Міграція та інтеграція

Міграція з стандартного WPF

Міграція існуючого WPF додатку на HandyControl — це поступовий процес, який можна виконувати інкрементально.

Крок 1: Установка HandyControl

dotnet add package HandyControl

Крок 2: Підключення тем у App.xaml

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <hc:ThemeResources/>
            <hc:Theme/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Крок 3: Поступова заміна контролів

Не потрібно замінювати всі контроли одразу. Почніть з найпростіших:

<!-- Було -->
<Window>
    <Button Content="Зберегти"/>
</Window>

<!-- Стало -->
<hc:Window>
    <hc:Button Content="Зберегти" Style="{StaticResource ButtonPrimary}"/>
</hc:Window>

Крок 4: Додавання namespace

Додайте namespace HandyControl до кожного файлу XAML:

xmlns:hc="https://handyorg.github.io/handycontrol"

Типові проблеми при міграції:

  1. Конфлікти стилів — HandyControl може перевизначити стандартні стилі WPF. Рішення: використовуйте BasedOn для власних стилів.
  2. Розмір додатку — HandyControl додає ~5-8 MB до розміру. Рішення: використовуйте trimming при публікації.
  3. Performance — деякі контроли HandyControl важчі за стандартні WPF. Рішення: використовуйте virtualization для списків.

Інтеграція з MVVM

HandyControl добре працює з MVVM pattern. Всі контроли підтримують data binding та commands.

Приклад з CommunityToolkit.Mvvm:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

public partial class MainViewModel : ObservableObject
{
    [ObservableProperty]
    private string _userName;
    
    [ObservableProperty]
    private bool _isLoading;
    
    [RelayCommand]
    private async Task SaveAsync()
    {
        IsLoading = true;
        try
        {
            await SaveDataAsync();
            Growl.Success("Дані збережено успішно!");
        }
        catch (Exception ex)
        {
            Growl.Error($"Помилка: {ex.Message}");
        }
        finally
        {
            IsLoading = false;
        }
    }
}

Валідація з IDataErrorInfo:

public class UserViewModel : ObservableObject, IDataErrorInfo
{
    private string _email;
    public string Email
    {
        get => _email;
        set => SetProperty(ref _email, value);
    }
    
    public string this[string columnName]
    {
        get
        {
            if (columnName == nameof(Email))
            {
                if (string.IsNullOrEmpty(Email))
                    return "Email обов'язковий";
                if (!IsValidEmail(Email))
                    return "Невірний формат email";
            }
            return null;
        }
    }
    
    public string Error => null;
    
    private bool IsValidEmail(string email)
    {
        return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
    }
}

Інтеграція з іншими бібліотеками

Чи можна змішувати HandyControl з WPF UI?

Технічно можна, але не рекомендується через конфлікти стилів. Якщо потрібно використовувати обидві бібліотеки:

  1. Завантажуйте теми в певному порядку
  2. Використовуйте явні стилі (Style="{StaticResource ...}")
  3. Ізолюйте контроли в окремих ResourceDictionary

Інтеграція з LiveCharts для графіків:

<hc:Card Effect="{StaticResource EffectShadow2}" Padding="20">
    <lvc:CartesianChart Series="{Binding SeriesCollection}">
        <lvc:CartesianChart.AxisX>
            <lvc:Axis Title="Місяць"/>
        </lvc:CartesianChart.AxisX>
        <lvc:CartesianChart.AxisY>
            <lvc:Axis Title="Продажі"/>
        </lvc:CartesianChart.AxisY>
    </lvc:CartesianChart>
</hc:Card>

Інтеграція з MaterialDesignThemes:

Не рекомендується змішувати HandyControl з MaterialDesignThemes, оскільки обидві бібліотеки надають повний набір контролів і стилів. Виберіть одну бібліотеку для всього проекту.

Порівняння з альтернативами

ХарактеристикаHandyControlWPF UIModernWpfMahApps.Metro
Кількість контролів80+30+25+40+
Стиль дизайнуMaterial-likeFluent (Win11)Fluent (Win10)Metro (Win8)
ДокументаціяКитайська/АнглійськаАнглійськаАнглійськаАнглійська
Активність розробкиАктивнийДуже активнийМенш активнийАктивний
Складність вивченняВисокаСередняСередняВисока
Розмір бібліотеки~5-8 MB~2-3 MB~3-4 MB~4-6 MB
.NET Framework✅ 4.6.2+✅ 4.6.2+✅ 4.5+
.NET 6/7/8
Темна тема
Власні теми✅ Складно✅ Легко✅ Середньо✅ Середньо
Windows 11 native
Accessibility⚠️ Обмежена✅ Добра✅ Добра✅ Добра
Performance⚠️ Середній✅ Добрий✅ Добрий✅ Добрий
СпільнотаВелика (Китай)ВеликаСередняВелика

Коли використовувати HandyControl

Використовуйте HandyControl, якщо:

  • Потрібна велика колекція готових контролів (80+)
  • Створюєте складний enterprise додаток (CRM, ERP, admin панель)
  • Не критична документація англійською мовою
  • Не важливий Windows 11 native вигляд
  • Потрібні унікальні контроли (WaterfallPanel, CompareSlider, WaveProgressBar)
  • Бюджет на UI/UX обмежений

НЕ використовуйте HandyControl, якщо:

  • Додаток повинен виглядати нативно на Windows 11 → використайте WPF UI
  • Простий додаток з мінімальним UI → HandyControl додасть зайвий розмір
  • Жорсткі вимоги до accessibility → HandyControl має обмежену підтримку
  • Команда не знає англійської/китайської → документація буде складною
  • Критична performance → деякі контроли HandyControl важчі за стандартні

Рекомендації по вибору бібліотеки

Для Windows 11 додатків: WPF UI — найкращий вибір для нативного вигляду

Для Windows 10 додатків: ModernWpf — точна реалізація Fluent Design

Для складних enterprise додатків: HandyControl — найбільша колекція контролів

Для Metro стилю: MahApps.Metro — зріла бібліотека з великою спільнотою

Для простих додатків: Стандартний WPF або WPF UI з мінімальним набором контролів

Best Practices

Performance

Які контроли важкі:

  • WaterfallPanel з великою кількістю елементів
  • WaveProgressBar (анімація води)
  • GifImage (анімовані GIF)
  • DataGrid з великою кількістю рядків без virtualization

Як оптимізувати:

  1. Використовуйте virtualization для списків:
<hc:ListBox VirtualizingPanel.IsVirtualizing="True"
            VirtualizingPanel.VirtualizationMode="Recycling"
            ItemsSource="{Binding LargeCollection}"/>
  1. Lazy loading для зображень:
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.CreateOptions = BitmapCreateOptions.DelayCreation;
bitmap.UriSource = new Uri(imagePath);
bitmap.EndInit();
  1. Обмежте кількість анімацій:

Вимкніть анімації для контролів, які не видимі користувачу.

  1. Використовуйте Pagination:

Замість завантаження всіх даних одразу, використовуйте пагінацію.

Theming

Організація тем:

Створіть окрему папку Themes/ для всіх тем:

Themes/
  ├── LightTheme.xaml
  ├── DarkTheme.xaml
  └── CustomTheme.xaml

Динамічна зміна теми:

public class ThemeService
{
    public void ApplyTheme(string themeName)
    {
        var theme = themeName switch
        {
            "Light" => ApplicationTheme.Light,
            "Dark" => ApplicationTheme.Dark,
            _ => ApplicationTheme.Light
        };
        
        ThemeManager.Current.ApplicationTheme = theme;
        
        // Збереження вибору
        Properties.Settings.Default.Theme = themeName;
        Properties.Settings.Default.Save();
    }
}

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

Створіть ResourceDictionary з власними кольорами та завантажуйте його після HandyControl тем.

Локалізація

HandyControl підтримує тільки англійську та китайську мови. Для додавання власної мови:

  1. Створіть ResourceDictionary з перекладами
  2. Використовуйте DynamicResource для всіх текстів
  3. Перемикайте ResourceDictionary при зміні мови
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <system:String x:Key="SaveButton">Зберегти</system:String>
    <system:String x:Key="CancelButton">Скасувати</system:String>
    <system:String x:Key="DeleteButton">Видалити</system:String>
</ResourceDictionary>

Accessibility

HandyControl має обмежену підтримку accessibility. Для покращення:

  1. Додавайте AutomationProperties.Name до всіх інтерактивних елементів
  2. Використовуйте AutomationProperties.HelpText для додаткової інформації
  3. Забезпечте keyboard navigation (Tab order)
  4. Тестуйте з screen readers (Narrator, NVDA)
<hc:Button Content="Зберегти"
           AutomationProperties.Name="Зберегти зміни"
           AutomationProperties.HelpText="Натисніть, щоб зберегти всі зміни"/>

Обмеження та проблеми

Документація

Проблема: Документація переважно китайською мовою.

Рішення:

  • Використовуйте Google Translate для перекладу
  • Вивчайте demo додаток (HandyControlDemo)
  • Шукайте приклади на GitHub
  • Читайте англомовні статті та туторіали

Стиль

Проблема: HandyControl не має чистого Fluent Design, може не підходити для Windows 11.

Рішення:

  • Для Windows 11 native вигляду використовуйте WPF UI
  • Для внутрішніх корпоративних систем стиль HandyControl прийнятний
  • Можна кастомізувати стилі для наближення до Fluent Design

Розмір

Проблема: Велика бібліотека (~5-8 MB) впливає на розмір додатку.

Рішення:

  • Використовуйте trimming при публікації
  • Розгляньте self-contained deployment з NativeAOT
  • Для простих додатків виберіть легшу бібліотеку

Підтримка

Проблема: GitHub Issues переважно китайською мовою.

Рішення:

  • Пишіть Issues англійською — розробники зазвичай відповідають
  • Шукайте схожі проблеми через GitHub Search
  • Приєднуйтесь до англомовної спільноти WPF

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

Рівень 1: Базова інтеграція (2-3 години)

Мета: Створити простий додаток з HandyControl та освоїти базові контроли.

Завдання:

  1. Створіть новий WPF проект (.NET 6 або вище)
  2. Встановіть HandyControl через NuGet
  3. Підключіть теми у App.xaml
  4. Створіть вікно з формою реєстрації, яка включає:
    • TextBox для імені (з Title та Placeholder)
    • TextBox для email (з валідацією)
    • PasswordBox з ShowEyeButton
    • DatePicker для дати народження
    • ComboBox для вибору країни
    • CheckBox для згоди з умовами
    • Button для реєстрації (Primary style)
  5. Додайте перемикач теми (Light/Dark) у верхній частині вікна
  6. Реалізуйте базову валідацію через IDataErrorInfo
  7. При успішній реєстрації показуйте Growl notification

Критерії оцінки:

  • Всі контроли HandyControl використовуються правильно
  • Валідація працює коректно
  • Тема перемикається без помилок
  • Код організований з використанням MVVM

Рівень 2: Dashboard з даними (4-6 годин)

Мета: Створити dashboard з картками, графіками, та таблицею даних.

Завдання:

  1. Створіть додаток з бічним меню (SideMenu) та кількома сторінками
  2. Головна сторінка повинна містити:
    • 4 статистичні картки (Card) з різними метриками
    • Графік активності (можна використати placeholder або інтегрувати LiveCharts)
    • Timeline з останніми подіями
  3. Сторінка "Користувачі" повинна містити:
    • DataGrid з даними користувачів (мінімум 50 записів)
    • SearchBar для пошуку
    • ComboBox для фільтрації за роллю
    • Pagination для навігації по сторінках
  4. Сторінка "Налаштування" повинна містити:
    • PropertyGrid для редагування налаштувань
    • Перемикач теми
    • Вибір мови (англійська/українська)
  5. Реалізуйте навігацію між сторінками через SideMenu
  6. Додайте NotificationArea для системних повідомлень

Критерії оцінки:

  • Dashboard виглядає професійно
  • Навігація працює плавно
  • Фільтрація та пошук працюють коректно
  • Pagination відображає правильну кількість сторінок
  • Код організований з використанням MVVM та Services

Рівень 3: Повноцінний Admin Panel (8-12 годин)

Мета: Створити повноцінний admin panel з CRUD операціями, аутентифікацією, та advanced features.

Завдання:

  1. Створіть систему аутентифікації:
    • Login вікно з анімацією
    • Валідація credentials
    • Збереження сесії
  2. Головне вікно з:
    • SideMenu з іконками та вкладеними пунктами
    • Header з аватаром користувача (Gravatar) та кнопкою виходу
    • Breadcrumb для навігації
  3. CRUD для користувачів:
    • DataGrid з можливістю сортування та фільтрації
    • Діалог для створення/редагування користувача
    • Підтвердження видалення через MessageBox
    • Експорт даних у CSV/Excel
  4. CRUD для продуктів:
    • WaterfallPanel для відображення продуктів у вигляді карток
    • ImageSelector для завантаження зображень продуктів
    • ColorPicker для вибору кольору продукту
    • Rate для рейтингу
  5. Сторінка статистики:
    • Інтеграція з LiveCharts для графіків
    • CircleProgressBar для відображення метрик
    • Gauge для показників
    • Timeline для історії змін
  6. Налаштування:
    • Власна тема з кастомними кольорами
    • Збереження налаштувань у Settings
    • Експорт/імпорт налаштувань
  7. Advanced features:
    • Drawer для швидкого доступу до функцій
    • Notification system з різними типами повідомлень
    • Loading states для асинхронних операцій
    • Error handling з відображенням помилок

Критерії оцінки:

  • Додаток виглядає професійно та консистентно
  • Всі CRUD операції працюють коректно
  • Аутентифікація та авторизація реалізовані правильно
  • Асинхронні операції не блокують UI
  • Error handling покриває всі можливі сценарії
  • Код організований з використанням MVVM, Services, Repository pattern
  • Використано мінімум 30 різних контролів HandyControl
  • Додаток має власну тему з кастомними кольорами

Резюме

HandyControl — це найбільша бібліотека UI контролів для WPF з понад 80 компонентами, яка надає розробникам потужний інструментарій для створення сучасних desktop додатків. Бібліотека виникла в китайській open-source спільноті та швидко стала популярною завдяки великій кількості готових контролів, системі тем, та активній розробці.

Основні переваги HandyControl — це велика колекція контролів (від базових кнопок до складних компонентів як WaterfallPanel, CompareSlider, WaveProgressBar), підтримка світлої та темної теми, можливість створення власних тем, та велика спільнота розробників. Бібліотека підходить для складних enterprise додатків, де потрібна велика різноманітність UI елементів.

Основні недоліки — це документація переважно китайською мовою (хоча є англійські переклади), стиль, який не відповідає Windows 11 Fluent Design, великий розмір бібліотеки (~5-8 MB), та обмежена підтримка accessibility. Також деякі контроли можуть бути важчими за стандартні WPF, що впливає на performance.

HandyControl найкраще підходить для внутрішніх корпоративних систем, admin панелей, CRM/ERP систем, dashboard додатків, та інших складних enterprise додатків, де важливіша функціональність, ніж точна відповідність Windows 11 дизайну. Для додатків, які повинні виглядати нативно на Windows 11, краще використати WPF UI.

Бібліотека добре інтегрується з MVVM pattern, підтримує data binding та commands, має систему валідації, та може працювати разом з іншими бібліотеками (наприклад, LiveCharts для графіків). Міграція існуючого WPF додатку на HandyControl може бути поступовою — не потрібно замінювати всі контроли одразу.

HandyControl — open-source бібліотека UI контролів для WPF з понад 80 компонентамиGrowl — система toast notifications у HandyControlSideMenu — бічне меню для навігаціїCard — контейнер з elevation (тінь) для групування контентуWaterfallPanel — панель з waterfall/masonry layout (як Pinterest)CompareSlider — контрол для порівняння двох зображеньWaveProgressBar — прогрес бар з анімованою хвилеюInfoElement — attached properties для додавання заголовків та placeholder до контролівThemeManager — клас для управління темамиElevation — тінь, яка створює ефект підняття елемента над поверхнею

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

Офіційні ресурси:

Community ресурси:

Туторіали та статті:

Альтернативні бібліотеки:

Інструменти для розробки:

  • LiveCharts — бібліотека для графіків
  • OxyPlot — альтернативна бібліотека для графіків
  • CommunityToolkit.Mvvm — MVVM toolkit від Microsoft
  • Prism — framework для MVVM додатків