Para ver el artículo en inglés, active la casilla Inglés. También puede ver el texto en inglés en una ventana emergente si pasa el puntero del mouse por el texto.
Traducción
Inglés
Esta documentación está archivada y no tiene mantenimiento.

Personalizar la apariencia de un control existente creando una clase ControlTemplate

Visual Studio 2010

Una plantilla ControlTemplate especifica la estructura y el comportamiento visuales de un control. Puede personalizar la apariencia de un control proporcionándole una nueva plantilla ControlTemplate. Cuando se crea una plantilla ControlTemplate, se reemplaza la apariencia de un control existente sin cambiar su funcionalidad. Por ejemplo, puede hacer que los botones de la aplicación sean redondos en lugar de rectangulares como son de forma predeterminada, pero cada botón seguirá generando el evento Click.

En este tema se explican las distintas partes de una plantilla ControlTemplate, se muestra cómo se crea una plantilla ControlTemplate sencilla para un control Button y se explica cómo entender el contrato de un control para que pueda personalizar su apariencia. Puesto que las plantillas ControlTemplate se crean en XAML, puede cambiar la apariencia de un control sin escribir código. También puede usar un diseñador, como Microsoft Expression Blend, para crear plantillas de control personalizado. En este tema se muestran ejemplos de XAML que personalizan la apariencia de un control Button y se muestra el ejemplo completo al final del tema. Para obtener más información sobre cómo usar Expression Blend, vea Aplicar estilo a un control que admite plantillas.

En las ilustraciones siguientes se muestra un control Button que usa la plantilla ControlTemplate que se crea en este tema.

Botón que usa una plantilla de control personalizada

Botón con una plantilla de control personalizada.
Botón que usa una plantilla de control personalizada y tiene el puntero del mouse sobre él

Botón con un borde rojo.

Este tema contiene las secciones siguientes.

En este tema se da por supuesto que entiende cómo crear y usar controles y estilos tal como se describe en Controles. Los conceptos descritos en este tema se aplican a los elementos que heredan de la clase Control, salvo UserControl. No se puede aplicar una plantilla ControlTemplate a un control UserControl.

Los controles tienen muchas propiedades, como Background, Foreground y FontFamily, que puede establecer para especificar distintos aspectos de la apariencia del control, pero los cambios que puede realizar estableciendo estas propiedades son limitados. Por ejemplo, puede establecer la propiedad Foreground en azul y la propiedad FontStyle en cursiva en un control CheckBox.

Sin la capacidad de crear una nueva plantilla ControlTemplate para los controles, todos los controles de todas las aplicaciones basadas en WPF tendrían la misma apariencia general, lo que limitaría la capacidad de crear una aplicación con una apariencia y un funcionamiento personalizados. De forma predeterminada, todos los controles CheckBox tienen características similares. Por ejemplo, el contenido de CheckBox aparece siempre a la derecha del indicador de selección y la marca de verificación siempre se emplea para indicar que el control CheckBox está activado.

Una plantilla ControlTemplate se crea cuando se desea personalizar la apariencia del control más allá de las posibilidades que ofrece establecer otras propiedades del mismo. En el ejemplo del control CheckBox, suponga que desea que el contenido de la casilla se encuentre encima del indicador de selección y que aparezca una X para indicar que CheckBox está activado. Se debe especificar estos cambios en la plantilla ControlTemplate del control CheckBox.

En la ilustración siguiente se muestra un control CheckBox que usa un objeto ControlTemplate predeterminado.

Control CheckBox que usa la plantilla de control predeterminada

Casilla con la plantilla de control predeterminada.

En la ilustración siguiente se muestra un control CheckBox que usa un ControlTemplate personalizado para colocar el contenido de CheckBox sobre el indicador de la selección y muestra una X cuando CheckBox está activado.

Control CheckBox que usa una plantilla de control personalizada

Casilla con una plantilla de control personalizada.

La plantilla ControlTemplate para el control CheckBox de este ejemplo es relativamente compleja, por lo que en este tema se usa un ejemplo más sencillo para crear una plantilla ControlTemplate para un control Button.

En WPF, un control suele estar compuesto de objetos FrameworkElement. Al crear una plantilla ControlTemplate, se combinan objetos FrameworkElement para compilar un único control. Una plantilla ControlTemplate debe tener un solo objeto FrameworkElement como su elemento raíz. El elemento raíz normalmente contiene otros objetos FrameworkElement. La combinación de objetos constituye la estructura visual del control.

En el ejemplo siguiente se crea una plantilla ControlTemplate personalizada para el control Button. ControlTemplate crea la estructura visual de Button. En este ejemplo no se modifica la apariencia del botón al situar el puntero del mouse sobre él o al hacer clic en él. La modificación de la apariencia del botón cuando se encuentra en un estado diferente se explica más adelante en este mismo tema.

