Как я делал user-control на WPF (VS2019, c#)

от автора

Всех приветствую, решил выложить свой первый пост на Хабре, не судите строго — вдруг кому-нибудь да пригодится =)

Исходная ситуация: в рамках проекта по разработке декстопного приложения под винду заказчиком было выражено фи по поводу деталей интерфейса, в частности кнопок. Возникла необходимость сделать свой контрол а-ля навигационные кнопки в браузерах.

Задача: сделать контрол кнопки (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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *