How to: Create a New Control by Creating a ControlTemplate

Microsoft Silverlight will reach end of support after October 2021. Learn more.

The following example creates a custom control, NumericUpDown, that displays a numeric value that a user can increase or decrease by clicking the control's buttons. The example is described in detail in Creating a New Control by Creating a ControlTemplate.

Run this sample

Example

<!--This is the contents of the themes/generic.xaml file.-->
<ResourceDictionary  
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:src="clr-namespace:NumericUpDownCustomControl;assembly=NumericUpDownCustomControl"
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
    >
  <Style TargetType="src:NumericUpDown">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="src:NumericUpDown">
          <Grid  Margin="3" 
                Background="{TemplateBinding Background}">

            <vsm:VisualStateManager.VisualStateGroups>


              <vsm:VisualStateGroup x:Name="ValueStates">

                <!--Make the Value property red when it is negative.-->
                <vsm:VisualState x:Name="Negative">
                  <Storyboard>
                    <ColorAnimation To="Red"
                                    Storyboard.TargetName="TextBlock" 
                                    Storyboard.TargetProperty="(Foreground).(SolidBruch.Color)"/>
                  </Storyboard>

                </vsm:VisualState>

                <!--Return the control to its initial state by
                    return the TextBlock's Foreground to its 
                    original color.-->
                <vsm:VisualState x:Name="Positive"/>
              </vsm:VisualStateGroup>

              <vsm:VisualStateGroup x:Name="FocusStates">

                <!--Add a focus rectangle to highlight the entire control
                    when it has focus.-->
                <vsm:VisualState x:Name="Focused">
                  <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisual" 
                                                   Storyboard.TargetProperty="Visibility" Duration="0">
                      <DiscreteObjectKeyFrame KeyTime="0">
                        <DiscreteObjectKeyFrame.Value>
                          <Visibility>Visible</Visibility>
                        </DiscreteObjectKeyFrame.Value>
                      </DiscreteObjectKeyFrame>
                    </ObjectAnimationUsingKeyFrames>
                  </Storyboard>
                </vsm:VisualState>

                <!--Return the control to its initial state by
                    hiding the focus rectangle.-->
                <vsm:VisualState x:Name="Unfocused"/>
              </vsm:VisualStateGroup>

            </vsm:VisualStateManager.VisualStateGroups>

            <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">
                <TextBlock x:Name="TextBlock" TextAlignment="Center" Padding="5"
                           Foreground="{TemplateBinding Foreground}"/>

              </Border>

              <RepeatButton Content="Up" Margin="2,5,5,0"
                          x:Name="UpButton"
                          Grid.Column="1" Grid.Row="0"/>
              <RepeatButton Content="Down" Margin="2,0,5,5"
                          x:Name="DownButton"
                          Grid.Column="1" Grid.Row="1"/>

              <Rectangle Name="FocusVisual" Grid.ColumnSpan="2" Grid.RowSpan="2" 
                       Stroke="Black" StrokeThickness="1"  
                       Visibility="Collapsed"/>
            </Grid>

          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>
Imports System
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Controls.Primitives
Imports System.Windows.Input

