Crear un control que tiene una apariencia personalizableCreating a Control That Has a Customizable Appearance

Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) le ofrece la capacidad para crear un control cuya apariencia puede personalizarse.Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) gives you the ability to create a control whose appearance can be customized. Por ejemplo, puede cambiar la apariencia de un CheckBox más allá de qué configuración de propiedades hará mediante la creación de un nuevo ControlTemplate.For example, you can change the appearance of a CheckBox beyond what setting properties will do by creating a new ControlTemplate. La siguiente ilustración muestra un CheckBox que usa un valor predeterminado ControlTemplate y un CheckBox que usa un ControlTemplate.The following illustration shows a CheckBox that uses a default ControlTemplate and a CheckBox that uses a custom ControlTemplate.

Casilla con la plantilla de control predeterminada. un control CheckBox que usa la plantilla de control predeterminadaA checkbox with the default control template. A CheckBox that uses the default control template

Casilla con una plantilla de control personalizado. un control CheckBox que usa una plantilla de control personalizadoA checkbox with a custom control template. A CheckBox that uses a custom control template

Si sigue el modelo de Estados y elementos al crear un control, pueden personalizar la apariencia del control.If you follow the parts and states model when you create a control, your control's appearance will be customizable. Herramientas del diseñador, como Microsoft Expression Blend admiten el modelo de Estados y elementos, por lo que al seguir este modelo podrá personalizar en esos tipos de aplicaciones de su control.Designer tools such as Microsoft Expression Blend support the parts and states model, so when you follow this model your control will be customizable in those types of applications. En este tema se describe el modelo de Estados y elementos y cómo seguir al crear su propio control.This topic discusses the parts and states model and how to follow it when you create your own control. Este tema usa un ejemplo de un control personalizado, NumericUpDown, para ilustrar la filosofía de este modelo.This topic uses an example of a custom control, NumericUpDown, to illustrate the philosophy of this model. El NumericUpDown control muestra un valor numérico, que un usuario puede aumentar o disminuir haciendo clic en los botones del control.The NumericUpDown control displays a numeric value, which a user can increase or decrease by clicking on the control's buttons. La siguiente ilustración muestra el NumericUpDown control que se describe en este tema.The following illustration shows the NumericUpDown control that is discussed in this topic.

Control personalizado NumericUpDown. un control NumericUpDown personalizadoNumericUpDown custom control. A custom NumericUpDown control

Este tema contiene las siguientes secciones:This topic contains the following sections:

Requisitos previosPrerequisites

Este tema se supone que sabe cómo crear un nuevo ControlTemplate para un control existente, está familiarizado con cuáles son los elementos de un contrato de control y comprender los conceptos tratados en personalizar la apariencia de un Control existente mediante la Creación de un clase ControlTemplate.This topic assumes that you know how to create a new ControlTemplate for an existing control, are familiar with what the elements on a control contract are, and understand the concepts discussed in Customizing the Appearance of an Existing Control by Creating a ControlTemplate.

Nota

Para crear un control que puede tener su apariencia personalizado, debe crear un control que hereda de la Control clase o una de sus subclases distinto UserControl.To create a control that can have its appearance customized, you must create a control that inherits from the Control class or one of its subclasses other than UserControl. Un control que hereda de UserControl es un control que se puede crear rápidamente, pero no utiliza un ControlTemplate y no se puede personalizar su apariencia.A control that inherits from UserControl is a control that can be quickly created, but it does not use a ControlTemplate and you cannot customize its appearance.

Modelo de Estados y elementosParts and States Model

El modelo de Estados y elementos especifica cómo definir la estructura visual y el comportamiento visual de un control.The parts and states model specifies how to define the visual structure and visual behavior of a control. Para seguir el modelo de Estados y elementos, debe hacer lo siguiente:To follow the parts and states model, you should do the following:

  • Definir la estructura visual y el comportamiento visual en el ControlTemplate de un control.Define the visual structure and visual behavior in the ControlTemplate of a control.

  • Siga algunos procedimientos recomendados cuando la lógica del control interactúa con los elementos de la plantilla de control.Follow certain best practices when your control's logic interacts with parts of the control template.

  • Proporcionar un contrato de control para especificar lo que deben incluirse en el ControlTemplate.Provide a control contract to specify what should be included in the ControlTemplate.

Al definir la estructura visual y el comportamiento visual en el ControlTemplate de un control, los autores de aplicaciones pueden cambiar la estructura visual y el comportamiento visual del control creando un nuevo ControlTemplate en lugar de escribir código.When you define the visual structure and visual behavior in the ControlTemplate of a control, application authors can change the visual structure and visual behavior of your control by creating a new ControlTemplate instead of writing code. Debe proporcionar un contrato de control que le indica a aplicación autores que FrameworkElement objetos y los Estados se deben definir en el ControlTemplate.You must provide a control contract that tells application authors which FrameworkElement objects and states should be defined in the ControlTemplate. Debe seguir algunos procedimientos recomendados al interactuar con los elementos de la ControlTemplate para que el control administra correctamente incompleto ControlTemplate.You should follow some best practices when you interact with the parts in the ControlTemplate so that your control properly handles an incomplete ControlTemplate. Si sigue estos tres principios, los autores de la aplicación será capaces de crear un ControlTemplate para el control simplemente tan fácilmente como puede para los controles que se envían con WPFWPF.If you follow these three principles, application authors will be able to create a ControlTemplate for your control just as easily as they can for the controls that ship with WPFWPF. La siguiente sección explica cada una de estas recomendaciones en detalle.The following section explains each of these recommendations in detail.