En este ejemplo, la estructura visual consta de las partes siguientes:


<ControlTemplate TargetType="Button">
  <Border Name="RootElement">

    <!--Create the SolidColorBrush for the Background 
        as an object elemment and give it a name so 
        it can be referred to elsewhere in the
        control template.-->
    <Border.Background>
      <SolidColorBrush x:Name="BorderBrush" Color="Black"/>
    </Border.Background>

    <!--Create a border that has a different color
        by adding smaller grid. The background of 
        this grid is specificied by the button's 
        Background property.-->
    <Grid Margin="4" Background="{TemplateBinding Background}">

      <!--Use a ContentPresenter to display the Content of
          the Button.-->
      <ContentPresenter
        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
        VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
        Margin="4,5,4,4" />
    </Grid>

  </Border>
</ControlTemplate>


Conservar la funcionalidad de las propiedades de un control mediante TemplateBinding

Al crear una nueva plantilla ControlTemplate, puede que desee seguir usando las propiedades públicas para cambiar la apariencia del control. La extensión de marcado TemplateBinding enlaza una propiedad de un elemento que se encuentra en la plantilla ControlTemplate a una propiedad pública definida por el control. Cuando se usa TemplateBinding, se permite a las propiedades del control actuar como parámetros de la plantilla. Es decir, cuando se establece una propiedad de un control, ese valor se pasa al elemento que tiene TemplateBinding.

En el ejemplo siguiente se repite la parte del ejemplo anterior que usa la extensión de marcado TemplateBinding para enlazar propiedades de elementos que se encuentran en la plantilla ControlTemplate a las propiedades públicas definidas por el botón.


<Grid Margin="4" Background="{TemplateBinding Background}">

  <!--Use a ContentPresenter to display the Content of
      the Button.-->
  <ContentPresenter
    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
    Margin="4,5,4,4" />
</Grid>


En este ejemplo, el control Grid tiene su plantilla de propiedad Panel.Background enlazada a Control.Background. Puesto que Panel.Background está enlazado a la plantilla, puede crear varios botones que usen la misma plantilla ControlTemplate y establecer la propiedad Control.Background en valores diferentes para cada botón. Si la propiedad Control.Background no estuviera enlazada a una propiedad de un elemento de la plantilla ControlTemplate, establecer la propiedad Control.Background de un botón no afectaría a la apariencia del botón.

Tenga en cuenta que los nombres de las dos propiedades no tienen por qué ser idénticos. En el ejemplo anterior, la propiedad Control.HorizontalContentAlignment del control Button está enlazada a la plantilla a la propiedad FrameworkElement.HorizontalAlignment del control ContentPresenter. Esto permite colocar el contenido del botón horizontalmente. ContentPresenter no tiene una propiedad denominada HorizontalContentAlignment, pero Control.HorizontalContentAlignment se puede enlazar a FrameworkElement.HorizontalAlignment. Cuando el enlace la plantilla a una propiedad, asegúrese de que las propiedades de origen y de destino sean del mismo tipo.

La clase Control define varias propiedades que la plantilla de control debe usar para provocar un efecto sobre el control cuando se establecen. La forma en que ControlTemplate usa la propiedad depende de la propiedad. ControlTemplate debe usar la propiedad de una de las maneras siguientes:

En la tabla siguiente se enumeran las propiedades visuales heredadas por un control de la clase Control. También se indica si la plantilla de control predeterminada de un control usa el valor de propiedad heredado o si debe estar enlazada a la plantilla.

Propiedad

Método de uso

Background

Enlace a plantilla

BorderThickness

Enlace a plantilla

BorderBrush

Enlace a plantilla

FontFamily

Herencia de propiedades o enlace a plantilla

FontSize

Herencia de propiedades o enlace a plantilla

FontStretch

Herencia de propiedades o enlace a plantilla

FontWeight

Herencia de propiedades o enlace a plantilla

Foreground

Herencia de propiedades o enlace a plantilla

HorizontalContentAlignment

Enlace a plantilla

Padding

Enlace a plantilla

VerticalContentAlignment

Enlace a plantilla

En la tabla solo se muestran las propiedades visuales heredadas de la clase Control. Aparte de las propiedades enumeradas en la tabla anterior, un control puede heredar también las propiedades DataContext, TextDecorations y Language del elemento de marco de trabajo primario.

Además, si el ContentPresenter se encuentra en la plantilla ControlTemplate de un control ContentControl, el ContentPresenter automáticamente se enlazará a las propiedades del Content y al ContentTemplate. Del mismo modo, un control ItemsPresenter que se encuentre en la plantilla ControlTemplate de un control ItemsControl se enlazará automáticamente a las propiedades ItemsPresenter e Items.

En el ejemplo siguiente se crean dos botones que usan la plantilla ControlTemplate definida en el ejemplo anterior. En el ejemplo se establecen las propiedades Background, Foreground y FontSize de cada botón. Establecer la propiedad Background surte efecto, porque está enlazada a plantilla en ControlTemplate. Aunque las propiedades FontSize y Foreground no están enlazadas a plantilla, establecerlas surte efecto porque sus valores se heredan.


<StackPanel>
  <Button Style="{StaticResource newTemplate}" 
          Background="Navy" Foreground="White" FontSize="14"
          Content="Button1"/>

  <Button Style="{StaticResource newTemplate}" 
          Background="Purple" Foreground="White" FontSize="14"
          Content="Button2" HorizontalContentAlignment="Left"/>
</StackPanel>


En el ejemplo anterior se generan unos resultados similares a los mostrados en la siguiente ilustración.

Dos botones con distintos colores de fondo

Dos botones, uno azul y otro púrpura.

La diferencia entre un botón con su apariencia predeterminada y el botón del ejemplo anterior es que el botón predeterminado cambia sutilmente cuando se encuentra en estados diferentes. Por ejemplo, la apariencia del botón predeterminado cambia cuando está presionado o cuando el puntero del mouse está encima de él. Si bien la plantilla ControlTemplate no cambia la funcionalidad de un control, sí modifica su comportamiento visual. Un comportamiento visual describe la apariencia del control cuando se encuentra en un estado determinado. Para entender la diferencia entre la funcionalidad y el comportamiento visual de un control, tomemos el ejemplo del botón. La funcionalidad del botón consiste en generar el evento Click cuando se hace clic en él; en cambio, el comportamiento visual del botón consiste en cambiar su apariencia al señalarlo o presionarlo.

Para especificar la apariencia de un control cuando se encuentra en un estado determinado se emplean objetos VisualState. Un objeto VisualState contiene un objeto Storyboard que cambia la apariencia de los elementos que se encuentran en la plantilla ControlTemplate. No es necesario escribir código para que esto suceda, ya que la lógica del control cambia de estado mediante VisualStateManager. Cuando el control entra en el estado especificado por la propiedad VisualState.Name, se inicia Storyboard. Cuando el control sale del estado, Storyboard se detiene.

En el ejemplo siguiente se muestra un objeto VisualState que cambia la apariencia de un control Button cuando el puntero del mouse está encima de él. Storyboard cambia el color del borde del botón cambiando el color de BorderBrush. Si se fija en el ejemplo de ControlTemplate que se proporciona al principio de este tema, recordará que BorderBrush es el nombre del objeto SolidColorBrush asignado a la propiedad Background del control Border.


<!--Change the border of the button to red when the
    mouse is over the button.-->
<VisualState x:Name="MouseOver">
  <Storyboard>
    <ColorAnimation Storyboard.TargetName="BorderBrush"     
                    Storyboard.TargetProperty="Color"
                    To="Red" />

  </Storyboard>
</VisualState>


El control es responsable de definir los estados como parte de su contrato de control; esto se explica con detalle en Personalizar otros controles entendiendo el contrato de control más adelante en este mismo tema. En la tabla siguiente se enumeran los estados especificados para Button.

Nombre de VisualState

Nombre de VisualStateGroup

Descripción

Normal

CommonStates

Estado predeterminado.

MouseOver

CommonStates

El puntero del mouse está situado sobre el control.

Pressed

CommonStates

El control está presionado.

Disabled

CommonStates

El control está deshabilitado.

Focused

FocusStates

El control tiene el foco.

Unfocused

FocusStates

El control no tiene el foco.

Button define dos grupos de estados: el grupo CommonStates contiene los estados Normal, MouseOver, Pressed y Disabled. Por su parte, el grupo FocusStates contiene los estados Unfocused y Focused. Los estados contenidos en el mismo grupo de estados son mutuamente excluyentes. El control siempre se encuentra exactamente en un estado de cada grupo. Por ejemplo, un control Button puede tener el foco incluso cuando el puntero del mouse no está encima de él, por lo que un control Button en el estado Focused puede estar en cualquiera de los estados MouseOver, Pressed o Normal.

Los objetos VisualState se agregan a los objetos VisualStateGroup. Los objetos VisualStateGroup se agregan a la propiedad adjunta VisualStateManager.VisualStateGroups. En el ejemplo siguiente se definen los objetos VisualState correspondientes a los estados Normal, MouseOver y Pressed, todos ellos pertenecientes al grupo CommonStates. La propiedad Name de cada VisualState coincide con el nombre de la tabla anterior. El estado Disabled y los estados del grupo FocusStates se omiten para que el ejemplo resulte breve, pero se incluyen en el ejemplo completo que encontrará al final de este tema.

NotaNota

Asegúrese de establecer la propiedad adjunta VisualStateManager.VisualStateGroups del FrameworkElement raíz de ControlTemplate.


<ControlTemplate TargetType="Button">
  <Border Name="RootElement">

    <VisualStateManager.VisualStateGroups>

      <!--Define the states and transitions for the common states.
          The states in the VisualStateGroup are mutually exclusive to
          each other.-->
      <VisualStateGroup Name="CommonStates">

        <!--The Normal state is the state the button is in
            when it is not in another state from this VisualStateGroup.-->
        <VisualState Name="Normal" />

        <!--Change the SolidColorBrush, BorderBrush, to red when the
            mouse is over the button.-->
        <VisualState Name="MouseOver">
          <Storyboard>
            <ColorAnimation Storyboard.TargetName="BorderBrush" 
                            Storyboard.TargetProperty="Color" 
                            To="Red" />
          </Storyboard>
        </VisualState>

        <!--Change the SolidColorBrush, BorderBrush, to Transparent when the
            button is pressed.-->
        <VisualState Name="Pressed">
          <Storyboard>
            <ColorAnimation Storyboard.TargetName="BorderBrush" 
                            Storyboard.TargetProperty="Color"
                            To="Transparent"/>
          </Storyboard>
        </VisualState>

        <!--The Disabled state is omitted for brevity.-->
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

    <Border.Background>
      <SolidColorBrush x:Name="BorderBrush" Color="Black"/>
    </Border.Background>

    <Grid Background="{TemplateBinding Background}" Margin="4">
      <ContentPresenter
        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
        VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
        Margin="4,5,4,4" />
    </Grid>
  </Border>
</ControlTemplate>


En el ejemplo anterior se generan unos resultados similares a los mostrados en las ilustraciones siguientes.

Un botón que usa una plantilla de control personalizada en el estado normal

Botón con una plantilla de control personalizada.
Un botón que usa una plantilla de control personalizada en el estado de pasar el mouse

Botón con un borde rojo.
Un botón que usa una plantilla de control personalizada en el estado presionado

El borde es transparente en un botón presionado.

Para buscar los estados visuales de los controles que se incluyen con WPF, vea Estilos y plantillas de Control.

En el ejemplo anterior, la apariencia del botón también cambia cuando el usuario hace clic en él, pero a menos que el botón se presione durante un segundo completo, el usuario no verá el efecto. De forma predeterminada, la animación tarda un segundo en producirse. Puesto que es probable que los usuarios hagan clic en el botón y lo suelten en mucho menos tiempo, la representación visual no será efectiva si se deja ControlTemplate en su estado predeterminado.

Puede especificar la cantidad de tiempo que tardará una animación en producirse para efectuar una transición fluida de un estado a otro en un control; para ello, agregue objetos VisualTransition a la plantilla ControlTemplate. Al crear un objeto VisualTransition, puede especificar uno o varios de los elementos siguientes:

  • El tiempo que tarda en producirse una transición entre estados.

  • Los cambios adicionales en la apariencia del control que se producen en el momento de la transición.

  • A qué estados se aplica el objeto VisualTransition.

Especificar la duración de una transición

Se puede especificar cuánto tiempo tarda una transición estableciendo la propiedad GeneratedDuration. El ejemplo anterior contiene un objeto VisualState que especifica que el borde del botón se vuelve transparente al presionarlo, pero la animación tarda demasiado tiempo y no se aprecia cuando el botón se presiona y suelta rápidamente. Puede usar un objeto VisualTransition para especificar la cantidad de tiempo que el control tarda en hacer la transición al estado presionado. En el ejemplo siguiente se especifica que el control tarda una centésima de segundo en entrar en el estado presionado.


<!--Take one hundredth of a second to transition to the
    Pressed state.-->
<VisualTransition To="Pressed" 
                  GeneratedDuration="0:0:0.01" />


Especificar cambios en la apariencia del control durante una transición

VisualTransition contiene un objeto Storyboard que se inicia cuando el control hace la transición entre estados. Por ejemplo, puede especificar que se produzca determinada animación cuando el control realice la transición del estado MouseOver al estado Normal. En el ejemplo siguiente se crea un objeto VisualTransition que especifica que cuando el usuario mueve el puntero del mouse fuera del botón, su borde cambia a azul, después a amarillo y por último a negro en 1,5 segundos.


<!--Take one and a half seconds to transition from the
    MouseOver state to the Normal state. 
    Have the SolidColorBrush, BorderBrush, fade to blue, 
    then to yellow, and then to black in that time.-->
<VisualTransition From="MouseOver" To="Normal" 
                      GeneratedDuration="0:0:1.5">
  <Storyboard>
    <ColorAnimationUsingKeyFrames
      Storyboard.TargetProperty="Color"
      Storyboard.TargetName="BorderBrush"
      FillBehavior="HoldEnd" >

      <ColorAnimationUsingKeyFrames.KeyFrames>

        <LinearColorKeyFrame Value="Blue" 
          KeyTime="0:0:0.5" />
        <LinearColorKeyFrame Value="Yellow" 
          KeyTime="0:0:1" />
        <LinearColorKeyFrame Value="Black" 
          KeyTime="0:0:1.5" />

      </ColorAnimationUsingKeyFrames.KeyFrames>
    </ColorAnimationUsingKeyFrames>
  </Storyboard>
</VisualTransition>


Especificar cuándo se aplica un objeto VisualTransition

VisualTransition se puede restringir para aplicarse solo a ciertos estados o se puede aplicar siempre que el control realice la transición entre estados. En el ejemplo anterior, VisualTransition se aplica cuando el control pasa del estado MouseOver al estado Normal; en el ejemplo anterior a este, VisualTransition se aplica cuando el control entra en el estado Pressed. Para restringir cuándo se aplica un objeto VisualTransition hay que establecer las propiedades From y To. En la tabla siguiente se describen los niveles de restricción, desde el más restrictivo al menos restrictivo.

Tipo de restricción

Valor de From

Valor de To

Desde un estado especificado a otro estado especificado

Nombre de un objeto VisualState.

Nombre de un objeto VisualState.

Desde cualquier estado a un estado especificado

No establecido

Nombre de un objeto VisualState.

Desde un estado especificado a cualquier estado

Nombre de un objeto VisualState.

No establecido

Desde cualquier estado a cualquier otro estado

No establecido

No establecida

Se pueden tener varios objetos VisualTransition en un VisualStateGroup que haga referencia al mismo estado, pero se usarán en el orden que se especifica en la tabla anterior. En el ejemplo siguiente hay dos objetos VisualTransition. Cuando el control realiza la transición del estado Pressed al estado MouseOver, se usa el objeto VisualTransition que tiene establecidas las dos propiedades From y To. Cuando el control realiza la transición de un estado que no es Pressed al estado MouseOver, se emplea el otro estado.


<!--Take one half second to trasition to the MouseOver state.-->
<VisualTransition To="MouseOver" 
                  GeneratedDuration="0:0:0.5" />

<!--Take one hundredth of a second to transition from the
    Pressed state to the MouseOver state.-->
<VisualTransition From="Pressed" To="MouseOver" 
                  GeneratedDuration="0:0:0.01" />


El objeto VisualStateGroup tiene una propiedad Transitions que contiene los objetos VisualTransition que se aplican a los objetos VisualState de VisualStateGroup. Como autor de ControlTemplate, dispone de libertad para incluir cualquier objeto VisualTransition que desee. Sin embargo, si las propiedades From y To se establecen en nombres de estados que no pertenecen a VisualStateGroup, el objeto VisualTransition se omite.

En el ejemplo siguiente se muestra el objeto VisualStateGroup de CommonStates. En el ejemplo se define un objeto VisualTransition para cada una de las transiciones siguientes del botón.

  • Al estado Pressed.

  • Al estado MouseOver.

  • Del estado Pressed al estado MouseOver.

  • Del estado MouseOver al estado Normal.


<VisualStateGroup Name="CommonStates">

  <!--Define the VisualTransitions that
      can be used when the control transitions 
      between VisualStates that are defined in the
      VisualStatGroup.-->
  <VisualStateGroup.Transitions>

    <!--Take one hundredth of a second to 
        transition to the Pressed state.-->
    <VisualTransition To="Pressed" 
                      GeneratedDuration="0:0:0.01" />

    <!--Take one half second to trasition 
        to the MouseOver state.-->
    <VisualTransition To="MouseOver" 
                      GeneratedDuration="0:0:0.5" />

    <!--Take one hundredth of a second to transition from the
        Pressed state to the MouseOver state.-->
    <VisualTransition From="Pressed" To="MouseOver" 
                      GeneratedDuration="0:0:0.01" />

    <!--Take one and a half seconds to transition from the
        MouseOver state to the Normal state. 
        Have the SolidColorBrush, BorderBrush, fade to blue, 
        then to yellow, and then to black in that time.-->
    <VisualTransition From="MouseOver" To="Normal" 
                      GeneratedDuration="0:0:1.5">
      <Storyboard>
        <ColorAnimationUsingKeyFrames
          Storyboard.TargetProperty="Color"
          Storyboard.TargetName="BorderBrush"
          FillBehavior="HoldEnd" >

          <ColorAnimationUsingKeyFrames.KeyFrames>
            <LinearColorKeyFrame Value="Blue" 
              KeyTime="0:0:0.5" />
            <LinearColorKeyFrame Value="Yellow" 
              KeyTime="0:0:1" />
            <LinearColorKeyFrame Value="Black" 
              KeyTime="0:0:1.5" />

          </ColorAnimationUsingKeyFrames.KeyFrames>
        </ColorAnimationUsingKeyFrames>
      </Storyboard>
    </VisualTransition>
  </VisualStateGroup.Transitions>

  <!--The remainder of the VisualStateGroup is the
      same as the previous example.-->

  <VisualState Name="Normal" />

  <VisualState Name="MouseOver">
    <Storyboard>
      <ColorAnimation 
        Storyboard.TargetName="BorderBrush" 
        Storyboard.TargetProperty="Color" 
        To="Red" />

    </Storyboard>
  </VisualState>

  <VisualState Name="Pressed">
    <Storyboard>
      <ColorAnimation 
        Storyboard.TargetName="BorderBrush" 
        Storyboard.TargetProperty="Color" 
        To="Transparent"/>
    </Storyboard>
  </VisualState>

  <!--The Disabled state is omitted for brevity.-->