<TemplatePart(Name:="TextElement", Type:=GetType(TextBlock))> _
<TemplatePart(Name:="UpButtonElement", Type:=GetType(RepeatButton))> _
<TemplatePart(Name:="DownButtonElement", Type:=GetType(RepeatButton))> _
<TemplateVisualState(Name:="Positive", GroupName:="ValueStates")> _
<TemplateVisualState(Name:="Negative", GroupName:="ValueStates")> _
<TemplateVisualState(Name:="Focused", GroupName:="FocusedStates")> _
<TemplateVisualState(Name:="Unfocused", GroupName:="FocusedStates")> _
Public Class NumericUpDown
    Inherits Control
    Public Sub New()
        DefaultStyleKey = GetType(NumericUpDown)
        Me.IsTabStop = True
    End Sub

    Public Shared ReadOnly ValueProperty As DependencyProperty = _
        DependencyProperty.Register("Value", GetType(Integer), _
            GetType(NumericUpDown), _
            New PropertyMetadata(New PropertyChangedCallback(AddressOf ValueChangedCallback)))


    Private Shared Sub ValueChangedCallback(ByVal obj As DependencyObject, _
                        ByVal args As DependencyPropertyChangedEventArgs)

        Dim ctl As NumericUpDown = DirectCast(obj, NumericUpDown)
        Dim newValue As Integer = args.NewValue

        ' Update the TextElement to the new value. 
        If ctl.TextElement IsNot Nothing Then
            ctl.TextElement.Text = newValue.ToString()
        End If

        ' Call UpdateStates because the Value might have caused the 
        ' control to change ValueStates. 
        ctl.UpdateStates(True)

        ' Raise the ValueChanged event so applications can be alerted 
        ' when Value changes. 
        Dim e As New ValueChangedEventArgs(newValue)
        ctl.OnValueChanged(e)
    End Sub


    Public Event ValueChanged As ValueChangedEventHandler

    Protected Overridable Sub OnValueChanged(ByVal e As ValueChangedEventArgs)

        RaiseEvent ValueChanged(Me, e)

    End Sub

    Private Sub UpdateStates(ByVal useTransitions As Boolean)
        If Value >= 0 Then
            VisualStateManager.GoToState(Me, "Positive", useTransitions)
        Else
            VisualStateManager.GoToState(Me, "Negative", useTransitions)
        End If

        If isFocused Then
            VisualStateManager.GoToState(Me, "Focused", useTransitions)
        Else
            VisualStateManager.GoToState(Me, "Unfocused", useTransitions)
        End If

    End Sub

    Public Property Value() As Integer
        Get
            Return CInt(GetValue(ValueProperty))
        End Get

        Set(ByVal value As Integer)

            SetValue(ValueProperty, value)
        End Set
    End Property


    Public Overloads Overrides Sub OnApplyTemplate()
        UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
        DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)
        TextElement = TryCast(GetTemplateChild("TextBlock"), TextBlock)

        UpdateStates(False)
    End Sub

    Private m_textElement As TextBlock

    Private Property TextElement() As TextBlock
        Get
            Return m_textElement
        End Get

        Set(ByVal txtBlock As TextBlock)
            m_textElement = txtBlock

            If m_textElement IsNot Nothing Then
                m_textElement.Text = Value.ToString()
                m_textElement.IsHitTestVisible = True
            End If
        End Set
    End Property


    Private m_downButtonElement As RepeatButton

    Private Property DownButtonElement() As RepeatButton
        Get
            Return m_downButtonElement
        End Get

        Set(ByVal repeatBtn As RepeatButton)
            If m_downButtonElement IsNot Nothing Then
                RemoveHandler m_downButtonElement.Click, AddressOf downButtonElement_Click
            End If
            m_downButtonElement = repeatBtn

            If m_downButtonElement IsNot Nothing Then
                AddHandler m_downButtonElement.Click, AddressOf downButtonElement_Click
            End If
        End Set
    End Property

    Private Sub downButtonElement_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Value -= 1
    End Sub

    Private m_upButtonElement As RepeatButton

    Private Property UpButtonElement() As RepeatButton
        Get
            Return m_upButtonElement
        End Get

        Set(ByVal repeatBtn As RepeatButton)
            If m_upButtonElement IsNot Nothing Then
                RemoveHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
            End If
            m_upButtonElement = repeatBtn

            If m_upButtonElement IsNot Nothing Then
                AddHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
            End If
        End Set
    End Property

    Private Sub upButtonElement_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Value += 1
    End Sub

    Protected Overloads Overrides Sub OnMouseLeftButtonDown(ByVal e As MouseButtonEventArgs)
        MyBase.OnMouseLeftButtonDown(e)
        Focus()
    End Sub

    Private isFocused As Boolean

    Protected Overloads Overrides Sub OnGotFocus(ByVal e As RoutedEventArgs)
        MyBase.OnGotFocus(e)
        isFocused = True
        UpdateStates(True)
    End Sub

    Protected Overloads Overrides Sub OnLostFocus(ByVal e As RoutedEventArgs)
        MyBase.OnLostFocus(e)
        isFocused = False
        UpdateStates(True)
    End Sub

End Class


Public Delegate Sub ValueChangedEventHandler(ByVal sender As Object, ByVal e As ValueChangedEventArgs)

Public Class ValueChangedEventArgs
    Inherits EventArgs
    Private _value As Integer

    Public Sub New(ByVal num As Integer)
        _value = num
    End Sub

    Public ReadOnly Property Value() As Integer
        Get
            Return _value
        End Get
    End Property
End Class
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace NumericUpDownCustomControl
{
    [TemplatePart(Name = "TextElement", Type = typeof(TextBlock))]
    [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(int), typeof(NumericUpDown),
                            new PropertyMetadata(new PropertyChangedCallback(ValueChangedCallback)));


        private static void ValueChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            NumericUpDown ctl = (NumericUpDown)obj;
            int newValue = (int)args.NewValue;

            // Update the TextElement to the new value.
            if (ctl.TextElement != null)
            {
                ctl.TextElement.Text = newValue.ToString();
            }

            // Call UpdateStates because the Value might have caused the
            // control to change ValueStates.
            ctl.UpdateStates(true);

            // Raise the ValueChanged event so applications can be alerted
            // when Value changes.
            ValueChangedEventArgs e = new ValueChangedEventArgs(newValue);
            ctl.OnValueChanged(e);
        }


        public event ValueChangedEventHandler ValueChanged;

        protected virtual void OnValueChanged(ValueChangedEventArgs e)
        {
            ValueChangedEventHandler handler = ValueChanged;

            if (handler != null)
            {
                handler(this, 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 int Value
        {
            get
            {
                return (int)GetValue(ValueProperty);
            }

            set
            {
                SetValue(ValueProperty, value);

            }
        }


        public override void OnApplyTemplate()
        {
            UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
            DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
            TextElement = GetTemplateChild("TextBlock") as TextBlock;

            UpdateStates(false);
        }

        private TextBlock textElement;

        private TextBlock TextElement
        {
            get { return textElement; }

            set
            {
                textElement = value;

                if (textElement != null)
                {
                    textElement.Text = Value.ToString();
                    textElement.IsHitTestVisible = true;
                }
            }

        }

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

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

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);
            Focus();
        }

        bool isFocused;

        protected override void OnGotFocus(RoutedEventArgs e)
        {
            base.OnGotFocus(e);
            isFocused = true;
            UpdateStates(true);
        }

        protected override void OnLostFocus(RoutedEventArgs e)
        {
            base.OnLostFocus(e);
            isFocused = false;
            UpdateStates(true);
        }

    }


    public delegate void ValueChangedEventHandler(object sender, ValueChangedEventArgs e);

    public class ValueChangedEventArgs : EventArgs
    {
        private int _value;

        public ValueChangedEventArgs(int num)
        {
            _value = num;
        }

        public int Value
        {
            get { return _value; }
        }
    }
}

See Also

Other Resources