Как я делал user-control на WPF (VS2019, c#)
Всех приветствую, решил выложить свой первый пост на Хабре, не судите строго — вдруг кому-нибудь да пригодится =)
Исходная ситуация: в рамках проекта по разработке декстопного приложения под винду заказчиком было выражено фи по поводу деталей интерфейса, в частности кнопок. Возникла необходимость сделать свой контрол а-ля навигационные кнопки в браузерах.
Задача: сделать контрол кнопки (WPF): круглая, с возможностью использования в качестве иконки объекта Path, с возможностью использовать свойство IsChecked, и сменой цветовых схем при наведении/нажатии.
В итоге кнопка будет иметь следующий внешний вид (иконки само-собой произвольные):
Переходим к реализации. Назовем наш контрол VectorRoundButton, наследуя его от UserControl. XAML разметка нашего контрола предельно проста: масштабируемый Grid; объект Ellipse, символизирующий столь желанную круглую кнопку и объект Path с выбранной иконкой.
Для контроля внешнего вида и состояния кнопки будем использовать следующие свойства:
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;
}
}
}
Во собственно и все =) Понимаю, что все написанное до банального просто и вряд ли представляет интерес для серьезных разработчиков, тем более что реализация довольно кривая, но в рамках каких-либо учебных проектов может кому и сгодится.
Камнями не кидайтесь, за сим хочу раскланяться =)