Converting UserControl and Code-Behind to Style and Dependency Property

Jin Ka 26 Reputation points
2023-12-16T12:06:47.3133333+00:00

How can I convert a customNumericUpDown created using a UserControl and Code-Behind to a ResourceDictionary and a C# class where all the DependencyProperties are defined? I want to copy and paste the ResourceDictionary into Themes.Generic.xaml. While I know how to do this for the Properties, what about the Methods? Here's the link to my customControl Github repository for reference: https://github.com/MeshkaM/customNUD.

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,675 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,268 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
765 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Hui Liu-MSFT 38,331 Reputation points Microsoft Vendor
    2023-12-18T08:26:58.81+00:00

    Hi,@Jin Ka . Welcome to Microsoft Q&A.

    Here is a complete example of a NumericUpDown custom control that uses a ResourceDictionary for styling and defines Dependency Properties. The control includes event handling for value changes. You could try to refer to it.

    I removed the code Style="{StaticResource MaterialDesignFloatingHintTextBox}" here. Since I will get an error when adding this code, I haven't determined the reason yet.

    <Style TargetType="{x:Type local:NumericUpDown}">
    		<Setter Property="Template">
    			<Setter.Value>
    				<ControlTemplate TargetType="local:NumericUpDown">
    					<Grid  Margin="3"
    						  Background="{TemplateBinding Background}">
    						<Grid>
    							<Grid.RowDefinitions>
    								<RowDefinition/>
    								<RowDefinition/>
    							</Grid.RowDefinitions>
    							<Grid.ColumnDefinitions>
    								<ColumnDefinition/>
    								<ColumnDefinition/>
    							</Grid.ColumnDefinitions>
    
    							<Border BorderThickness="1" BorderBrush="Gray"
    							  Margin="7,2,2,2" Grid.RowSpan="2"
    							  Background="#E0FFFFFF"
    							  VerticalAlignment="Center"
    							  HorizontalAlignment="Stretch">
    								
    								<TextBox  Name="TextBlock"		    FontSize="20"	     HorizontalContentAlignment="Right"
    										  materialDesign:HintAssist.Hint="Height"
                    materialDesign:HintAssist.FontFamily="Century Gothic"
                    materialDesign:HintAssist.IsFloating="False"
                								
    								  Width="60" TextAlignment="Right" Padding="5"
    								  Text="{Binding RelativeSource={RelativeSource FindAncestor, 
                                     AncestorType={x:Type local:NumericUpDown}}, 
                                     Path=Value}"/>
    							</Border>
    							<StackPanel Grid.Column="1" Orientation="Vertical">
    								<RepeatButton Content="Up" Margin="2,5,5,0"
    				Name="UpButton"
    				Grid.Column="1" Grid.Row="0">
    									<RepeatButton.Template>
    										<ControlTemplate TargetType="RepeatButton">
    											<materialDesign:PackIcon Kind="Triangle"
    																	 Height="10"
    																	 Width="10"
    																	 Margin="0,3,0,0"
    																	 Foreground="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}"/>
    											<ControlTemplate.Triggers>
    												<Trigger Property="IsMouseOver" Value="True">
    													<Setter Property="Foreground" Value="Blue" />
    												</Trigger>
    												<Trigger Property="IsPressed" Value="True">
    													<Setter Property="Foreground" Value="Red" />
    												</Trigger>
    												<Trigger Property="IsEnabled" Value="False">
    													<Setter Property="Foreground" Value="Gray" />
    												</Trigger>
    											</ControlTemplate.Triggers>
    										</ControlTemplate>
    									</RepeatButton.Template>
    									<RepeatButton.Style>
    										<Style TargetType="RepeatButton">
    											<Setter Property="Foreground" Value="Lime" />
    										</Style>
    									</RepeatButton.Style>
    								</RepeatButton>
    								<RepeatButton Content="Down" Margin="2,0,5,5"
    								  Name="DownButton"
    								  Grid.Column="1" Grid.Row="1">
    									<RepeatButton.Template>
    										<ControlTemplate TargetType="RepeatButton">
    											<materialDesign:PackIcon Kind="TriangleDown"
    																	 Height="10"
    																	 Width="10"
    																	 Margin="0,0,0,0"
    																	 Foreground="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}" />
    											<ControlTemplate.Triggers>
    												<Trigger Property="IsMouseOver" Value="True">
    													<Setter Property="Foreground" Value="Blue" />
    												</Trigger>
    												<Trigger Property="IsPressed" Value="True">
    													<Setter Property="Foreground" Value="Red" />
    												</Trigger>
    												<Trigger Property="IsEnabled" Value="False">
    													<Setter Property="Foreground" Value="Gray" />
    												</Trigger>
    											</ControlTemplate.Triggers>
    										</ControlTemplate>
    									</RepeatButton.Template>
    									<RepeatButton.Style>
    										<Style TargetType="RepeatButton">
    											<Setter Property="Foreground" Value="Lime" />
    										</Style>
    									</RepeatButton.Style>
    								</RepeatButton>
    							</StackPanel>
    							
    						</Grid>
    
    					</Grid>
    				</ControlTemplate>
    			</Setter.Value>
    		</Setter>
    	</Style>
    

    NumericUpDown.cs:

    
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Input;
    
    namespace DecimalUpDownCustomizationTEST
    {
       /// <summary>
      /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
      ///
      /// Step 1a) Using this custom control in a XAML file that exists in the current project.
      /// Add this XmlNamespace attribute to the root element of the markup file where it is 
      /// to be used:
      ///
      ///     xmlns:MyNamespace="clr-namespace:DecimalUpDownCustomizationTEST"
      ///
      ///
      /// Step 1b) Using this custom control in a XAML file that exists in a different project.
      /// Add this XmlNamespace attribute to the root element of the markup file where it is 
      /// to be used:
      ///
      ///     xmlns:MyNamespace="clr-namespace:DecimalUpDownCustomizationTEST;assembly=DecimalUpDownCustomizationTEST"
      ///
      /// You will also need to add a project reference from the project where the XAML file lives
      /// to this project and Rebuild to avoid compilation errors:
      ///
      ///     Right click on the target project in the Solution Explorer and
      ///     "Add Reference"->"Projects"->[Browse to and select this project]
      ///
      ///
      /// Step 2)
      /// Go ahead and use your control in the XAML file.
      ///
      ///     <MyNamespace:NumericUpDown/>
      ///
      /// </summary>
    
      [TemplatePart(Name = "UpButtonElement", Type = typeof(RepeatButton))]
      [TemplatePart(Name = "DownButtonElement", Type = typeof(RepeatButton))]
      [TemplateVisualState(Name = "Positive", GroupName = "ValueStates")]
      [TemplateVisualState(Name = "Negative", GroupName = "ValueStates")]
      [TemplateVisualState(Name = "Focused", GroupName = "FocusedStates")]
      [TemplateVisualState(Name = "Unfocused", GroupName = "FocusedStates")]
      public class NumericUpDown : Control
      {
        public NumericUpDown()
        {
          DefaultStyleKey = typeof(NumericUpDown);
          this.IsTabStop = true;
        }
    
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register(
                "Value", typeof(double), typeof(NumericUpDown),
                new PropertyMetadata(
                    new PropertyChangedCallback(ValueChangedCallback)));
    
        public double Value
        {
          get
          {
            return (double)GetValue(ValueProperty);
          }
    
          set
          {
            SetValue(ValueProperty, value);
          }
        }
    
        private static void ValueChangedCallback(DependencyObject obj,
            DependencyPropertyChangedEventArgs args)
        {
          NumericUpDown ctl = (NumericUpDown)obj;
          double newValue = (double)args.NewValue;
    
          ctl.UpdateStates(true);
    
          ctl.OnValueChanged(
              new ValueChangedEventArgs(NumericUpDown.ValueChangedEvent,
                  newValue));
        }
    
        public static readonly RoutedEvent ValueChangedEvent =
            EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Direct,
                          typeof(ValueChangedEventHandler), typeof(NumericUpDown));
    
        public event ValueChangedEventHandler ValueChanged
        {
          add { AddHandler(ValueChangedEvent, value); }
          remove { RemoveHandler(ValueChangedEvent, value); }
        }
    
        protected virtual void OnValueChanged(ValueChangedEventArgs e)
        {
          RaiseEvent(e);
        }
    
        private void UpdateStates(bool useTransitions)
        {
          if (Value >= 0)
          {
            VisualStateManager.GoToState(this, "Positive", useTransitions);
          }
          else
          {
            VisualStateManager.GoToState(this, "Negative", useTransitions);
          }
    
          if (IsFocused)
          {
            VisualStateManager.GoToState(this, "Focused", useTransitions);
          }
          else
          {
            VisualStateManager.GoToState(this, "Unfocused", useTransitions);
          }
        }
    
        public override void OnApplyTemplate()
        {
          UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
          DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
        
          UpdateStates(false);
        }
    
        private RepeatButton downButtonElement;
    
        private RepeatButton DownButtonElement
        {
          get
          {
            return downButtonElement;
          }
    
          set
          {
            if (downButtonElement != null)
            {
              downButtonElement.Click -=
                  new RoutedEventHandler(downButtonElement_Click);
            }
            downButtonElement = value;
    
            if (downButtonElement != null)
            {
              downButtonElement.Click +=
                  new RoutedEventHandler(downButtonElement_Click);
            }
          }
        }
    
        void downButtonElement_Click(object sender, RoutedEventArgs e)
        {
          Value=Value-0.1;
        }
    
        private RepeatButton upButtonElement;
    
        private RepeatButton UpButtonElement
        {
          get
          {
            return upButtonElement;
          }
    
          set
          {
            if (upButtonElement != null)
            {
              upButtonElement.Click -=
                  new RoutedEventHandler(upButtonElement_Click);
            }
            upButtonElement = value;
    
            if (upButtonElement != null)
            {
              upButtonElement.Click +=
                  new RoutedEventHandler(upButtonElement_Click);
            }
          }
        }
    
        void upButtonElement_Click(object sender, RoutedEventArgs e)
        {
          Value = Value +0.1;
        }
    
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
          base.OnMouseLeftButtonDown(e);
          Focus();
        }
    
    
        protected override void OnGotFocus(RoutedEventArgs e)
        {
          base.OnGotFocus(e);
          UpdateStates(true);
        }
    
        protected override void OnLostFocus(RoutedEventArgs e)
        {
          base.OnLostFocus(e);
          UpdateStates(true);
        }
      }
    
      public delegate void ValueChangedEventHandler(object sender, ValueChangedEventArgs e);
    
      public class ValueChangedEventArgs : RoutedEventArgs
      {
        private double _value;
    
        public ValueChangedEventArgs(RoutedEvent id, double num)
        {
          _value = num;
          RoutedEvent = id;
        }
    
        public double Value
        {
          get { return _value; }
        }
      }
    
    }
    
    
    

    MainWindow.xaml:

         <local:NumericUpDown Width="200"    Value="3.5"
                                         Height="80"
                                     
                                         />
    

    The result:

    1


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.