HandyControl — велика бібліотека UI контролів для WPF
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
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" та встановіть останню стабільну версію.
- .NET Framework 4.6.2+ — всі версії HandyControl
- .NET 6/7/8 — HandyControl 3.4+
- .NET Core 3.1 — HandyControl 3.0+
Підключення тем у 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)...
<Grid Margin="20" Background="White">
<StackPanel>
<TextBlock Text="Ласкаво просимо до HandyControl!"
FontSize="24"
FontWeight="Bold"
Margin="0,0,0,20"/>
<Border BorderBrush="#DCDCDC" BorderThickness="1" Padding="10" Margin="0,0,0,10">
<StackPanel>
<TextBlock Text="Ім'я користувача" FontSize="12" Foreground="#666" Margin="0,0,0,5"/>
<TextBox Text="" BorderThickness="0"/>
</StackPanel>
</Border>
<Border BorderBrush="#DCDCDC" BorderThickness="1" Padding="10" Margin="0,0,0,20">
<StackPanel>
<TextBlock Text="Пароль" FontSize="12" Foreground="#666" Margin="0,0,0,5"/>
<PasswordBox BorderThickness="0"/>
</StackPanel>
</Border>
<Button Content="Увійти"
Background="#1E90FF"
Foreground="White"
Padding="20,8"
BorderThickness="0"
HorizontalAlignment="Left"/>
</StackPanel>
</Grid>
Зверніть увагу на кілька ключових моментів:
- hc:Window замість Window — HandyControl надає власний контрол вікна з покращеним title bar, кнопками мінімізації/максимізації/закриття, та підтримкою тем
- hc:InfoElement attached properties — дозволяють додавати заголовки та placeholder до input контролів без додаткових TextBlock
- 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)...
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Content="Стандартна" Margin="5" Padding="15,8" Background="#F0F0F0" BorderBrush="#DCDCDC" BorderThickness="1"/>
<Button Content="Primary" Margin="5" Padding="15,8" Background="#1E90FF" Foreground="White" BorderThickness="0"/>
<Button Content="Success" Margin="5" Padding="15,8" Background="#27AE60" Foreground="White" BorderThickness="0"/>
<Button Content="Danger" Margin="5" Padding="15,8" Background="#E74C3C" Foreground="White" BorderThickness="0"/>
<Button Content="Warning" Margin="5" Padding="15,8" Background="#F39C12" Foreground="White" BorderThickness="0"/>
<Button Content="Info" Margin="5" Padding="15,8" Background="#3498DB" Foreground="White" BorderThickness="0"/>
</StackPanel>
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>
ButtonPrimary,ButtonSuccess,ButtonDanger,ButtonWarning,ButtonInfo— кольорові кнопкиButtonIcon— кнопка-іконка без текстуButtonCustom— базовий стиль для створення власних варіантівButtonDefault— стандартний стиль (застосовується автоматично)
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 = "Великобританія" },
// ...
};
}
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)...
<Grid Background="White" Width="150" Height="150">
<Ellipse Stroke="#E0E0E0" StrokeThickness="10" Width="100" Height="100"/>
<Path Stroke="#1E90FF" StrokeThickness="10" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="50,0">
<ArcSegment Point="100,50" Size="50,50" SweepDirection="Clockwise" IsLargeArc="False"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<TextBlock Text="65%" FontSize="24" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
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, ColumnReverseWrap— NoWrap, Wrap, WrapReverseJustifyContent— FlexStart, FlexEnd, Center, SpaceBetween, SpaceAroundAlignItems— 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)...
<Grid Background="#F5F5F5" Padding="20">
<Border Background="White"
Padding="20"
CornerRadius="4"
Effect="{StaticResource EffectShadow2}">
<StackPanel>
<TextBlock Text="Статистика"
FontSize="18"
FontWeight="Bold"
Margin="0,0,0,10"/>
<TextBlock Text="Користувачів: 1,234" Margin="0,0,0,5"/>
<TextBlock Text="Активних: 567" Margin="0,0,0,5"/>
<TextBlock Text="Нових сьогодні: 89"/>
</StackPanel>
</Border>
</Grid>
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)...
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Border Background="#555" Padding="8,4" Margin="2,0,0,0">
<TextBlock Text="Build" Foreground="White" FontSize="11"/>
</Border>
<Border Background="#27AE60" Padding="8,4">
<TextBlock Text="Passing" Foreground="White" FontSize="11"/>
</Border>
<Border Background="#555" Padding="8,4" Margin="10,0,0,0">
<TextBlock Text="Version" Foreground="White" FontSize="11"/>
</Border>
<Border Background="#3498DB" Padding="8,4">
<TextBlock Text="1.2.3" Foreground="White" FontSize="11"/>
</Border>
</StackPanel>
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)...
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Data="M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z"
Fill="#FFD700" Width="24" Height="24" Margin="2"/>
<Path Data="M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z"
Fill="#FFD700" Width="24" Height="24" Margin="2"/>
<Path Data="M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z"
Fill="#FFD700" Width="24" Height="24" Margin="2"/>
<Path Data="M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z"
Fill="#CCCCCC" Width="24" Height="24" Margin="2"/>
<Path Data="M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z"
Fill="#CCCCCC" Width="24" Height="24" Margin="2"/>
</StackPanel>
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"
Типові проблеми при міграції:
- Конфлікти стилів — HandyControl може перевизначити стандартні стилі WPF. Рішення: використовуйте
BasedOnдля власних стилів. - Розмір додатку — HandyControl додає ~5-8 MB до розміру. Рішення: використовуйте trimming при публікації.
- 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?
Технічно можна, але не рекомендується через конфлікти стилів. Якщо потрібно використовувати обидві бібліотеки:
- Завантажуйте теми в певному порядку
- Використовуйте явні стилі (
Style="{StaticResource ...}") - Ізолюйте контроли в окремих 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, оскільки обидві бібліотеки надають повний набір контролів і стилів. Виберіть одну бібліотеку для всього проекту.
Порівняння з альтернативами
| Характеристика | HandyControl | WPF UI | ModernWpf | MahApps.Metro |
|---|---|---|---|---|
| Кількість контролів | 80+ | 30+ | 25+ | 40+ |
| Стиль дизайну | Material-like | Fluent (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
Як оптимізувати:
- Використовуйте virtualization для списків:
<hc:ListBox VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
ItemsSource="{Binding LargeCollection}"/>
- Lazy loading для зображень:
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.CreateOptions = BitmapCreateOptions.DelayCreation;
bitmap.UriSource = new Uri(imagePath);
bitmap.EndInit();
- Обмежте кількість анімацій:
Вимкніть анімації для контролів, які не видимі користувачу.
- Використовуйте 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 підтримує тільки англійську та китайську мови. Для додавання власної мови:
- Створіть ResourceDictionary з перекладами
- Використовуйте
DynamicResourceдля всіх текстів - Перемикайте 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. Для покращення:
- Додавайте
AutomationProperties.Nameдо всіх інтерактивних елементів - Використовуйте
AutomationProperties.HelpTextдля додаткової інформації - Забезпечте keyboard navigation (Tab order)
- Тестуйте з 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 та освоїти базові контроли.
Завдання:
- Створіть новий WPF проект (.NET 6 або вище)
- Встановіть HandyControl через NuGet
- Підключіть теми у App.xaml
- Створіть вікно з формою реєстрації, яка включає:
- TextBox для імені (з Title та Placeholder)
- TextBox для email (з валідацією)
- PasswordBox з ShowEyeButton
- DatePicker для дати народження
- ComboBox для вибору країни
- CheckBox для згоди з умовами
- Button для реєстрації (Primary style)
- Додайте перемикач теми (Light/Dark) у верхній частині вікна
- Реалізуйте базову валідацію через IDataErrorInfo
- При успішній реєстрації показуйте Growl notification
Критерії оцінки:
- Всі контроли HandyControl використовуються правильно
- Валідація працює коректно
- Тема перемикається без помилок
- Код організований з використанням MVVM
Рівень 2: Dashboard з даними (4-6 годин)
Мета: Створити dashboard з картками, графіками, та таблицею даних.
Завдання:
- Створіть додаток з бічним меню (SideMenu) та кількома сторінками
- Головна сторінка повинна містити:
- 4 статистичні картки (Card) з різними метриками
- Графік активності (можна використати placeholder або інтегрувати LiveCharts)
- Timeline з останніми подіями
- Сторінка "Користувачі" повинна містити:
- DataGrid з даними користувачів (мінімум 50 записів)
- SearchBar для пошуку
- ComboBox для фільтрації за роллю
- Pagination для навігації по сторінках
- Сторінка "Налаштування" повинна містити:
- PropertyGrid для редагування налаштувань
- Перемикач теми
- Вибір мови (англійська/українська)
- Реалізуйте навігацію між сторінками через SideMenu
- Додайте NotificationArea для системних повідомлень
Критерії оцінки:
- Dashboard виглядає професійно
- Навігація працює плавно
- Фільтрація та пошук працюють коректно
- Pagination відображає правильну кількість сторінок
- Код організований з використанням MVVM та Services
Рівень 3: Повноцінний Admin Panel (8-12 годин)
Мета: Створити повноцінний admin panel з CRUD операціями, аутентифікацією, та advanced features.
Завдання:
- Створіть систему аутентифікації:
- Login вікно з анімацією
- Валідація credentials
- Збереження сесії
- Головне вікно з:
- SideMenu з іконками та вкладеними пунктами
- Header з аватаром користувача (Gravatar) та кнопкою виходу
- Breadcrumb для навігації
- CRUD для користувачів:
- DataGrid з можливістю сортування та фільтрації
- Діалог для створення/редагування користувача
- Підтвердження видалення через MessageBox
- Експорт даних у CSV/Excel
- CRUD для продуктів:
- WaterfallPanel для відображення продуктів у вигляді карток
- ImageSelector для завантаження зображень продуктів
- ColorPicker для вибору кольору продукту
- Rate для рейтингу
- Сторінка статистики:
- Інтеграція з LiveCharts для графіків
- CircleProgressBar для відображення метрик
- Gauge для показників
- Timeline для історії змін
- Налаштування:
- Власна тема з кастомними кольорами
- Збереження налаштувань у Settings
- Експорт/імпорт налаштувань
- 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 може бути поступовою — не потрібно замінювати всі контроли одразу.
Додаткові ресурси
Офіційні ресурси:
- GitHub репозиторій — вихідний код та Issues
- Офіційна документація — документація (переважно китайською)
- Demo додаток — завантажте HandyControlDemo для перегляду всіх контролів
- NuGet пакет — офіційний пакет
Community ресурси:
- HandyControl Wiki — додаткова документація
- Stack Overflow — питання та відповіді
- Reddit r/wpf — обговорення WPF та бібліотек
Туторіали та статті:
- HandyControl Getting Started — офіційний гайд для початківців
- HandyControl Themes Guide — гайд по темах
- WPF UI Libraries Comparison — порівняння різних WPF бібліотек
Альтернативні бібліотеки:
- WPF UI — Fluent Design для Windows 11
- ModernWpf — Fluent Design для Windows 10
- MahApps.Metro — Metro стиль
- MaterialDesignInXamlToolkit — Material Design
Інструменти для розробки:
- LiveCharts — бібліотека для графіків
- OxyPlot — альтернативна бібліотека для графіків
- CommunityToolkit.Mvvm — MVVM toolkit від Microsoft
- Prism — framework для MVVM додатків
WPF UI — сучасна бібліотека Fluent контролів
Інтеграція WPF UI (Wpf.Ui) у додатки: сучасні контроли Windows 11, NavigationView, Mica/Acrylic ефекти, темна тема, та повна екосистема Fluent Design
Простори імен та ресурси XAML
Розбираємо xmlns-простори імен, підключення власних класів у XAML, ResourceDictionary, StaticResource vs DynamicResource та організацію ресурсів у великих проєктах.