Converting UserControl and Code-Behind to Style and Dependency Property

Jin Ka 31 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,783 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,995 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.
814 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Hui Liu-MSFT 48,566 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.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.