Creación de una plantilla de un controlCreate a template for a control

Con Windows Presentation Foundation (WPF), puede usar su propia plantilla reutilizable para personalizar la estructura visual y el comportamiento de un control existente.With Windows Presentation Foundation (WPF), you can customize an existing control's visual structure and behavior with your own reusable template. Las plantillas se pueden aplicar de forma global a la aplicación, las ventanas y las páginas, o bien directamente a los controles.Templates can be applied globally to your application, windows and pages, or directly to controls. La mayoría de los escenarios en los que es necesario crear un control se pueden solventar creando en su lugar una plantilla para un control existente.Most scenarios that require you to create a new control can be covered by instead creating a new template for an existing control.

Importante

La documentación de la guía de escritorio está en fase de construcción.The Desktop Guide documentation is under construction.

En este artículo, veremos cómo crear un objeto ControlTemplate para el control Button.In this article, you'll explore creating a new ControlTemplate for the Button control.

Cuándo crear un objeto ControlTemplateWhen to create a ControlTemplate

Los controles tienen muchas propiedades, como Background, Foreground y FontFamily.Controls have many properties, such as Background, Foreground, and FontFamily. Estas propiedades se encargan de distintos aspectos relacionados con la apariencia del control, pero los cambios que se pueden realizar configurando estas propiedades son limitados.These properties control different aspects of the control's appearance, but the changes that you can make by setting these properties are limited. Por ejemplo, la propiedad Foreground se puede establecer en azul y FontStyle en cursiva en un objeto CheckBox.For example, you can set the Foreground property to blue and FontStyle to italic on a CheckBox. Si queremos personalizar la apariencia del control de forma diferente a como lo hace la configuración de las demás propiedades del control, hay que crear un objeto ControlTemplate.When you want to customize the control's appearance beyond what setting the other properties on the control can do, you create a ControlTemplate.

En la mayoría de las interfaces de usuario, los botones tienen la misma apariencia en general: un rectángulo con texto.In most user interfaces, a button has the same general appearance: a rectangle with some text. Si quisiéramos crear un botón redondeado, podríamos crear un control que herede del botón o que vuelva a crear la funcionalidad del botónIf you wanted to create a rounded button, you could create a new control that inherits from the button or recreates the functionality of the button. y que, además, aporte el elemento visual circular.In addition, the new user control would provide the circular visual.

Para no tener que crear más controles, se puede personalizar el diseño visual de un control existente.You can avoid creating new controls by customizing the visual layout of an existing control. En el caso del botón redondeado, hay que crear un objeto ControlTemplate con el diseño visual que buscamos.With a rounded button, you create a ControlTemplate with the desired visual layout.

Por otra parte, si necesitamos un control con nueva funcionalidad, con propiedades diferentes y con nuevas configuraciones, crearíamos un objeto UserControl.On the other hand, if you need a control with new functionality, different properties, and new settings, you would create a new UserControl.

Requisitos previosPrerequisites

Cree una aplicación de WPF y, en MainWindow.xaml (o en otra ventana de su elección), configure las siguientes propiedades en el elemento <Window> :Create a new WPF application and in MainWindow.xaml (or another window of your choice) set the following properties on the <Window> element:

Title Template Intro Sample
SizeToContent WidthAndHeight
MinWidth 250

Configure el contenido del elemento <Window> en el siguiente código XAML:Set the content of the <Window> element to the following XAML:

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button>Button 2</Button>
</StackPanel>

Al final del proceso, el archivo MainWindow.xaml debe tener un aspecto similar al siguiente:In the end, the MainWindow.xaml file should look similar to the following:

<Window x:Class="IntroToStylingAndTemplating.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:IntroToStylingAndTemplating"
        mc:Ignorable="d"
        Title="Template Intro Sample" SizeToContent="WidthAndHeight" MinWidth="250">
    <StackPanel Margin="10">
        <Label>Unstyled Button</Label>
        <Button>Button 1</Button>
        <Label>Rounded Button</Label>
        <Button>Button 2</Button>
    </StackPanel>
</Window>

Si la aplicación se ejecuta, tendrá el siguiente aspecto:If you run the application, it looks like the following:

Ventana de WPF con dos botones sin estilo

