Уявіть собі бібліотеку, яка надає вам понад 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 може не підійти для:
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 у файлі 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. Навіть базове вікно автоматично отримає покращений вигляд завдяки стилям бібліотеки.
Простий приклад 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>
Зверніть увагу на кілька ключових моментів:
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. Система тем базується на наборі ключових кольорів, які використовуються всіма контролами.
Основні кольори теми:
Створення власної теми в окремому файлі 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");
HandyControl також підтримує концепцію "Skin" — це розширена версія тем, яка може включати не тільки кольори, але й зміни в layout, розмірах, шрифтах, та інших аспектах UI.
Skin — це ResourceDictionary, який може перевизначати будь-які ресурси HandyControl. На відміну від простої зміни кольорів, Skin може змінювати:
Приклад створення 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 контролів з додатковими можливостями, анімаціями, та кращим виглядом. Розглянемо найважливіші з них.
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 — стандартний стиль (застосовується автоматично)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;
}
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 підтримує кілька режимів вибору:
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 бібліотеках.
HandyControl надає спеціалізовані панелі для складних layout сценаріїв.
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, StretchWaterfallPanel — 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 — контейнер з 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 корисний для приховування рідко використовуваних налаштувань або додаткової інформації.
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 — напрямок scrollHandyControl надає кілька типів вікон з різними ефектами та можливостями.
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 автоматично додає:
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);
}
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>
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 автоматично застосовує стилі для:
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 — значок з числом (для сповіщень):
<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 може відображати:
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 — часова шкала для відображення історії подій:
<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 корисний для вибору елементів зі списку (наприклад, вибір користувачів для групи, вибір прав доступу, тощо).
HandyControl надає базові можливості для відображення графіків, хоча для складних сценаріїв краще використовувати спеціалізовані бібліотеки як LiveCharts або OxyPlot.
Gauge — датчик/спідометр:
<hc:CircleProgressBar Value="{Binding CpuUsage}"
Maximum="100"
Width="150"
Height="150"
ShowText="True"
ArcThickness="15"
Style="{StaticResource CircleProgressBarGauge}"/>
Gauge можна використовувати для відображення:
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 дозволяє:
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"/>
GifImage — відображення анімованих GIF:
<hc:GifImage Uri="/Assets/loading.gif"
Width="100"
Height="100"
AutoPlay="True"/>
Стандартний WPF Image не підтримує анімацію GIF, тому GifImage — корисний контрол для відображення анімованих зображень.
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
});
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>
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 показується, коли немає даних для відображення (порожній список, відсутні результати пошуку, тощо).
RichTextBox — покращений rich text editor:
<hc:RichTextBox Height="300"
ShowLineNumber="True"
AcceptsReturn="True"
AcceptsTab="True"/>
HandyControl RichTextBox додає:
CalendarWithClock — календар з годинником:
<hc:CalendarWithClock SelectedDateTime="{Binding SelectedDateTime, Mode=TwoWay}"/>
CalendarWithClock комбінує календар та аналоговий годинник для вибору дати та часу.
Clock — аналоговий годинник:
<hc:Clock Width="200"
Height="200"
ShowSecondHand="True"
DisplayTime="{Binding CurrentTime}"/>
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"/>
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, тощо).
HandyControl надає набір attached properties, behaviors, converters та extensions для спрощення розробки.
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"/>
DragElement — drag and drop функціональність:
<Border hc:DragElement.IsDraggable="True"
Background="LightBlue"
Width="100" Height="100">
<TextBlock Text="Перетягни мене"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
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'}"/>
Створимо 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; }
}
Створимо простий 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; }
}
Створимо 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 додатку на 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"
Типові проблеми при міграції:
BasedOn для власних стилів.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 ...}")Інтеграція з 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, якщо:
Для Windows 11 додатків: WPF UI — найкращий вибір для нативного вигляду
Для Windows 10 додатків: ModernWpf — точна реалізація Fluent Design
Для складних enterprise додатків: HandyControl — найбільша колекція контролів
Для Metro стилю: MahApps.Metro — зріла бібліотека з великою спільнотою
Для простих додатків: Стандартний WPF або WPF UI з мінімальним набором контролів
Які контроли важкі:
Як оптимізувати:
<hc:ListBox VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
ItemsSource="{Binding LargeCollection}"/>
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.CreateOptions = BitmapCreateOptions.DelayCreation;
bitmap.UriSource = new Uri(imagePath);
bitmap.EndInit();
Вимкніть анімації для контролів, які не видимі користувачу.
Замість завантаження всіх даних одразу, використовуйте пагінацію.
Організація тем:
Створіть окрему папку 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 підтримує тільки англійську та китайську мови. Для додавання власної мови:
DynamicResource для всіх текстів<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>
HandyControl має обмежену підтримку accessibility. Для покращення:
AutomationProperties.Name до всіх інтерактивних елементівAutomationProperties.HelpText для додаткової інформації<hc:Button Content="Зберегти"
AutomationProperties.Name="Зберегти зміни"
AutomationProperties.HelpText="Натисніть, щоб зберегти всі зміни"/>
Проблема: Документація переважно китайською мовою.
Рішення:
Проблема: HandyControl не має чистого Fluent Design, може не підходити для Windows 11.
Рішення:
Проблема: Велика бібліотека (~5-8 MB) впливає на розмір додатку.
Рішення:
Проблема: GitHub Issues переважно китайською мовою.
Рішення:
Мета: Створити простий додаток з HandyControl та освоїти базові контроли.
Завдання:
Критерії оцінки:
Мета: Створити dashboard з картками, графіками, та таблицею даних.
Завдання:
Критерії оцінки:
Мета: Створити повноцінний admin panel з CRUD операціями, аутентифікацією, та advanced features.
Завдання:
Критерії оцінки:
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 може бути поступовою — не потрібно замінювати всі контроли одразу.
Офіційні ресурси:
Community ресурси:
Туторіали та статті:
Альтернативні бібліотеки:
Інструменти для розробки:
WPF UI — сучасна бібліотека Fluent контролів
Інтеграція WPF UI (Wpf.Ui) у додатки: сучасні контроли Windows 11, NavigationView, Mica/Acrylic ефекти, темна тема, та повна екосистема Fluent Design
Простори імен та ресурси XAML
Розбираємо xmlns-простори імен, підключення власних класів у XAML, ResourceDictionary, StaticResource vs DynamicResource та організацію ресурсів у великих проєктах.