Definir la estructura Visual y el comportamiento Visual de un Control en un ControlTemplateDefining the Visual Structure and Visual Behavior of a Control in a ControlTemplate

Cuando se crea el control personalizado mediante el modelo de Estados y elementos, se definen la estructura visual y el comportamiento visual en el control su ControlTemplate en lugar de en su lógica.When you create your custom control by using the parts and states model, you define the control's visual structure and visual behavior in its ControlTemplate instead of in its logic. La estructura visual de un control es la composición de FrameworkElement objetos que componen el control.The visual structure of a control is the composite of FrameworkElement objects that make up the control. El comportamiento visual es la manera en que el control aparece cuando se encuentra en un estado determinado.The visual behavior is the way the control appears when it is in a certain state. Para obtener más información acerca de cómo crear un ControlTemplate que especifica la estructura visual y el comportamiento visual de un control, vea personalizar la apariencia de un Control existente creando una clase ControlTemplate.For more information about creating a ControlTemplate that specifies the visual structure and visual behavior of a control, see Customizing the Appearance of an Existing Control by Creating a ControlTemplate.

En el ejemplo de la NumericUpDown control, la estructura visual incluye dos RepeatButton controles y un TextBlock.In the example of the NumericUpDown control, the visual structure includes two RepeatButton controls and a TextBlock. Si agrega estos controles en el código de la NumericUpDown control--en su constructor, por ejemplo, las posiciones de esos controles serían inalterables.If you add these controls in the code of the NumericUpDown control--in its constructor, for example--the positions of those controls would be unalterable. En lugar de definir la estructura visual y el comportamiento visual del control en su código, debe definirla en el ControlTemplate.Instead of defining the control's visual structure and visual behavior in its code, you should define it in the ControlTemplate. A continuación, un desarrollador de aplicaciones para personalizar la posición de los botones y TextBlock y especificar qué ocurre cuando Value es negativo porque el ControlTemplate puede reemplazarse.Then an application developer to customize the position of the buttons and TextBlock and specify what behavior occurs when Value is negative because the ControlTemplate can be replaced.

El ejemplo siguiente muestra la estructura visual de la NumericUpDown control, que incluye un RepeatButton para aumentar Value, un RepeatButton reducir Valuey un TextBlock para mostrar Value.The following example shows the visual structure of the NumericUpDown control, which includes a RepeatButton to increase Value, a RepeatButton to decrease Value, and a TextBlock to display Value.

<ControlTemplate TargetType="src: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">

        <!--Bind the TextBlock to the Value property-->
        <TextBlock Name="TextBlock"
                   Width="60" TextAlignment="Right" Padding="5"
                   Text="{Binding RelativeSource={RelativeSource FindAncestor, 
                     AncestorType={x:Type src:NumericUpDown}}, 
                     Path=Value}"/>
      </Border>

      <RepeatButton Content="Up" Margin="2,5,5,0"
        Name="UpButton"
        Grid.Column="1" Grid.Row="0"/>
      <RepeatButton Content="Down" Margin="2,0,5,5"
        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>

Un comportamiento visual de la NumericUpDown control es que el valor en una fuente de color rojo si es negativo.A visual behavior of the NumericUpDown control is that the value is in a red font if it is negative. Si cambia el Foreground de la TextBlock en el código cuando la Value es negativo, el NumericUpDown siempre se mostrará un valor negativo en rojo.If you change the Foreground of the TextBlock in code when the Value is negative, the NumericUpDown will always show a red negative value. Especificar el comportamiento visual del control en el ControlTemplate agregando VisualState objetos a la ControlTemplate.You specify the visual behavior of the control in the ControlTemplate by adding VisualState objects to the ControlTemplate. El ejemplo siguiente se muestra el VisualState de objetos para el Positive y Negative Estados.The following example shows the VisualState objects for the Positive and Negative states. Positive y Negative son mutuamente excluyente (el control siempre está en uno de los dos), por lo que en el ejemplo se coloca el VisualState objetos en una sola VisualStateGroup.Positive and Negative are mutually exclusive (the control is always in exactly one of the two), so the example puts the VisualState objects into a single VisualStateGroup. Cuando el control entra en el Negative estado, el Foreground de la TextBlock cambia a rojo.When the control goes into the Negative state, the Foreground of the TextBlock turns red. Cuando el control está en el Positive estado, el Foreground devuelve a su valor original.When the control is in the Positive state, the Foreground returns to its original value. Definir VisualState objetos en un ControlTemplate se explican con más detalle en personalizar la apariencia de un Control existente creando una clase ControlTemplate.Defining VisualState objects in a ControlTemplate is further discussed in Customizing the Appearance of an Existing Control by Creating a ControlTemplate.

Nota

Asegúrese de establecer el VisualStateGroups propiedad adjunta en la raíz FrameworkElement de la ControlTemplate.Be sure to set the VisualStateGroups attached property on the root FrameworkElement of the ControlTemplate.

<ControlTemplate TargetType="local:NumericUpDown">
  <Grid  Margin="3" 
         Background="{TemplateBinding Background}">

    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup Name="ValueStates">

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

        </VisualState>

        <!--Return the TextBlock's Foreground to its 
            original color.-->
        <VisualState Name="Positive"/>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
  </Grid>
</ControlTemplate>

Uso de elementos de ControlTemplate en el códigoUsing Parts of the ControlTemplate in Code

Un ControlTemplate autor podría omitir FrameworkElement o VisualState objetos, ya sea intencionadamente o por error, pero la lógica del control que tenga esos elementos para funcionar correctamente.A ControlTemplate author might omit FrameworkElement or VisualState objects, either purposefully or by mistake, but your control's logic might need those parts to function properly. El modelo de Estados y elementos especifica que el control debe ser resistente a un ControlTemplate que falta FrameworkElement o VisualState objetos.The parts and states model specifies that your control should be resilient to a ControlTemplate that is missing FrameworkElement or VisualState objects. El control no debería producir una excepción o un informe de un error si una FrameworkElement, VisualState, o VisualStateGroup no está presente en el ControlTemplate.Your control should not throw an exception or report an error if a FrameworkElement, VisualState, or VisualStateGroup is missing from the ControlTemplate. Esta sección describen los procedimientos recomendados para interactuar con FrameworkElement objetos y administración de Estados.This section describes the recommended practices for interacting with FrameworkElement objects and managing states.

Prever falta FrameworkElement (objetos)Anticipate Missing FrameworkElement Objects

Al definir FrameworkElement objetos en el ControlTemplate, la lógica del control podría necesitar interactuar con algunos de ellos.When you define FrameworkElement objects in the ControlTemplate, your control's logic might need to interact with some of them. Por ejemplo, el NumericUpDown control se suscribe a los botones Click eventos para aumentar o disminuir Value y establece el Text propiedad de la TextBlock a Value.For example, the NumericUpDown control subscribes to the buttons' Click event to increase or decrease Value and sets the Text property of the TextBlock to Value. Si un personalizado ControlTemplate omite el TextBlock o botones, es aceptable que el control pierde parte de su funcionalidad, pero debe asegurarse de que el control no produzca un error.If a custom ControlTemplate omits the TextBlock or buttons, it is acceptable that the control loses some of its functionality, but you should be sure that your control does not cause an error. Por ejemplo, si un ControlTemplate no contiene los botones para cambiar Value, el NumericUpDown pierde esa funcionalidad, pero una aplicación que utiliza el ControlTemplate seguirá ejecutándose.For example, if a ControlTemplate does not contain the buttons to change Value, the NumericUpDown loses that functionality, but an application that uses the ControlTemplate will continue to run.

Las siguientes prácticas se asegurará de que el control responde correctamente que faltan FrameworkElement objetos:The following practices will ensure that your control responds properly to missing FrameworkElement objects:

  1. Establecer el x:Name atributo para cada FrameworkElement que debe hacer referencia en el código.Set the x:Name attribute for each FrameworkElement that you need to reference in code.

  2. Definir las propiedades privadas de cada FrameworkElement que necesita para interactuar con.Define private properties for each FrameworkElement that you need to interact with.

  3. Suscribir y cancelar la suscripción a los eventos que controla el control de la FrameworkElement descriptor de acceso set de la propiedad.Subscribe to and unsubscribe from any events that your control handles in the FrameworkElement property's set accessor.

  4. Establecer el FrameworkElement las propiedades que definen en el paso 2 el OnApplyTemplate método.Set the FrameworkElement properties that you defined in step 2 in the OnApplyTemplate method. Esto es el más antiguo que el FrameworkElement en el ControlTemplate está disponible para el control.This is the earliest that the FrameworkElement in the ControlTemplate is available to the control. Use la x:Name de la FrameworkElement para obtenerlo desde el ControlTemplate.Use the x:Name of the FrameworkElement to get it from the ControlTemplate.

  5. Compruebe que la FrameworkElement no null antes de acceder a sus miembros.Check that the FrameworkElement is not null before accessing its members. Si es null, no se notificará un error.If it is null, do not report an error.

Los ejemplos siguientes muestran cómo el NumericUpDown control interactúa con FrameworkElement objetos de acuerdo con las recomendaciones de la lista anterior.The following examples show how the NumericUpDown control interacts with FrameworkElement objects in accordance with the recommendations in the preceding list.

En el ejemplo que define la estructura visual de la NumericUpDown en controlar la ControlTemplate, el RepeatButton que aumenta Value tiene su x:Name atributo establecido en UpButton.In the example that defines the visual structure of the NumericUpDown control in the ControlTemplate, the RepeatButton that increases Value has its x:Name attribute set to UpButton. En el ejemplo siguiente se declara una propiedad denominada UpButtonElement que representa el RepeatButton que está declarado en el ControlTemplate.The following example declares a property called UpButtonElement that represents the RepeatButton that is declared in the ControlTemplate. El set descriptor de acceso en primer lugar cancela la suscripción en el botón Click eventos si UpDownElement no null, a continuación, Establece la propiedad y, a continuación, se suscribe a la Click eventos.The set accessor first unsubscribes to the button's Click event if UpDownElement is not null, then it sets the property, and then it subscribes to the Click event. También hay una propiedad definida, pero no se muestra aquí, para los demás RepeatButton, llamado DownButtonElement.There is also a property defined, but not shown here, for the other RepeatButton, called DownButtonElement.

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);
        }
    }
}
Private m_upButtonElement As RepeatButton

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

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

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

El ejemplo siguiente se muestra el OnApplyTemplate para el NumericUpDown control.The following example shows the OnApplyTemplate for the NumericUpDown control. El ejemplo se usa el GetTemplateChild método para obtener el FrameworkElement objetos desde el ControlTemplate.The example uses the GetTemplateChild method to get the FrameworkElement objects from the ControlTemplate. Tenga en cuenta que el ejemplo protege frente a casos donde GetTemplateChild busca un FrameworkElement con el nombre especificado que no es del tipo esperado.Notice that the example guards against cases where GetTemplateChild finds a FrameworkElement with the specified name that is not of the expected type. También es una práctica recomendada para omitir los elementos que tienen especificado x:Name pero son del tipo incorrecto.It is also a best practice to ignore elements that have the specified x:Name but are of the wrong type.

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

    UpdateStates(false);
}
Public Overloads Overrides Sub OnApplyTemplate()

    UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
    DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)

    UpdateStates(False)
End Sub

Si sigue las prácticas que se muestran en los ejemplos anteriores, asegúrese de que el control continuará ejecutándose cuando la ControlTemplate falta un FrameworkElement.By following the practices that are shown in the previous examples, you ensure that your control will continue to run when the ControlTemplate is missing a FrameworkElement.

Usar VisualStateManager para administrar estadosUse the VisualStateManager to Manage States

El VisualStateManager realiza un seguimiento de los Estados de un control y ejecuta la lógica necesaria para realizar la transición entre Estados.The VisualStateManager keeps track of the states of a control and performs the logic necessary to transition between states. Al agregar VisualState objetos a la ControlTemplate, agregarlos a un VisualStateGroup y agregue el VisualStateGroup a la VisualStateGroups propiedad adjunta para que el VisualStateManager tiene acceso a ellos.When you add VisualState objects to the ControlTemplate, you add them to a VisualStateGroup and add the VisualStateGroup to the VisualStateGroups attached property so that the VisualStateManager has access to them.

En el ejemplo siguiente se repite el ejemplo anterior que muestra la VisualState objetos que corresponden a la Positive y Negative Estados del control.The following example repeats the previous example that shows the VisualState objects that correspond to the Positive and Negative states of the control. El Storyboard en el Negative VisualState activa el Foreground de la TextBlock rojo.The Storyboard in the NegativeVisualState turns the Foreground of the TextBlock red. Cuando el NumericUpDown control se encuentra en la Negative de estado, el guión gráfico en el Negative estado comienza.When the NumericUpDown control is in the Negative state, the storyboard in the Negative state begins. El Storyboard en el Negative estado se detiene cuando el control vuelve a la Positive estado.Then the Storyboard in the Negative state stops when the control returns to the Positive state. El Positive VisualState no necesita contener un Storyboard porque cuando el Storyboard para el Negative se detiene, el Foreground vuelve a su color original.The PositiveVisualState does not need to contain a Storyboard because when the Storyboard for the Negative stops, the Foreground returns to its original color.

<ControlTemplate TargetType="local:NumericUpDown">
  <Grid  Margin="3" 
         Background="{TemplateBinding Background}">

    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup Name="ValueStates">

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

        </VisualState>

        <!--Return the TextBlock's Foreground to its 
            original color.-->
        <VisualState Name="Positive"/>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
  </Grid>
</ControlTemplate>

Tenga en cuenta que el TextBlock recibe un nombre, pero la TextBlock no está en el contrato de control NumericUpDown porque la lógica del control nunca hace referencia a la TextBlock.Note that the TextBlock is given a name, but the TextBlock is not in the control contract for NumericUpDown because the control's logic never references the TextBlock. Los elementos que se hace referencia en el ControlTemplate tienen nombres, pero no es necesario formar parte del contrato de control porque un nuevo ControlTemplate para el control no deba hacer referencia a ese elemento.Elements that are referenced in the ControlTemplate have names, but do not need to be part of the control contract because a new ControlTemplate for the control might not need to reference that element. Por ejemplo, alguien que crea un nuevo ControlTemplate para NumericUpDown podría decidir no indicar que Value es negativo cambiando el Foreground.For example, someone who creates a new ControlTemplate for NumericUpDown might decide to not indicate that Value is negative by changing the Foreground. En ese caso, ni el código ni la ControlTemplate referencias el TextBlock por su nombre.In that case, neither the code nor the ControlTemplate references the TextBlock by name.

La lógica del control es responsable de cambiar el estado del control.The control's logic is responsible for changing the control's state. En el ejemplo siguiente se muestra que el NumericUpDown las llamadas de control del GoToState método para entrar en el Positive estado cuando Value es 0 o mayor y el Negative estado cuando Value es menor que 0.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.

if (Value >= 0)
{
    VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
    VisualStateManager.GoToState(this, "Negative", useTransitions);
}
If Value >= 0 Then
    VisualStateManager.GoToState(Me, "Positive", useTransitions)
Else
    VisualStateManager.GoToState(Me, "Negative", useTransitions)
End If