</VisualStateGroup>


Un control que usa una plantilla ControlTemplate para especificar su estructura visual (mediante objetos FrameworkElement) y su comportamiento visual (mediante objetos VisualState) emplea el modelo de control de elementos. Muchos de los controles incluidos en WPF 4 usan este modelo. Los elementos que un autor de plantillas ControlTemplate debe conocer se comunican mediante el contrato de control. Si se entienden los elementos de un contrato de control, es posible personalizar la apariencia de cualquier control que use el modelo de control de elementos.

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.

Elementos visuales del contrato de control

A veces, la lógica de un control interactúa con un objeto FrameworkElement que se encuentra en una plantilla ControlTemplate. Por ejemplo, el control podría controlar un evento de uno de sus elementos. Cuando un control espera encontrar un objeto FrameworkElement determinado en la plantilla ControlTemplate, debe transmitir esa información al autor de ControlTemplate. El control usa TemplatePartAttribute para transmitir el tipo de elemento que se espera y cuál debe ser el nombre del elemento. El control Button no tiene elementos del objeto FrameworkElement en su contrato de control; sin embargo, otros controles, como ComboBox, sí los tienen.

En el ejemplo siguiente se muestran los objetos TemplatePartAttribute que se especifican en la clase ComboBox. La lógica del control ComboBox espera encontrar un objeto TextBox denominado PART_EditableTextBox y un objeto Popup denominado PART_Popup en la plantilla ControlTemplate.


[TemplatePartAttribute(Name = "PART_EditableTextBox", Type = typeof(TextBox))]
[TemplatePartAttribute(Name = "PART_Popup", Type = typeof(Popup))]
public class ComboBox : ItemsControl
{
}


En el ejemplo siguiente se muestra una plantilla ControlTemplate simplificada para el control ComboBox que incluye los elementos especificados por los objetos TemplatePartAttribute en la clase ComboBox.


<ControlTemplate TargetType="ComboBox">
  <Grid>
    <ToggleButton x:Name="DropDownToggle"
      HorizontalAlignment="Stretch" VerticalAlignment="Stretch"  
      Margin="-1" HorizontalContentAlignment="Right"
      IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,
                  RelativeSource={RelativeSource TemplatedParent}}">
      <Path x:Name="BtnArrow" Height="4" Width="8" 
        Stretch="Uniform" Margin="0,0,6,0"  Fill="Black"
        Data="F1 M 300,-190L 310,-190L 305,-183L 301,-190 Z " />
    </ToggleButton>
    <ContentPresenter x:Name="ContentPresenter" Margin="6,2,25,2"
      Content="{TemplateBinding SelectionBoxItem}"
      ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
      ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}">
    </ContentPresenter>
    <TextBox x:Name="PART_EditableTextBox"
      Style="{x:Null}"
      Focusable="False"
      Background="{TemplateBinding Background}"
      HorizontalAlignment="Left" 
      VerticalAlignment="Center" 
      Margin="3,3,23,3"
      Visibility="Hidden"
      IsReadOnly="{TemplateBinding IsReadOnly}"/>

    <Popup x:Name="PART_Popup"
      IsOpen="{TemplateBinding IsDropDownOpen}">
      <Border x:Name="PopupBorder" 
        HorizontalAlignment="Stretch" Height="Auto" 
        MinWidth="{TemplateBinding ActualWidth}"
        MaxHeight="{TemplateBinding MaxDropDownHeight}"
        BorderThickness="{TemplateBinding BorderThickness}" 
        BorderBrush="Black" Background="White" CornerRadius="3">
        <ScrollViewer x:Name="ScrollViewer" BorderThickness="0" Padding="1">
          <ItemsPresenter/>
        </ScrollViewer>
      </Border>
    </Popup>

  </Grid>