Creación de una clase ControlTemplateCreate a ControlTemplate

La forma más común de declarar un objeto ControlTemplate es como un recurso en la sección Resources de un archivo XAML.The most common way to declare a ControlTemplate is as a resource in the Resources section in a XAML file. Dado que las plantillas son recursos, siguen las mismas reglas de ámbito que se aplican a todos los recursos.Because templates are resources, they obey the same scoping rules that apply to all resources. Simplemente, la ubicación de la declaración de una plantilla afecta a dónde se puede aplicar la plantilla.Put simply, where you declare a template affects where the template can be applied. Por ejemplo, si declara una plantilla en el elemento raíz del archivo XAML de definición de la aplicación, puede usar en cualquier parte de esta.For example, if you declare the template in the root element of your application definition XAML file, the template can be used anywhere in your application. Si la plantilla se define en una ventana, solo los controles de esa ventana podrán usarla.If you define the template in a window, only the controls in that window can use the template.

Para empezar, agregue un elemento Window.Resources al archivo MainWindow.xaml:To start with, add a Window.Resources element to your MainWindow.xaml file:

<Window x:Class="IntroToStylingAndTemplating.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:IntroToStylingAndTemplating"
        mc:Ignorable="d"
        Title="Template Intro Sample" SizeToContent="WidthAndHeight" MinWidth="250">
    <Window.Resources>
        
    </Window.Resources>
    <StackPanel Margin="10">
        <Label>Unstyled Button</Label>
        <Button>Button 1</Button>
        <Label>Rounded Button</Label>
        <Button>Button 2</Button>
    </StackPanel>
</Window>

Cree un valor <ControlTemplate> nuevo con las propiedades siguientes:Create a new <ControlTemplate> with the following properties set:

x:Keyx:Key roundbutton
TargetType Button

Esta plantilla de control será sencilla:This control template will be simple:

  • Un elemento raíz del control, un elemento Grida root element for the control, a Grid
  • Un elemento Ellipse para trazar el aspecto redondeado del botónan Ellipse to draw the rounded appearance of the button
  • Un elemento ContentPresenter para mostrar el contenido del botón especificado por el usuarioa ContentPresenter to display the user-specified button content
<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

TemplateBindingTemplateBinding

Al crear un elemento ControlTemplate, puede que quiera seguir usando las propiedades públicas para cambiar la apariencia del control.When you create a new ControlTemplate, you still might want to use the public properties to change the control's appearance. La extensión de marcado TemplateBinding enlaza una propiedad de un elemento que está en el elemento ControlTemplate a una propiedad pública que está definida por el control.The TemplateBinding markup extension binds a property of an element that is in the ControlTemplate to a public property that is defined by the control. Cuando se usa TemplateBinding, se habilitan propiedades en el control que actúan como parámetros de la plantilla.When you use a TemplateBinding, you enable properties on the control to act as parameters to the template. Es decir, cuando se establece una propiedad de un control, ese valor se pasa al elemento que tiene la extensión TemplateBinding.That is, when a property on a control is set, that value is passed on to the element that has the TemplateBinding on it.

EllipseEllipse

Cabe decir que las propiedades Fill y Stroke del elemento <Ellipse> están enlazadas a las propiedades Foreground y Background del control.Notice that the Fill and Stroke properties of the <Ellipse> element are bound to the control's Foreground and Background properties.

ContentPresenterContentPresenter

También se agrega a la plantilla un elemento <ContentPresenter>.A <ContentPresenter> element is also added to the template. Dado que esta plantilla está diseñada para un botón, tenga en cuenta que el botón hereda de ContentControl.Because this template is designed for a button, take into consideration that the button inherits from ContentControl. El botón presenta el contenido del elemento.The button presents the content of the element. Se puede establecer cualquier elemento dentro del botón, como texto sin formato o incluso otro control.You can set anything inside of the button, such as plain text or even another control. Los dos botones siguientes son válidos:Both of the following are valid buttons:

<Button>My Text</Button>

<!-- and -->

<Button>
    <CheckBox>Checkbox in a button</CheckBox>
</Button>

