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