How I did user-control on WPF (VS2019, c#)

Task: make a button control (WPF): round, with the ability to use the Path object as an icon, with the ability to use the IsChecked property, and change color schemes on hover / click.

As a result, the button will have the following appearance (the icons themselves are arbitrary):

Let’s move on to implementation. Let’s call our control VectorRoundButton, inheriting it from UserControl. The XAML markup of our control is extremely simple: a scalable Grid; an Ellipse object representing the much-desired round button and a Path object with the selected icon.

<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>

To control the appearance and state of the button, we will use the following properties:

IsCheckable – the ability to display in checkbox mode
IsChecked – in the speech of the checkbox – on / off (the button is circled)
ActiveButtonColor – the color of the active button (when the cursor is hovered over)
InactiveButtonColor – button color in normal state
button icon – button icon

 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); }
        }
     }

For the control to work correctly in the process of visual layout of our application, it is necessary to implement data binding through the appropriate 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));

The described attached properties contain event handlers for changing them, related to selecting an icon, color, pressing a button, etc. Below are the methods that implement these handlers.

The handler for changing the icon of our button:

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;
  }
}

Button color change handlers:

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;
  }
}

Handlers for changing the state on/off for a button in the checkbox mode, as well as enabling/disabling this mode:

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;
  }
}

It remains quite a bit – to implement the reaction of the button to the movement of the mouse through this control, as well as the event of pressing the left mouse button:

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;
  }
}

As a result, we have the following user control code:

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;
            }
        }
    }

In fact, that’s all =) I understand that everything written is simple to the point of banal and is hardly of interest to serious developers, especially since the implementation is rather crooked, but within the framework of any educational projects, it can fit anyone.

Do not throw stones, for this I want to bow =)

Similar Posts

Leave a Reply