The control's logic is responsible for changing the control's state. The following example shows that the NumericUpDown control calls the GoToState method to go into the Positive state when Value is 0 or greater, and the Negative state when Value is less than 0.
It is recommended that you do the following to maintain your control's states:
The NumericUpDown control uses its Value property to track whether it is in the Positive or Negative state. In addition to the Positive and Negative states, the NumericUpDown control defines the Focused and UnFocused states, which occur when the NumericUpDown has focus, or does not have focus, respectively. The following example shows that the NumericUpDown control defines a private property to track whether the control has focus and sets that property according to whether the control has focus.
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
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);
}
The following example shows the NumericUpDown control's helper method, UpdateStates. When Value is greater than or equal to 0, the Control is in the Positive state. When Value is less than 0, the control is in the Negative state. When isFocused is true, the control is in the Focused state; otherwise, it is in the Unfocused state.
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
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);
}
}
If you pass a state name to GoToState when the control is already in that state, GoToState does nothing, so you don't need to check for the control's current state. For example, if Value changes from one negative number to another negative number, the storyboard for the Negative state is not interrupted and the user will not see a change in the control.
The VisualStateManager uses VisualStateGroup objects to determine which state to exit when you call GoToState. The control is always in one state for each VisualStateGroup that is defined in its ControlTemplate and only leaves a state when it goes into another state from the same VisualStateGroup. For example, the ControlTemplate of the NumericUpDown control defines the Positive and Negative VisualState objects in one VisualStateGroup and the Focused and Unfocused VisualState objects in another. (You can see the Focused and Unfocused VisualState defined in How to: Create a New Control by Creating a ControlTemplate.) When the control goes from the Positive state to the Negative state, or vice versa, the control remains in either the Focused or Unfocused state.
There are three typical places where the state of a control might change:
The following examples demonstrate updating the state of the NumericUpDown control in these cases.
You should update the state of the control in the OnApplyTemplate method so that the control appears in the correct state when the ControlTemplate is applied. The following example calls UpdateStates in OnApplyTemplate to ensure that the control is in the appropriate states. For example, suppose that you create a NumericUpDown control, and then set its Foreground to green and Value to -5. If you do not call UpdateStates when the ControlTemplate is applied to the NumericUpDown control, the control is not in the Negative state and the value is green instead of red. You must call UpdateStates to put the control in the Negative state.
Public Overloads Overrides Sub OnApplyTemplate()
UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)
TextElement = TryCast(GetTemplateChild("TextBlock"), TextBlock)
UpdateStates(False)
End Sub
public override void OnApplyTemplate()
{
UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
TextElement = GetTemplateChild("TextBlock") as TextBlock;
UpdateStates(false);
}
You often need to update the states of a control when a property changes. The following example shows the entire ValueChangedCallback method. Because ValueChangedCallback is called when Value changes, the method calls UpdateStates in case Value changed from positive to negative or vice versa. It is acceptable to call UpdateStates when Value changes but remains positive or negative because in that case, the control will not change states.
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
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);
}
You might also need to update states when an event occurs. The following example shows that the NumericUpDown calls UpdateStates on the Control to handle the GotFocus event.
Protected Overloads Overrides Sub OnGotFocus(ByVal e As RoutedEventArgs)
MyBase.OnGotFocus(e)
isFocused = True
UpdateStates(True)
End Sub
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
isFocused = true;
UpdateStates(true);
}
The VisualStateManager helps you manage your control's states. By using the VisualStateManager, you ensure that your control correctly transitions between states. If you follow the recommendations described in this section for working with the VisualStateManager, your control's code will remain readable and maintainable.