En estos dos ejemplos, el texto y la casilla se configuran como la propiedad Button.Content.In both of the previous examples, the text and the checkbox are set as the Button.Content property. Lo que se establezca como contenido se puede mostrar a través de un elemento <ContentPresenter> , que es lo que la plantilla hace.Whatever is set as the content can be presented through a <ContentPresenter>, which is what the template does.

Si el elemento ControlTemplate se aplica a un tipo ContentControl, como un elemento Button, se busca un elemento ContentPresenter en el árbol de elementos.If the ControlTemplate is applied to a ContentControl type, such as a Button, a ContentPresenter is searched for in the element tree. Si se halla el elemento ContentPresenter, la plantilla enlaza automáticamente la propiedad Content del control a ContentPresenter.If the ContentPresenter is found, the template automatically binds the control's Content property to the ContentPresenter.

Uso de la plantillaUse the template

Busque los botones que se declararon al principio de este artículo.Find the buttons that were declared at the start of this article.

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button>Button 2</Button>
</StackPanel>

Establezca la propiedad Template del segundo botón en el recurso roundbutton:Set the second button's Template property to the roundbutton resource:

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button Template="{StaticResource roundbutton}">Button 2</Button>
</StackPanel>

Si ejecuta el proyecto y observa el resultado, verá que el botón tiene un fondo redondeado.If you run the project and look at the result, you'll see that the button has a rounded background.

Ventana de WPF con un botón ovalado de plantilla

Posiblemente se haya dado cuenta de que el botón no es un círculo perfecto, sino que está distorsionado.You may have noticed that the button isn't a circle but is skewed. El elemento <Ellipse> funciona de forma que se expande para rellenar el espacio disponible.Because of the way the <Ellipse> element works, it always expands to fill the available space. Para que el círculo sea uniforme, cambie las propiedades width y height del botón al mismo valor:Make the circle uniform by changing the button's width and height properties to the same value:

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button Template="{StaticResource roundbutton}" Width="65" Height="65">Button 2</Button>
</StackPanel>

Ventana de WPF con un botón circular de plantilla

Incorporación de un desencadenadorAdd a Trigger

Aunque un botón que tiene una plantilla aplicada tiene un aspecto diferente, se comporta igual que cualquier otro botón.Even though a button with a template applied looks different, it behaves the same as any other button. Si se presiona, se activa el evento Click.If you press the button, the Click event fires. Pese a ello, posiblemente se haya dado cuenta de que, al mover el ratón por el botón, sus elementos visuales no cambian.However, you may have noticed that when you move your mouse over the button, the button's visuals don't change. Todas estas interacciones visuales están definidas por la plantilla.These visual interactions are all defined by the template.

Gracias a los sistemas de propiedades y eventos dinámicos que WPF proporciona, se puede ver una propiedad específica de un valor y, después, volver a aplicar el estilo de la plantilla cuando sea necesario.With the dynamic event and property systems that WPF provides, you can watch a specific property for a value and then restyle the template when appropriate. En este ejemplo, veremos la propiedad IsMouseOver del botón.In this example, you'll watch the button's IsMouseOver property. Cuando el mouse esté sobre el control, aplique el estilo <Ellipse> con un nuevo color.When the mouse is over the control, style the <Ellipse> with a new color. Este tipo de desencadenador se conoce como PropertyTrigger.This type of trigger is known as a PropertyTrigger.

Para que funcione, hay que agregar un nombre al elemento <Ellipse> al que poder hacer referencia.For this to work, you'll need to add a name to the <Ellipse> that you can reference. Asígnele el nombre backgroundElement.Give it the name of backgroundElement.

<Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />

Después, agregue un nuevo elemento Trigger a la colección ControlTemplate.Triggers.Next, add a new Trigger to the ControlTemplate.Triggers collection. El desencadenador inspeccionará el evento IsMouseOver en busca del valor true.The trigger will watch the IsMouseOver event for the value true.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="true">

        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Después, agregue un elemento <Setter> al elemento <Trigger> , que cambia la propiedad Fill de <Ellipse> a un nuevo color.Next, add a <Setter> to the <Trigger> that changes the Fill property of the <Ellipse> to a new color.

<Trigger Property="IsMouseOver" Value="true">
    <Setter Property="Fill" TargetName="backgroundElement" Value="AliceBlue"/>