El GoToState método realiza la lógica necesaria para iniciar y detener los guiones gráficos de forma adecuada.The GoToState method performs the logic necessary to start and stop the storyboards appropriately. Cuando llama un control GoToState para cambiar su estado, el VisualStateManager hace lo siguiente:When a control calls GoToState to change its state, the VisualStateManager does the following:

  • Si el VisualState que el control se va a tiene un Storyboard, comienza el guión gráfico.If the VisualState that the control is going to has a Storyboard, the storyboard begins. A continuación, si la VisualState tiene que el control procede de un Storyboard, los extremos de guión gráfico.Then, if the VisualState that the control is coming from has a Storyboard, the storyboard ends.

  • Si el control ya está en el estado que se especifica, GoToState no realiza ninguna acción y devuelve true.If the control is already in the state that is specified, GoToState takes no action and returns true.

  • Si el estado especificado no existe en el ControlTemplate de control, GoToState no realiza ninguna acción y devuelve false.If state that is specified doesn't exist in the ControlTemplate of control, GoToState takes no action and returns false.

Procedimientos recomendados para trabajar con VisualStateManagerBest Practices for Working with the VisualStateManager

Se recomienda que realice lo siguiente para mantener los Estados del control:It is recommended that you do the following to maintain your control's states:

  • Usar propiedades para realizar un seguimiento de su estado.Use properties to track its state.

  • Cree un método auxiliar para realizar la transición entre Estados.Create a helper method to transition between states.

El NumericUpDown controlar usa su Value propiedad para realizar el seguimiento ya sea en el Positive o Negative estado.The NumericUpDown control uses its Value property to track whether it is in the Positive or Negative state. El NumericUpDown control también define la Focused y UnFocused indica qué pistas el IsFocused propiedad.The NumericUpDown control also defines the Focused and UnFocused states, which tracks the IsFocused property. Si utiliza los Estados que no corresponden naturalmente a una propiedad del control, puede definir una propiedad privada para realizar un seguimiento del estado.If you use states that do not naturally correspond to a property of the control, you can define a private property to track the state.

Un único método que actualiza todos los Estados centraliza las llamadas a la VisualStateManager y mantiene su código fácil de administrar.A single method that updates all the states centralizes calls to the VisualStateManager and keeps your code manageable. El ejemplo siguiente se muestra el NumericUpDown método auxiliar del control, UpdateStates.The following example shows the NumericUpDown control's helper method, UpdateStates. Cuando Value es mayor o igual a 0, el Control está en el Positive estado.When Value is greater than or equal to 0, the Control is in the Positive state. Cuando Value es menor que 0, el control está en el Negative estado.When Value is less than 0, the control is in the Negative state. Cuando IsFocused es true, el control está en el Focused estado; en caso contrario, se encuentra en la Unfocused estado.When IsFocused is true, the control is in the Focused state; otherwise, it is in the Unfocused state. El control puede llamar a UpdateStates siempre que sea necesario para cambiar su estado, independientemente de qué estado se cambia.The control can call UpdateStates whenever it needs to change its state, regardless of what state changes.

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

Si se pasa el nombre de un estado a GoToState cuando el control ya está en ese estado, GoToState no hace nada, por lo que no es necesario comprobar el estado del control actual.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. Por ejemplo, si Value cambia de un número negativo a otro número negativo, el guión gráfico para el Negative no se interrumpe el estado y el usuario no verá un cambio en el control.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.

El VisualStateManager usa VisualStateGroup objetos para determinar qué estado salir cuando se llama a GoToState.The VisualStateManager uses VisualStateGroup objects to determine which state to exit when you call GoToState. El control siempre está en un estado para cada VisualStateGroup que se define en su ControlTemplate y deja sólo un estado cuando entra en otro estado de la misma VisualStateGroup.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. Por ejemplo, el ControlTemplate de la NumericUpDown control define la Positive y Negative VisualState objetos en una VisualStateGroup y Focused y Unfocused VisualState objetos en otro.For example, the ControlTemplate of the NumericUpDown control defines the Positive and NegativeVisualState objects in one VisualStateGroup and the Focused and UnfocusedVisualState objects in another. (Puede ver el Focused y Unfocused VisualState definido en el ejemplo completo en este tema cuando el control pasa de la Positive estado el Negative estado, o viceversa, el control permanece en uno el Focused o Unfocused estado.(You can see the Focused and UnfocusedVisualState defined in the Complete Example section in this topic 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.

Hay tres lugares típicas donde puede cambiar el estado de un control:There are three typical places where the state of a control might change:

Los ejemplos siguientes muestran el estado de la actualización la NumericUpDown control en estos casos.The following examples demonstrate updating the state of the NumericUpDown control in these cases.

Debe actualizar el estado del control en el OnApplyTemplate método para que el control aparezca en el lugar correcto estado cuando el ControlTemplate se aplica.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. El ejemplo siguiente se llama UpdateStates en OnApplyTemplate para asegurarse de que el control está en los Estados adecuados.The following example calls UpdateStates in OnApplyTemplate to ensure that the control is in the appropriate states. Por ejemplo, suponga que crea un NumericUpDown y, a continuación, establezca su Foreground a verde y Value -5.For example, suppose that you create a NumericUpDown control, and then set its Foreground to green and Value to -5. Si no se llama UpdateStates cuando el ControlTemplate se aplica a la NumericUpDown control, el control no está en el Negative estado y el valor es verde en lugar de rojo.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. Debe llamar a UpdateStates para colocar el control en el Negative estado.You must call UpdateStates to put the control in the Negative state.

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

    UpdateStates(false);
}
Public Overloads Overrides Sub OnApplyTemplate()

    UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
    DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)

    UpdateStates(False)
