Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Windows Presentation Foundation (WPF) ofrece la posibilidad de crear un control cuya apariencia se puede personalizar. Por ejemplo, puede cambiar la apariencia de un control CheckBox más de lo que las propiedades de configuración permiten hacer mediante la creación de un nuevo objeto ControlTemplate. En la ilustración siguiente se muestra un control CheckBox que usa un ControlTemplate predeterminado y un control CheckBox que usa un ControlTemplatepersonalizado.
Control CheckBox que usa la plantilla de control predeterminada
Control CheckBox que usa una plantilla de control personalizada
Si sigue el modelo de estados y elementos al crear un control, la apariencia del control será personalizable. Existen herramientas de diseño como Microsoft Expression Blend que admiten el modelo de estados y elementos, por lo que si sigue dicho modelo, podrá personalizar el control en ese tipo de aplicaciones. En este tema se trata el modelo de estados y elementos, y cómo seguirlo al crear su propio control. En este tema se emplea un ejemplo de un control personalizado, NumericUpDown, con el fin de ilustrar la filosofía de este modelo. El control NumericUpDown muestra un valor numérico, que los usuarios pueden aumentar o disminuir haciendo clic en los botones del control. En la ilustración siguiente se muestra el control NumericUpDown que se explica en este tema.
Control NumericUpDown personalizado
Este tema contiene las siguientes secciones:
Requisitos previos
Modelo de estados y elementos
Definir la estructura y el comportamiento visuales de un control en ControlTemplate
Usar elementos de ControlTemplate en el código
Proporcionar el contrato de un control
Ejemplo completo
Requisitos previos
En este tema se da por supuesto que sabe crear un ControlTemplate nuevo para un control existente, que está familiarizado con cuáles son los elementos del contrato de un control y que entiende los conceptos tratados en Personalizar la apariencia de un control existente creando una clase ControlTemplate.
![]() |
---|
Para crear un control cuya apariencia se pueda personalizar, debe crear un control que herede de la clase Control o alguna de sus subclases que no sea UserControl.Un control que hereda de UserControl se puede crear rápidamente, pero al no usar ControlTemplate no se puede personalizar su apariencia. |
Modelo de estados y elementos
El modelo de estados y elementos especifica cómo definir la estructura y el comportamiento visuales de un control. Para seguir el modelo de estados y elementos, haga lo siguiente:
Defina la estructura y el comportamiento visuales en el ControlTemplate de un control.
Siga algunos procedimientos recomendados cuando la lógica de su control interactúe con elementos de la plantilla de control.
Proporcione un contrato del control para especificar lo que se debe incluir en ControlTemplate.
Al definir la estructura y el comportamiento visuales en el ControlTemplate de un control, los autores de la aplicación pueden cambiar la estructura y el comportamiento visuales del mismo creando un nuevo ControlTemplate en lugar de escribir código. Debe proporcionar un contrato del control que indique a los autores de la aplicación qué objetos y estados de FrameworkElement se deben definir en ControlTemplate. Debe seguir algunos procedimientos recomendados al interactuar con los elementos de ControlTemplate para que el control administre correctamente un ControlTemplate incompleto. Si sigue estos tres principios, los autores de la aplicación podrán crear un ControlTemplate para el control con la misma facilidad que para los controles que se incluyen en WPF. En la próxima sección se explica en detalle cada una de estas recomendaciones.
Definir la estructura y el comportamiento visuales de un control en ControlTemplate
Al crear un control personalizado usando el modelo de estados y elementos, se define la estructura y el comportamiento visuales del control en su ControlTemplate en lugar de en su lógica. La estructura visual de un control es la composición de los objetos FrameworkElement que constituyen el control. El comportamiento visual es el modo en que aparece el control cuando está en determinado estado. Para obtener más información sobre cómo crear un ControlTemplate que especifique la estructura y el comportamiento visuales de un control, vea Personalizar la apariencia de un control existente creando una clase ControlTemplate.
En el ejemplo del control NumericUpDown, la estructura visual incluye dos controles RepeatButton y un TextBlock. Si agrega estos controles en el código del control NumericUpDown (en su constructor, por ejemplo), las posiciones de esos controles serían inalterables. En lugar de definir la estructura y el comportamiento visuales del control en el código, debe hacerlo en el ControlTemplate. A continuación, un desarrollador de aplicaciones personaliza la posición de los botones y de TextBlock, y especifica qué comportamiento se produce cuando Value es negativo porque el ControlTemplate se puede reemplazar.
En el ejemplo siguiente se muestra la estructura visual del control NumericUpDown, que incluye un RepeatButton para aumentar Value, un RepeatButton para disminuir Value y un TextBlock para mostrar 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 del control NumericUpDown es que el valor aparece en una fuente de color rojo si es negativo. Si cambia la propiedad Foreground del TextBlock en el código cuando Value es negativo, NumericUpDown siempre mostrará un valor negativo en rojo. Se especifica el comportamiento visual del control en ControlTemplate agregando objetos VisualState al ControlTemplate. En el ejemplo siguiente se muestran los objetos VisualState para los estados Positive y Negative. Positive y Negative son mutuamente excluyentes (el control siempre está en uno de los dos), por lo que en el ejemplo se colocan los objetos VisualState en un mismo VisualStateGroup. Cuando el control entra en el estado Negative, la propiedad Foreground de TextBlock cambia a rojo. Cuando el control está en el estado Positive, la propiedad Foreground vuelve a su valor original. La definición de objetos VisualState en ControlTemplate se explica más detalladamente en Personalizar la apariencia de un control existente creando una clase ControlTemplate.
![]() |
---|
Asegúrese de establecer la propiedad adjunta VisualStateManager.VisualStateGroups del FrameworkElement raíz de 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>
Usar elementos de ControlTemplate en el código
Un autor de ControlTemplate podría omitir los objetos VisualState o FrameworkElement a propósito o por error, pero la lógica del control podría necesitar esos elementos para funcionar correctamente. El modelo de estados y elementos especifica que el control debe adaptarse a un ControlTemplate al que le falten objetos VisualState o FrameworkElement. El control no debe producir una excepción ni notificar un error si falta un FrameworkElement, VisualState o VisualStateGroup en ControlTemplate. En esta sección se describen los procedimientos recomendados para interactuar con objetos FrameworkElement y administrar estados.
Prever los objetos FrameworkElement que puedan faltar
Al definir objetos FrameworkElement en ControlTemplate, la lógica del control podría necesitar interactuar con algunos de ellos. Por ejemplo, el control NumericUpDown se suscribe al evento Click de los botones para aumentar o disminuir Value y establece la propiedad Text de TextBlock en Value. Si un ControlTemplate personalizado omite el TextBlock o los botones, es aceptable que el control pierda parte de su funcionalidad, pero debe asegurarse de que el control no produzca un error. Por ejemplo, si un ControlTemplate no contiene los botones para cambiar Value, el control NumericUpDown pierde esa funcionalidad, pero una aplicación que use el ControlTemplate continuará ejecutándose.
Con los procedimientos siguientes se asegurará de que el control responda correctamente a los objetos FrameworkElement que falten:
Establezca el atributo x:Name para cada FrameworkElement al que necesite hacer referencia en el código.
Defina las propiedades privadas para cada FrameworkElement con el que necesite interactuar.
Suscriba y cancele la suscripción de cualquier evento que el control administre en el descriptor de acceso set de la propiedad del objeto FrameworkElement.
Establezca las propiedades del objeto FrameworkElement que definió en el paso 2 del método OnApplyTemplate. Esto es lo antes que el FrameworkElement está disponible para el control en el ControlTemplate. Use el atributo x:Name del FrameworkElement para obtenerlo de ControlTemplate.
Compruebe que FrameworkElement no es null antes de tener acceso a sus miembros. Si es null, no notifique un error.
En los ejemplos siguientes se muestra cómo interactúa el control NumericUpDown con objetos FrameworkElement de acuerdo con las recomendaciones de la lista anterior.
En el ejemplo que define la estructura visual del control NumericUpDown en ControlTemplate, el RepeatButton que aumenta Value tiene el atributo x:Name establecido en UpButton. En el ejemplo siguiente se declara una propiedad denominada UpButtonElement que representa el RepeatButton que se declara en ControlTemplate. El descriptor de acceso set cancela primero la suscripción al evento Click del botón si UpDownElement no es null; a continuación, establece la propiedad y después se suscribe al evento Click. Aunque no se muestra aquí, también se define una propiedad del otro RepeatButton, denominado DownButtonElement.
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 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);
}
}
}
En el ejemplo siguiente se muestra el método OnApplyTemplate para el control NumericUpDown. Se usa el método GetTemplateChild para obtener los objetos FrameworkElement de ControlTemplate. Observe que en el ejemplo se evitan los casos donde GetTemplateChild busca un FrameworkElement con el nombre especificado que no es del tipo esperado. También es un procedimiento recomendado omitir los elementos que tienen el atributo x:Name especificado pero son del tipo equivocado.
Public Overloads Overrides Sub OnApplyTemplate()
UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)
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);
}
Siguiendo los procedimientos que se muestran en los ejemplos anteriores, se asegura de que el control continuará ejecutándose cuando a ControlTemplate le falte un FrameworkElement.
Usar VisualStateManager para administrar estados
VisualStateManager realiza un seguimiento de los estados de un control y ejecuta la lógica necesaria para la transición entre estados. Al agregar objetos VisualState a ControlTemplate, los agrega a un VisualStateGroup y agrega este VisualStateGroup a la propiedad adjunta VisualStateManager.VisualStateGroups de modo que VisualStateManager tenga acceso a ellos.
En el ejemplo siguiente se repite el ejemplo anterior donde se muestran los objetos VisualState correspondientes a los estados Positive y Negative del control. El Storyboard de Negative VisualState cambia la propiedad Foreground de TextBlock a rojo. Cuando el control NumericUpDown está en estado Negative, comienza el guión gráfico en el estado Negative. A continuación, el Storyboard del estado Negative se detiene cuando el control vuelve al estado Positive. El VisualState con estado Positive no necesita contener un Storyboard porque cuando el Storyboard para el estado Negative se detiene, la propiedad Foreground vuelve a su color original.
<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>
Observe que se asigna un nombre a TextBlock, pero TextBlock no es en el contrato del control para NumericUpDown porque la lógica del control nunca hace referencia a TextBlock. Los elementos a los que se hacen referencia en ControlTemplate tienen nombres, pero no necesitan formar parte del contrato del control porque un nuevo ControlTemplate para el control quizás no necesite hacer referencia a ese elemento. Por ejemplo, alguien que crea un nuevo ControlTemplate para NumericUpDown podría decidir no indicar que Value es negativo cambiando la propiedad Foreground. En ese caso, ni el código ni ControlTemplate hace referencia a TextBlock por nombre.
La lógica del control es responsable de cambiar el estado del control. En el ejemplo siguiente se muestra que el control NumericUpDown llama al método GoToState para entrar en el estado Positive cuando Value es 0 o mayor, y en el estado Negative cuando Value es menor que 0.
If Value >= 0 Then
VisualStateManager.GoToState(Me, "Positive", useTransitions)
Else
VisualStateManager.GoToState(Me, "Negative", useTransitions)
End If
if (Value >= 0)
{
VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Negative", useTransitions);
}
El método GoToState ejecuta la lógica necesaria para iniciar y detener los guiones gráficos correctamente. Cuando un control llama a GoToState para cambiar su estado, VisualStateManager hace lo siguiente:
Si el VisualState hacia el que va el control tiene un Storyboard, comienza el guión gráfico. A continuación, si el VisualState del que procede el control tiene un Storyboard, el guión gráfico se detiene.
Si el control ya está en el estado que se especifica, el método GoToState no realiza ninguna acción y devuelve true.
Si el estado que se especifica no existe en el ControlTemplate de control, el método GoToState no realiza ninguna acción y devuelve false.
Procedimientos recomendados para trabajar con VisualStateManager
Se recomienda que haga lo siguiente para mantener los estados del control:
Use propiedades para realizar un seguimiento del estado.
Cree un método auxiliar para la transición entre estados.
El control NumericUpDown usa su propiedad Value para comprobar si está en estado Positive o Negative. El control NumericUpDown también define los estados Focused y UnFocused, lo que hace un seguimiento de la propiedad IsFocused. Si emplea estados que no corresponden naturalmente a una propiedad del control, puede definir una propiedad privada para realizar un seguimiento del estado.
Un único método que actualiza todos los estados centraliza las llamadas a VisualStateManager y hace que el código se pueda controlar. En el ejemplo siguiente se muestra el método auxiliar UpdateStates del control NumericUpDown. Cuando Value es mayor o igual a 0, el Control está en estado Positive. Cuando Value es menor que 0, el control está en estado Negative. Cuando la propiedad IsFocused es true, el control está en estado Focused; de lo contrario, está en estado Unfocused. El control puede llamar a UpdateStates siempre que necesite cambiar su estado, independientemente del estado que cambie.
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);
}
}
Si pasa el nombre de un estado a GoToState cuando el control ya está en dicho estado, GoToState no hace nada, por lo que no necesita comprobar el estado actual del control. Por ejemplo, si Value cambia de un número negativo a otro número negativo, no se interrumpe el guión gráfico para el estado Negative y el usuario no verá ningún cambio en el control.
VisualStateManager usa objetos de VisualStateGroup para determinar de qué estado salir cuando se llama al método GoToState. El control siempre está en un estado para cada VisualStateGroup que se define en su ControlTemplate correspondiente y únicamente abandona un estado cuando entra en otro estado del mismo VisualStateGroup. Por ejemplo, el control ControlTemplate del NumericUpDown define los objetos Positive y Negative VisualState de un VisualStateGroup, y los objetos Focused y Unfocused VisualState de otro. (Puede ver la definición de VisualState en los estados Focused y Unfocused en la sección Ejemplo completo de este tema.) Cuando el control pasa del estado Positive al estado Negative o viceversa, el control permanece en el estado Focused o Unfocused.
Hay tres casos típicos en los que el estado de un control podría cambiar:
Cuando se aplica ControlTemplate a Control.
Cuando cambia una propiedad.
Cuando se produce un evento.
En los ejemplos siguientes se muestra cómo actualizar el estado del control NumericUpDown en estos casos.
Debe actualizar el estado del control en el método OnApplyTemplate de forma que el control aparezca en el estado correcto cuando se aplique ControlTemplate. En el ejemplo siguiente se llama a UpdateStates en el método OnApplyTemplate para garantizar que el control esté en los estados adecuados. Por ejemplo, suponga que crea un control NumericUpDown, y que establece su propiedad Foreground en verde y Value en -5. Si no llama a UpdateStates cuando el objeto ControlTemplate se aplica al control NumericUpDown, el control no está en el estado Negative y el valor es verde en lugar de rojo. Debe llamar a UpdateStates para poner el control en el estado Negative.
Public Overloads Overrides Sub OnApplyTemplate()
UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)
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);
}
Con frecuencia necesita actualizar los estados de un control cuando una propiedad cambia. En el ejemplo siguiente se muestra todo el método ValueChangedCallback. Puesto que se llama a ValueChangedCallback cuando Value cambia, el método llama a UpdateStates si Value cambió de positivo a negativo o viceversa. Es aceptable llamar a UpdateStates cuando Value cambia pero sigue siendo positivo o negativo, ya que en ese caso el control no cambiará de estado.
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
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));
}
También podría necesitar actualizar los estados cuando se produce un evento. En el ejemplo siguiente se muestra que NumericUpDown llama a UpdateStates en el Control para controlar el evento GotFocus.
Protected Overloads Overrides Sub OnGotFocus(ByVal e As RoutedEventArgs)
MyBase.OnGotFocus(e)
UpdateStates(True)
End Sub
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
UpdateStates(true);
}
VisualStateManager ayuda a administrar los estados del control. Usando VisualStateManager, se asegura de que el control realiza las transiciones entre estados correctamente. Si sigue las recomendaciones descritas en esta sección para trabajar con VisualStateManager, el código del control seguirá siendo legible y podrá mantenerse.
Proporcionar el contrato de un control
Se proporciona el contrato de un control para que los autores de ControlTemplate sepan qué deben incluir en la plantilla. El contrato de un control tiene tres elementos:
Los elementos visuales que usa la lógica del control.
Los estados del control y el grupo al que pertenece cada estado.
Las propiedades públicas que afectan visualmente al control.
Alguien que cree un nuevo ControlTemplate debe saber qué objetos FrameworkElement usa la lógica del control, el tipo de objeto que es cada uno y cómo se denomina. Un autor de ControlTemplate también necesita conocer el nombre de los posibles estados en los que se puede hallar el control y en qué VisualStateGroup se encuentra el estado.
Volviendo al ejemplo de NumericUpDown, el control espera que ControlTemplate tenga los siguientes objetos FrameworkElement:
Un control RepeatButton denominado UpButton.
Un RepeatButton denominado DownButton.
El control puede estar en los estados siguientes:
En el VisualStateGroup ValueStates
Positive
Negative
En el VisualStateGroup FocusStates
Focused
Unfocused
Para especificar qué objetos FrameworkElement espera el control, se utiliza la clase TemplatePartAttribute, que especifica el nombre y tipo de los elementos esperados. Para especificar los posibles estados de un control, se usa la clase TemplateVisualStateAttribute, que especifica el nombre del estado y a qué VisualStateGroup pertenece. Coloque las clases TemplatePartAttribute y TemplateVisualStateAttribute en la definición de clase del control.
Cualquier propiedad pública que afecte a la apariencia del control también forma parte del contrato de un control.
En el ejemplo siguiente se especifica el objeto FrameworkElement y los estados del control NumericUpDown.
<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
[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; }
}
Ejemplo completo
El ejemplo siguiente es todo el ControlTemplate para el control NumericUpDown.
<!--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: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>
En el ejemplo siguiente se muestra la lógica de NumericUpDown.
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
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; }
}
}
}
Vea también
Conceptos
Personalizar la apariencia de un control existente creando una clase ControlTemplate