</Trigger>

Ejecute el proyecto.Run the project. Fíjese en que, al mover el mouse por el botón, el color de <Ellipse> cambia.Notice that when you move the mouse over the button, the color of the <Ellipse> changes.

El ratón se mueve por el botón de WPF para cambiar el color de relleno

Uso de un elemento VisualStateUse a VisualState

Los estados visuales se definen y desencadenan a través de un control.Visual states are defined and triggered by a control. Por ejemplo, cuando el ratón se mueve por el control, se desencadena el estado CommonStates.MouseOver.For example, when the mouse is moved on top of the control, the CommonStates.MouseOver state is triggered. Los cambios de propiedad se pueden animar en función del estado actual del control.You can animate property changes based on the current state of the control. En la sección anterior, usamos un elemento <PropertyTrigger> para cambiar el primer plano del botón a AliceBlue cuando la propiedad IsMouseOver era true.In the previous section, a <PropertyTrigger> was used to change the foreground of the button to AliceBlue when the IsMouseOver property was true. Esta vez, cree un estado visual que anime el cambio de este color a través de una transición suave.Instead, create a visual state that animates the change of this color, providing a smooth transition. Para más información sobre los elementos VisualState, vea Estilos y plantillas en WPF.For more information about VisualStates, see Styles and templates in WPF.

Para convertir <PropertyTrigger> a un estado visual animado, quite en primer lugar el elemento <ControlTemplate.Triggers> de la plantilla.To convert the <PropertyTrigger> to an animated visual state, First, remove the <ControlTemplate.Triggers> element from your template.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

A continuación, en la raíz <Grid> de la plantilla de control, agregue el elemento <VisualStateManager.VisualStateGroups> con un <VisualStateGroup> para CommonStates.Next, in the <Grid> root of the control template, add the <VisualStateManager.VisualStateGroups> element with a <VisualStateGroup> for CommonStates. Defina dos estados, Normal y MouseOver.Define two states, Normal and MouseOver.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                </VisualState>
                <VisualState Name="MouseOver">
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

Cuando ese estado se desencadene, se aplicarán las animaciones definidas en el elemento <VisualState> .Any animations defined in a <VisualState> are applied when that state is triggered. Cree animaciones para cada estado.Create animations for each state. Las animaciones se colocan en un elemento <Storyboard> .Animations are put inside of a <Storyboard> element. Para obtener más información sobre los guiones gráficos, vea Información general sobre guiones gráficos.For more information about storyboards, see Storyboards Overview.

  • NormalNormal

    Este estado anima el relleno de la elipse y lo restaura al color Background del control.This state animates the ellipse fill, restoring it to the control's Background color.

    <Storyboard>
        <ColorAnimation Storyboard.TargetName="backgroundElement" 
            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
            To="{TemplateBinding Background}"
            Duration="0:0:0.3"/>
    </Storyboard>
    
  • MouseOverMouseOver

    Este estado anima el color Background de la elipse hacia un nuevo color: Yellow.This state animates the ellipse Background color to a new color: Yellow.

    <Storyboard>
        <ColorAnimation Storyboard.TargetName="backgroundElement" 
            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 
            To="Yellow" 
            Duration="0:0:0.3"/>
    </Storyboard>
    

<ControlTemplate> debería tener ahora un aspecto similar al siguiente.The <ControlTemplate> should now look like the following.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                    <Storyboard>
                        <ColorAnimation Storyboard.TargetName="backgroundElement" 
                            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                            To="{TemplateBinding Background}"
                            Duration="0:0:0.3"/>
                    </Storyboard>
                </VisualState>
                <VisualState Name="MouseOver">
                    <Storyboard>
                        <ColorAnimation Storyboard.TargetName="backgroundElement" 
                            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 
                            To="Yellow" 
                            Duration="0:0:0.3"/>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Ellipse Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter x:Name="contentPresenter" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

Ejecute el proyecto.Run the project. Fíjese en que, al mover el mouse por el botón, el color de <Ellipse> se cobra movimiento.Notice that when you move the mouse over the button, the color of the <Ellipse> animates.

El ratón se mueve por el botón de WPF para cambiar el color de relleno

Pasos siguientesNext steps