End Sub

A menudo necesitará actualizar los Estados de un control cuando cambia una propiedad.You often need to update the states of a control when a property changes. El ejemplo siguiente muestra toda la ValueChangedCallback método.The following example shows the entire ValueChangedCallback method. Dado que ValueChangedCallback se llama cuando Value cambia, las llamadas al método UpdateStates en caso de Value cambió de positivo, negativo o viceversa.Because ValueChangedCallback is called when Value changes, the method calls UpdateStates in case Value changed from positive to negative or vice versa. Es aceptable para llamar a UpdateStates cuando Value cambia, pero sigue siendo positiva o negativa, porque en ese caso, el control no cambiará de estado.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 static void ValueChangedCallback(DependencyObject obj, 
    DependencyPropertyChangedEventArgs args)
{
    NumericUpDown ctl = (NumericUpDown)obj;
    int newValue = (int)args.NewValue;

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

    // Call OnValueChanged to raise the ValueChanged event.
    ctl.OnValueChanged(
        new ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, 
            newValue));
}
Private Shared Sub ValueChangedCallback(ByVal obj As DependencyObject,
                                        ByVal args As DependencyPropertyChangedEventArgs)

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

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

    ' Call OnValueChanged to raise the ValueChanged event.
    ctl.OnValueChanged(New ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, newValue))
End Sub

También es posible que deba actualizar los Estados cuando se produce un evento.You might also need to update states when an event occurs. El ejemplo siguiente muestra que el NumericUpDown llamadas UpdateStates en el Control para controlar la GotFocus eventos.The following example shows that the NumericUpDown calls UpdateStates on the Control to handle the GotFocus event.

protected override void OnGotFocus(RoutedEventArgs e)
{
    base.OnGotFocus(e);
    UpdateStates(true);
}
Protected Overloads Overrides Sub OnGotFocus(ByVal e As RoutedEventArgs)
    MyBase.OnGotFocus(e)
    UpdateStates(True)
End Sub

El VisualStateManager le ayuda a administrar los Estados del control.The VisualStateManager helps you manage your control's states. Mediante el uso de la VisualStateManager, asegúrese de que el control realiza las transiciones entre Estados correctamente.By using the VisualStateManager, you ensure that your control correctly transitions between states. Si sigue las recomendaciones descritas en esta sección para trabajar con el VisualStateManager, el código de control seguirá siendo legible y fácil de mantener.If you follow the recommendations described in this section for working with the VisualStateManager, your control's code will remain readable and maintainable.

Proporcionar el contrato de ControlProviding the Control Contract

Proporcionar un contrato de control para que ControlTemplate autores sepan qué incluir en la plantilla.You provide a control contract so that ControlTemplate authors will know what to put in the template. Un contrato de control tiene tres elementos:A control contract has three elements:

  • Los elementos visuales que usa la lógica del control.The visual elements that the control's logic uses.

  • Los estados del control y el grupo al que pertenece cada estado.The states of the control and the group each state belongs to.

  • Las propiedades públicas que afectan visualmente al control.The public properties that visually affect the control.

Alguien que crea un nuevo ControlTemplate necesita saber qué FrameworkElement objetos utiliza la lógica del control, es el tipo de cada objeto, y es lo que su nombre.Someone that creates a new ControlTemplate needs to know what FrameworkElement objects the control's logic uses, what type each object is, and what its name is. Un ControlTemplate autor también debe saber el nombre de cada posible estado puede estar el control y que VisualStateGroup el estado es.A ControlTemplate author also needs to know the name of each possible state the control can be in, and which VisualStateGroup the state is in.

Volver a la NumericUpDown espera que el control de ejemplo, el ControlTemplate tener lo siguiente FrameworkElement objetos:Returning to the NumericUpDown example, the control expects the ControlTemplate to have the following FrameworkElement objects:

El control puede estar en los siguientes estados:The control can be in the following states:

Para especificar qué FrameworkElement espera que el control de objetos, utilice el TemplatePartAttribute, que especifica el nombre y tipo de los elementos esperados.To specify what FrameworkElement objects the control expects, you use the TemplatePartAttribute, which specifies the name and type of the expected elements. Para especificar los posibles estados de un control, utilice el TemplateVisualStateAttribute, que especifica el nombre del estado y que VisualStateGroup que pertenece.To specify the possible states of a control, you use the TemplateVisualStateAttribute, which specifies the state's name and which VisualStateGroup it belongs to. Coloque el TemplatePartAttribute y TemplateVisualStateAttribute en la definición de clase del control.Put the TemplatePartAttribute and TemplateVisualStateAttribute on the class definition of the control.

Cualquier propiedad pública que afecta a la apariencia del control es también una parte del contrato de control.Any public property that affects the appearance of your control is also a part of the control contract.

En el ejemplo siguiente se especifica la FrameworkElement objeto y Estados para el NumericUpDown control.The following example specifies the FrameworkElement object and states for the NumericUpDown control.