</ControlTemplate>


Estados del contrato de control

Los estados de un control también forman parte del contrato de control. En el ejemplo de creación de una plantilla ControlTemplate para un control Button se muestra cómo especificar la apariencia de Button según sus estados. Cree un VisualState para cada estado especificado y ponga todos los objetos VisualState que comparten un GroupName en un VisualStateGroup, tal y como se describe en Cambiar la apariencia de un control según su estado anteriormente en este tema. Los controles de otros fabricantes deben especificar los estados mediante TemplateVisualStateAttribute, que permite que las herramientas de diseño, como Expression Blend, expongan los estados del control para crear plantillas de control.

Para buscar el contrato de control de los controles que se incluyen con WPF, vea Estilos y plantillas de Control.

Propiedades del contrato de control

Las propiedades públicas que afectan visualmente al control también se incluyen en el contrato de control. Puede establecer estas propiedades para cambiar la apariencia del control sin crear una nueva plantilla ControlTemplate. También puede emplear la extensión de marcado TemplateBinding para enlazar propiedades de elementos que se encuentran en la plantilla ControlTemplate a propiedades públicas definidas por el control Button.

En el ejemplo siguiente se muestra el contrato de control del botón.


[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Pressed", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Unfocused", GroupName = "FocusStates")]
[TemplateVisualState(Name = "Focused", GroupName = "FocusStates")]
public class Button : ButtonBase
{
    public static readonly DependencyProperty BackgroundProperty;
    public static readonly DependencyProperty BorderBrushProperty;
    public static readonly DependencyProperty BorderThicknessProperty;
    public static readonly DependencyProperty ContentProperty;
    public static readonly DependencyProperty ContentTemplateProperty;
    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 object Content { get; set; }
    public DataTemplate ContentTemplate { 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; }
}


Al crear una plantilla ControlTemplate, con frecuencia resulta más fácil empezar con una plantilla ControlTemplate existente y modificarla. Para cambiar una plantilla ControlTemplate existente, puede realizar uno de los procedimientos siguientes:

En el ejemplo siguiente se muestra todo el control ButtonControlTemplate que se describe en este tema.


<StackPanel>
  <StackPanel.Resources>
    <Style TargetType="Button" x:Key="newTemplate">
      <!--Set the Background, Foreground, FontSize, Width, 
                  Height, Margin, and Template properties for
                  the Button.-->
      <Setter Property="Background" Value="Navy"/>
      <Setter Property="Foreground" Value="White"/>
      <Setter Property="FontSize" Value="14"/>
      <Setter Property="Width" Value="100"/>
      <Setter Property="Height" Value="40"/>
      <Setter Property="Margin" Value="10"/>
      <Setter Property="HorizontalContentAlignment" Value="Center"/>
      <Setter Property="VerticalContentAlignment" Value="Center"/>
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="Button">
            <Border x:Name="RootElement">
              <VisualStateManager.VisualStateGroups>

                <!--Define the states and transitions for the common states.
                    The states in the VisualStateGroup are mutually exclusive to
                    each other.-->
                <VisualStateGroup Name="CommonStates">

                  <!--Define the VisualTransitions that can be used when the control
                      transitions between VisualStates that are defined in the
                      VisualStatGroup.-->
                  <VisualStateGroup.Transitions>

                    <!--Take one hundredth of a second to transition to the
                        Pressed state.-->
                    <VisualTransition To="Pressed" 
                                      GeneratedDuration="0:0:0.01" />

                    <!--Take one half second to trasition to the MouseOver state.-->
                    <VisualTransition To="MouseOver" 
                                      GeneratedDuration="0:0:0.5" />

                    <!--Take one hundredth of a second to transition from the
                        Pressed state to the MouseOver state.-->
                    <VisualTransition From="Pressed" To="MouseOver" 
                                      GeneratedDuration="0:0:0.01" />

                    <!--Take one and a half seconds to transition from the
                        MouseOver state to the Normal state. 
                        Have the SolidColorBrush, BorderBrush, fade to blue, 
                        then to yellow, and then to black in that time.-->
                    <VisualTransition From="MouseOver" To="Normal" 
                                          GeneratedDuration="0:0:1.5">
                      <Storyboard>
                        <ColorAnimationUsingKeyFrames
                          Storyboard.TargetProperty="Color"
                          Storyboard.TargetName="BorderBrush"
                          FillBehavior="HoldEnd" >

                          <ColorAnimationUsingKeyFrames.KeyFrames>

                            <LinearColorKeyFrame Value="Blue" 
                              KeyTime="0:0:0.5" />
                            <LinearColorKeyFrame Value="Yellow" 
                              KeyTime="0:0:1" />
                            <LinearColorKeyFrame Value="Black" 
                              KeyTime="0:0:1.5" />

                          </ColorAnimationUsingKeyFrames.KeyFrames>
                        </ColorAnimationUsingKeyFrames>
                      </Storyboard>
                    </VisualTransition>
                  </VisualStateGroup.Transitions>

                  <!--The Normal state is the state the button is in
                      when it is not in another state from this VisualStateGroup.
                      There is no special visual behavior for this state, but
                      the VisualState must be defined in order for the button
                      to return to its initial state.-->
                  <VisualState x:Name="Normal" />

                  <!--Change the border of the button to red when the
                      mouse is over the button.-->
                  <VisualState x:Name="MouseOver">
                    <Storyboard>
                      <ColorAnimation Storyboard.TargetName="BorderBrush"     
                                      Storyboard.TargetProperty="Color"
                                      To="Red" />

                    </Storyboard>
                  </VisualState>

                  <!--Change the border of the button to Transparent when the
                      button is pressed.-->
                  <VisualState x:Name="Pressed">
                    <Storyboard >
                      <ColorAnimation Storyboard.TargetName="BorderBrush" 
                                      Storyboard.TargetProperty="Color" 
                                      To="Transparent" 
                                      />
                    </Storyboard>
                  </VisualState>

                  <!--Show the DisabledRect when the IsEnabled property on
                      the button is false.-->
                  <VisualState x:Name="Disabled">
                    <Storyboard>
                      <DoubleAnimation Storyboard.TargetName="DisabledRect" 
                                       Storyboard.TargetProperty="Opacity"
                                       To="1" Duration="0" />
                    </Storyboard>
                  </VisualState>
                </VisualStateGroup>

                <!--Define the states and transitions for the focus states.
                    The states in the VisualStateGroup are mutually exclusive to
                    each other.-->
                <VisualStateGroup x:Name="FocusStates">

                  <!--Define the VisualStates in this VistualStateGroup.-->
                  <VisualState x:Name="Focused">
                    <Storyboard>
                      <ObjectAnimationUsingKeyFrames 
                        Storyboard.TargetName="FocusVisual" 
                        Storyboard.TargetProperty="Visibility" Duration
                        ="0">

                        <DiscreteObjectKeyFrame KeyTime="0">
                          <DiscreteObjectKeyFrame.Value>
                            <Visibility>Visible</Visibility>
                          </DiscreteObjectKeyFrame.Value>
                        </DiscreteObjectKeyFrame>
                      </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                  </VisualState>
                  <VisualState x:Name="Unfocused">
                    <Storyboard>
                      <ObjectAnimationUsingKeyFrames 
                        Storyboard.TargetName="FocusVisual" 
                        Storyboard.TargetProperty="Visibility"
                        Duration="0">

                        <DiscreteObjectKeyFrame KeyTime="0">
                          <DiscreteObjectKeyFrame.Value>
                            <Visibility>Collapsed</Visibility>
                          </DiscreteObjectKeyFrame.Value>
                        </DiscreteObjectKeyFrame>
                      </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                  </VisualState>
                </VisualStateGroup>
              </VisualStateManager.VisualStateGroups>

              <!--Create the SolidColorBrush for the Background 
                  as an object elemment and give it a name so 
                  it can be referred to elsewhere in the control template.-->
              <Border.Background>
                <SolidColorBrush x:Name="BorderBrush" Color="Black"/>
              </Border.Background>

              <!--Create a border that has a different color by adding smaller grid.
                  The background of this grid is specified by the button's Background
                  property.-->
              <Grid Background="{TemplateBinding Background}" Margin="4">

                <!--Create a Rectangle that indicates that the
                    Button has focus.-->
                <Rectangle Name="FocusVisual" 
                           Visibility="Collapsed" Margin="2" 
                           Stroke="{TemplateBinding Foreground}" 
                           StrokeThickness="1" 
                           StrokeDashArray="1.5 1.5"/>

                <!--Use a ContentPresenter to display the Content of
                    the Button.-->
                <ContentPresenter
                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                  Margin="4,5,4,4" />

                <!--Create a rectangle that causes the button to appear
                    grayed out when it is disabled.-->
                <Rectangle x:Name="DisabledRect" 
                         Fill="#A5FFFFFF"
                         Opacity="0" IsHitTestVisible="false" />
              </Grid>
            </Border>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

  </StackPanel.Resources>

  <Button Style="{StaticResource newTemplate}" 
          Content="Button1"/>

  <Button Style="{StaticResource newTemplate}"
          Background="Purple" 
          Content="Button2" />
</StackPanel>


Mostrar: