Всех приветствую, решил выложить свой первый пост на Хабре, не судите строго — вдруг кому-нибудь да пригодится =)
Исходная ситуация: в рамках проекта по разработке декстопного приложения под винду заказчиком было выражено фи по поводу деталей интерфейса, в частности кнопок. Возникла необходимость сделать свой контрол а-ля навигационные кнопки в браузерах.
Задача: сделать контрол кнопки (WPF): круглая, с возможностью использования в качестве иконки объекта Path, с возможностью использовать свойство IsChecked, и сменой цветовых схем при наведении/нажатии.
В итоге кнопка будет иметь следующий внешний вид (иконки само-собой произвольные):

Переходим к реализации. Назовем наш контрол VectorRoundButton, наследуя его от UserControl. XAML разметка нашего контрола предельно проста: масштабируемый Grid; объект Ellipse, символизирующий столь желанную круглую кнопку и объект Path с выбранной иконкой.
<UserControl x:Class="UserControls.VectorRoundButton" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:UserControls" mc:Ignorable="d" d:DesignHeight="50" d:DesignWidth="50" Loaded="UserControl_Loaded" MouseEnter="UserControl_MouseEnter" MouseLeave="UserControl_MouseLeave" MouseLeftButtonDown="UserControl_MouseLeftButtonDown" MouseLeftButtonUp="UserControl_MouseLeftButtonUp"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="20*"/> <RowDefinition Height="60*"/> <RowDefinition Height="20*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="20*"/> <ColumnDefinition Width="60*"/> <ColumnDefinition Width="20*"/> </Grid.ColumnDefinitions> <Ellipse x:Name="ButtonEllipse" Grid.ColumnSpan="3" Grid.RowSpan="3"/> <Path x:Name="ButtonImage" Stretch="Uniform" Grid.Row="1" Grid.Column="1" /> </Grid> </UserControl>
Для контроля внешнего вида и состояния кнопки будем использовать следующие свойства:
IsCheckable — возможность отображения в режиме чек-бокса
IsChecked — в речиме чек-бокса — включено/выключено (кнопка обводится кружком)
ActiveButtonColor — цвет активной кнопки (при наведенном курсоре)
InactiveButtonColor — цвет кнопки в нормальном состоянии
ButtonIcon — иконка кнопки
public partial class VectorRoundButton : UserControl { public bool IsCheckable { get { return (bool)GetValue(IsCheckableProperty); } set { SetValue(IsCheckableProperty, value); } } public bool IsChecked { get { return (bool)GetValue(IsCheckedProperty); } set { SetValue(IsCheckedProperty, value); } } public Brush ActiveButtonColor { get { return (Brush)GetValue(ActiveButtonColorProperty); } set { SetValue(ActiveButtonColorProperty, value); } } public Brush InactiveButtonColor { get { return (Brush)GetValue(InactiveButtonColorProperty); } set { SetValue(InactiveButtonColorProperty, value); } } public Path ButtonIcon { get { return (Path)GetValue(ButtonIconProperty); } set { SetValue(ButtonIconProperty, value); } } }
Для корректной работы контрола в процессе визуальной верстки нашего приложения необходимо реализовать привязку данных через соответствующие DependencyProperty:
public static readonly DependencyProperty IsCheckableProperty = DependencyProperty.Register( "IsCheckable", typeof(bool), typeof(VectorRoundButton), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender, IsCheckablePropertChanged)); public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register( "IsChecked", typeof(bool), typeof(VectorRoundButton), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender, IsCkeckedPropertChanged)); public static readonly DependencyProperty InactiveButtonColorProperty = DependencyProperty.Register( "InactiveButtonColor", typeof(Brush), typeof(VectorRoundButton), new FrameworkPropertyMetadata(System.Windows.SystemColors.ControlBrush, FrameworkPropertyMetadataOptions.AffectsRender, InactiveButtonColorPropertyChanged)); public static readonly DependencyProperty ActiveButtonColorProperty = DependencyProperty.Register( "ActiveButtonColor", typeof(Brush), typeof(VectorRoundButton), new FrameworkPropertyMetadata(System.Windows.SystemColors.ControlDarkBrush, FrameworkPropertyMetadataOptions.AffectsRender, ActiveButtonColorPropertyChanged)); public static readonly DependencyProperty ButtonIconProperty = DependencyProperty.Register( "ButtonIcon", typeof(Path), typeof(VectorRoundButton), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, ButtonIconPropertyChanged));
В описанных присоединенных свойствах указаны обработчики событий на их изменение, связанные с выбором иконки, цвета, нажатием на кнопку и т.д. Ниже приводятся методы, реализующие данные обработчики.
Обработчик изменения иконки нашей кнопки:
private static void ButtonIconPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { if (source is VectorRoundButton) { VectorRoundButton control = source as VectorRoundButton; control.ButtonIcon.Data = (e.NewValue as Path)?.Data; control.ButtonIcon.Fill = (e.NewValue as Path)?.Fill; control.ButtonImage.Data = control.ButtonIcon.Data; control.ButtonImage.Fill = control.ButtonIcon.Fill; } }
Обработчики изменения цветов кнопки:
private static void ActiveButtonColorPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { if (source is VectorRoundButton) { VectorRoundButton control = source as VectorRoundButton; control.ActiveButtonColor = (Brush)e.NewValue; } } private static void InactiveButtonColorPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { if (source is VectorRoundButton) { VectorRoundButton control = source as VectorRoundButton; control.InactiveButtonColor = (Brush)e.NewValue; control.ButtonEllipse.Fill = (Brush)e.NewValue; } }
Обработчики изменения состояния включено/выключено для кнопки в режиме чек-бокс, а также включения/выключения данного режима:
private static void IsCkeckedPropertChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { if (source is VectorRoundButton) { VectorRoundButton control = source as VectorRoundButton; if (control.IsCheckable) { control.IsChecked = (bool)e.NewValue; if (control.IsChecked) { control.ButtonEllipse.Stroke = System.Windows.SystemColors.ControlDarkBrush; control.ButtonEllipse.StrokeThickness = 2; } else { control.ButtonEllipse.Stroke = null; control.ButtonEllipse.StrokeThickness = 1; } } } } private static void IsCheckablePropertChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { if (source is VectorRoundButton) { VectorRoundButton control = source as VectorRoundButton; control.IsCheckable = (bool)e.NewValue; } }
Осталось совсем немного — реализовать реакцию кнопки на перемещение мышки через данный контрол, а также событие нажатия левой кнопки мыши:
private void UserControl_MouseEnter(object sender, MouseEventArgs e) { ButtonEllipse.Fill = ActiveButtonColor; } private void UserControl_MouseLeave(object sender, MouseEventArgs e) { ButtonEllipse.Fill = InactiveButtonColor; if (!IsChecked) ButtonEllipse.Stroke = null; } private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { ButtonEllipse.Stroke = System.Windows.SystemColors.ActiveCaptionBrush; } private void UserControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { ButtonEllipse.Fill = ActiveButtonColor; ButtonEllipse.Stroke = null; if (IsCheckable) { IsChecked = !IsChecked; } }
В итоге, имеем следующий код пользовательского контрола:
public partial class VectorRoundButton : UserControl { public bool IsCheckable { get { return (bool)GetValue(IsCheckableProperty); } set { SetValue(IsCheckableProperty, value); } } public bool IsChecked { get { return (bool)GetValue(IsCheckedProperty); } set { SetValue(IsCheckedProperty, value); } } public Brush ActiveButtonColor { get { return (Brush)GetValue(ActiveButtonColorProperty); } set { SetValue(ActiveButtonColorProperty, value); } } public Brush InactiveButtonColor { get { return (Brush)GetValue(InactiveButtonColorProperty); } set { SetValue(InactiveButtonColorProperty, value); } } public Path ButtonIcon { get { return (Path)GetValue(ButtonIconProperty); } set { SetValue(ButtonIconProperty, value); } } public static readonly DependencyProperty IsCheckableProperty = DependencyProperty.Register( "IsCheckable", typeof(bool), typeof(VectorRoundButton), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender, IsCheckablePropertChanged)); public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register( "IsChecked", typeof(bool), typeof(VectorRoundButton), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender, IsCkeckedPropertChanged)); public static readonly DependencyProperty InactiveButtonColorProperty = DependencyProperty.Register( "InactiveButtonColor", typeof(Brush), typeof(VectorRoundButton), new FrameworkPropertyMetadata(System.Windows.SystemColors.ControlBrush, FrameworkPropertyMetadataOptions.AffectsRender, InactiveButtonColorPropertyChanged)); public static readonly DependencyProperty ActiveButtonColorProperty = DependencyProperty.Register( "ActiveButtonColor", typeof(Brush), typeof(VectorRoundButton), new FrameworkPropertyMetadata(System.Windows.SystemColors.ControlDarkBrush, FrameworkPropertyMetadataOptions.AffectsRender, ActiveButtonColorPropertyChanged)); public static readonly DependencyProperty ButtonIconProperty = DependencyProperty.Register( "ButtonIcon", typeof(Path), typeof(VectorRoundButton), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, ButtonIconPropertyChanged)); private static void ButtonIconPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { if (source is VectorRoundButton) { VectorRoundButton control = source as VectorRoundButton; control.ButtonIcon.Data = (e.NewValue as Path)?.Data; control.ButtonIcon.Fill = (e.NewValue as Path)?.Fill; control.ButtonImage.Data = control.ButtonIcon.Data; control.ButtonImage.Fill = control.ButtonIcon.Fill; } } private static void ActiveButtonColorPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { if (source is VectorRoundButton) { VectorRoundButton control = source as VectorRoundButton; control.ActiveButtonColor = (Brush)e.NewValue; } } private static void InactiveButtonColorPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { if (source is VectorRoundButton) { VectorRoundButton control = source as VectorRoundButton; control.InactiveButtonColor = (Brush)e.NewValue; control.ButtonEllipse.Fill = (Brush)e.NewValue; } } private static void IsCkeckedPropertChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { if (source is VectorRoundButton) { VectorRoundButton control = source as VectorRoundButton; if (control.IsCheckable) { control.IsChecked = (bool)e.NewValue; if (control.IsChecked) { control.ButtonEllipse.Stroke = System.Windows.SystemColors.ControlDarkBrush; control.ButtonEllipse.StrokeThickness = 2; } else { control.ButtonEllipse.Stroke = null; control.ButtonEllipse.StrokeThickness = 1; } } } } private static void IsCheckablePropertChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { if (source is VectorRoundButton) { VectorRoundButton control = source as VectorRoundButton; control.IsCheckable = (bool)e.NewValue; } } public VectorRoundButton() { InitializeComponent(); } private void UserControl_Loaded(object sender, RoutedEventArgs e) { ButtonImage.Fill = ButtonIcon?.Fill; ButtonImage.Data = ButtonIcon?.Data; ButtonEllipse.Fill = InactiveButtonColor; } private void UserControl_MouseEnter(object sender, MouseEventArgs e) { ButtonEllipse.Fill = ActiveButtonColor; } private void UserControl_MouseLeave(object sender, MouseEventArgs e) { ButtonEllipse.Fill = InactiveButtonColor; if (!IsChecked) ButtonEllipse.Stroke = null; } private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { ButtonEllipse.Stroke = System.Windows.SystemColors.ActiveCaptionBrush; } private void UserControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { ButtonEllipse.Fill = ActiveButtonColor; ButtonEllipse.Stroke = null; if (IsCheckable) { IsChecked = !IsChecked; } } }
Во собственно и все =) Понимаю, что все написанное до банального просто и вряд ли представляет интерес для серьезных разработчиков, тем более что реализация довольно кривая, но в рамках каких-либо учебных проектов может кому и сгодится.
Камнями не кидайтесь, за сим хочу раскланяться =)
ссылка на оригинал статьи https://habr.com/ru/post/645791/
Добавить комментарий