[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 static readonly DependencyProperty BackgroundProperty;
    public static readonly DependencyProperty BorderBrushProperty;
    public static readonly DependencyProperty BorderThicknessProperty;
    public static readonly DependencyProperty FontFamilyProperty;
    public static readonly DependencyProperty FontSizeProperty;
    public static readonly DependencyProperty FontStretchProperty;
    public static readonly DependencyProperty FontStyleProperty;
    public static readonly DependencyProperty FontWeightProperty;
    public static readonly DependencyProperty ForegroundProperty;
    public static readonly DependencyProperty HorizontalContentAlignmentProperty;
    public static readonly DependencyProperty PaddingProperty;
    public static readonly DependencyProperty TextAlignmentProperty;
    public static readonly DependencyProperty TextDecorationsProperty;
    public static readonly DependencyProperty TextWrappingProperty;
    public static readonly DependencyProperty VerticalContentAlignmentProperty;

    public Brush Background { get; set; }
    public Brush BorderBrush { get; set; }
    public Thickness BorderThickness { get; set; }
    public FontFamily FontFamily { get; set; }
    public double FontSize { get; set; }
    public FontStretch FontStretch { get; set; }
    public FontStyle FontStyle { get; set; }
    public FontWeight FontWeight { get; set; }
    public Brush Foreground { get; set; }
    public HorizontalAlignment HorizontalContentAlignment { get; set; }
    public Thickness Padding { get; set; }
    public TextAlignment TextAlignment { get; set; }
    public TextDecorationCollection TextDecorations { get; set; }
    public TextWrapping TextWrapping { get; set; }
    public VerticalAlignment VerticalContentAlignment { get; set; }
}
<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 Shared ReadOnly BackgroundProperty As DependencyProperty
    Public Shared ReadOnly BorderBrushProperty As DependencyProperty
    Public Shared ReadOnly BorderThicknessProperty As DependencyProperty
    Public Shared ReadOnly FontFamilyProperty As DependencyProperty
    Public Shared ReadOnly FontSizeProperty As DependencyProperty
    Public Shared ReadOnly FontStretchProperty As DependencyProperty
    Public Shared ReadOnly FontStyleProperty As DependencyProperty
    Public Shared ReadOnly FontWeightProperty As DependencyProperty
    Public Shared ReadOnly ForegroundProperty As DependencyProperty
    Public Shared ReadOnly HorizontalContentAlignmentProperty As DependencyProperty
    Public Shared ReadOnly PaddingProperty As DependencyProperty
    Public Shared ReadOnly TextAlignmentProperty As DependencyProperty
    Public Shared ReadOnly TextDecorationsProperty As DependencyProperty
    Public Shared ReadOnly TextWrappingProperty As DependencyProperty
    Public Shared ReadOnly VerticalContentAlignmentProperty As DependencyProperty


    Private _Background As Brush
    Public Property Background() As Brush
        Get
            Return _Background
        End Get
        Set(ByVal value As Brush)
            _Background = value
        End Set
    End Property

    Private _BorderBrush As Brush
    Public Property BorderBrush() As Brush
        Get
            Return _BorderBrush
        End Get
        Set(ByVal value As Brush)
            _BorderBrush = value
        End Set
    End Property

    Private _BorderThickness As Thickness
    Public Property BorderThickness() As Thickness
        Get
            Return _BorderThickness
        End Get
        Set(ByVal value As Thickness)
            _BorderThickness = value
        End Set
    End Property

    Private _FontFamily As FontFamily
    Public Property FontFamily() As FontFamily
        Get
            Return _FontFamily
        End Get
        Set(ByVal value As FontFamily)
            _FontFamily = value
        End Set
    End Property

    Private _FontSize As Double
    Public Property FontSize() As Double
        Get
            Return _FontSize
        End Get
        Set(ByVal value As Double)
            _FontSize = value
        End Set
    End Property

    Private _FontStretch As FontStretch
    Public Property FontStretch() As FontStretch
        Get
            Return _FontStretch
        End Get
        Set(ByVal value As FontStretch)
            _FontStretch = value
        End Set
    End Property

    Private _FontStyle As FontStyle
    Public Property FontStyle() As FontStyle
        Get
            Return _FontStyle
        End Get
        Set(ByVal value As FontStyle)
            _FontStyle = value
        End Set
    End Property

    Private _FontWeight As FontWeight
    Public Property FontWeight() As FontWeight
        Get
            Return _FontWeight
        End Get
        Set(ByVal value As FontWeight)
            _FontWeight = value
        End Set
    End Property

    Private _Foreground As Brush
    Public Property Foreground() As Brush
        Get
            Return _Foreground
        End Get
        Set(ByVal value As Brush)
            _Foreground = value
        End Set
    End Property

    Private _HorizontalContentAlignment As HorizontalAlignment
    Public Property HorizontalContentAlignment() As HorizontalAlignment
        Get
            Return _HorizontalContentAlignment
        End Get
        Set(ByVal value As HorizontalAlignment)
            _HorizontalContentAlignment = value
        End Set
    End Property

    Private _Padding As Thickness
    Public Property Padding() As Thickness
        Get
            Return _Padding
        End Get
        Set(ByVal value As Thickness)
            _Padding = value
        End Set
    End Property

    Private _TextAlignment As TextAlignment
    Public Property TextAlignment() As TextAlignment
        Get
            Return _TextAlignment
        End Get
        Set(ByVal value As TextAlignment)
            _TextAlignment = value
        End Set
    End Property

    Private _TextDecorations As TextDecorationCollection
    Public Property TextDecorations() As TextDecorationCollection
        Get
            Return _TextDecorations
        End Get
        Set(ByVal value As TextDecorationCollection)
            _TextDecorations = value
        End Set
    End Property

    Private _TextWrapping As TextWrapping
    Public Property TextWrapping() As TextWrapping
        Get
            Return _TextWrapping
        End Get
        Set(ByVal value As TextWrapping)
            _TextWrapping = value
        End Set
    End Property

    Private _VerticalContentAlignment As VerticalAlignment
    Public Property VerticalContentAlignment() As VerticalAlignment
        Get
            Return _VerticalContentAlignment
        End Get
        Set(ByVal value As VerticalAlignment)
            _VerticalContentAlignment = value
        End Set
    End Property
End Class

Ejemplo completoComplete Example

El ejemplo siguiente es todo el ControlTemplate para el NumericUpDown control.The following example is the entire ControlTemplate for the NumericUpDown control.

<!--This is the contents of the themes/generic.xaml file.-->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:VSMCustomControl">


  <Style TargetType="{x:Type local:NumericUpDown}">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="local:NumericUpDown">
          <Grid  Margin="3" 
                Background="{TemplateBinding Background}">


            <VisualStateManager.VisualStateGroups>

              <VisualStateGroup Name="ValueStates">

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

                </VisualState>

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

              <VisualStateGroup Name="FocusStates">

                <!--Add a focus rectangle to highlight the entire control
                    when it has focus.-->
                <VisualState 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>
                </VisualState>

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

            </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">
                <!--Bind the TextBlock to the Value property-->
                <TextBlock Name="TextBlock"
                  Width="60" TextAlignment="Right" Padding="5"
                  Text="{Binding RelativeSource={RelativeSource FindAncestor, 
                                 AncestorType={x:Type local:NumericUpDown}}, 
                                 Path=Value}"/>
              </Border>

              <RepeatButton Content="Up" Margin="2,5,5,0"
                Name="UpButton"
                Grid.Column="1" Grid.Row="0"/>
              <RepeatButton Content="Down" Margin="2,0,5,5"
                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>

El ejemplo siguiente muestra la lógica para el NumericUpDown.The following example shows the logic for the NumericUpDown.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;

namespace VSMCustomControl
{
    [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)));

        public int Value
        {
            get
            {
                return (int)GetValue(ValueProperty);
            }

            set
            {
                SetValue(ValueProperty, value);

            }
        }

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

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

            // Call OnValueChanged to raise the ValueChanged event.
            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)
        {
            // Raise the ValueChanged event so applications can be alerted
            // when Value changes.
            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;
            //TextElement = GetTemplateChild("TextBlock") as TextBlock;

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

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


        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 int _value;

        public ValueChangedEventArgs(RoutedEvent id, int num)
        {
            _value = num;
            RoutedEvent = id;
        }

        public int Value
        {
            get { return _value; }
        }
    }
}
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Controls.Primitives
Imports System.Windows.Input
Imports System.Windows.Media

<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()
        DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(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)))

    Public Property Value() As Integer

        Get
            Return CInt(GetValue(ValueProperty))
        End Get

        Set(ByVal value As Integer)

            SetValue(ValueProperty, value)
        End Set
    End Property

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

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

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

        ' Call OnValueChanged to raise the ValueChanged event.
        ctl.OnValueChanged(New ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, newValue))
    End Sub

    Public Shared ReadOnly ValueChangedEvent As RoutedEvent =
        EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Direct,
                                         GetType(ValueChangedEventHandler), GetType(NumericUpDown))

    Public Custom Event ValueChanged As ValueChangedEventHandler

        AddHandler(ByVal value As ValueChangedEventHandler)
            Me.AddHandler(ValueChangedEvent, value)
        End AddHandler

        RemoveHandler(ByVal value As ValueChangedEventHandler)
            Me.RemoveHandler(ValueChangedEvent, value)
        End RemoveHandler

        RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Me.RaiseEvent(e)
        End RaiseEvent

    End Event


    Protected Overridable Sub OnValueChanged(ByVal e As ValueChangedEventArgs)
        ' Raise the ValueChanged event so applications can be alerted
        ' when Value changes.
        MyBase.RaiseEvent(e)
    End Sub


#Region "NUDCode"
    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 Overloads Overrides Sub OnApplyTemplate()

        UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
        DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)

        UpdateStates(False)
    End Sub

    Private m_downButtonElement As RepeatButton

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

        Set(ByVal value As RepeatButton)

            If m_downButtonElement IsNot Nothing Then
                RemoveHandler m_downButtonElement.Click, AddressOf downButtonElement_Click
            End If
            m_downButtonElement = value

            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 value As RepeatButton)
            If m_upButtonElement IsNot Nothing Then
                RemoveHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
            End If
            m_upButtonElement = value

            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


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

    Protected Overloads Overrides Sub OnLostFocus(ByVal e As RoutedEventArgs)
        MyBase.OnLostFocus(e)
        UpdateStates(True)
    End Sub
#End Region
End Class


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

Public Class ValueChangedEventArgs
    Inherits RoutedEventArgs
    Private _value As Integer

    Public Sub New(ByVal id As RoutedEvent,
                   ByVal num As Integer)

        _value = num
        RoutedEvent = id
    End Sub

    Public ReadOnly Property Value() As Integer
        Get
            Return _value
        End Get
    End Property
End Class

Vea tambiénSee also