Compartir a través de


¿Qué son los estilos y las plantillas?

Los estilos y plantillas de Windows Presentation Foundation (WPF) hacen referencia a un conjunto de características que permiten a los desarrolladores y diseñadores crear efectos visualmente atractivos y una apariencia coherente para su producto. Al personalizar la apariencia de una aplicación, quieres un modelo de plantillas y estilos sólido que permita el mantenimiento y el uso compartido de la apariencia dentro y entre las aplicaciones. WPF proporciona ese modelo.

Otra característica del modelo de estilos de WPF es la separación de la presentación y la lógica. Los diseñadores pueden trabajar en la apariencia de una aplicación usando solo XAML al mismo tiempo que los desarrolladores trabajan en la lógica de programación mediante C# o Visual Basic.

Esta información general se centra en los aspectos de estilo y plantillas de la aplicación y no analiza los conceptos de enlace de datos. Para obtener información sobre el enlace de datos, consulte Introducción al enlace de datos.

Es importante comprender los recursos, que son los que permiten reutilizar estilos y plantillas. Para obtener más información sobre los recursos, consulta Introducción a los recursos XAML.

Ejemplo

El código de ejemplo proporcionado en esta información general se basa en una aplicación de exploración de fotos sencilla que se muestra en la ilustración siguiente.

Vista de lista con estilo

Este sencillo ejemplo de foto usa estilos y plantillas para crear una experiencia de usuario visualmente atractiva. El ejemplo tiene dos TextBlock elementos y un ListBox control enlazado a una lista de imágenes.

Para obtener el ejemplo completo, vea Introduction to Style and Templating Sample(Introducción al estilo y el ejemplo de plantillas).

Estilos

Puede pensar en un Style como una manera conveniente de aplicar un conjunto de valores de propiedad a varios elementos. Puede usar un estilo en cualquier elemento que derive de FrameworkElement o FrameworkContentElement, como un Window o un Button.

La forma más común de declarar un estilo es como un recurso en la Resources sección de un archivo XAML. Dado que los estilos son recursos, cumplen las mismas reglas de ámbito que se aplican a todos los recursos. En pocas palabras, donde declaras un estilo afecta dónde se puede aplicar el estilo. Por ejemplo, si declaras el estilo en el elemento raíz del archivo XAML de definición de aplicación, el estilo se puede usar en cualquier parte de la aplicación.

Por ejemplo, el siguiente código XAML declara dos estilos para un TextBlock, uno aplicado automáticamente a todos los TextBlock elementos y otro al que se debe hacer referencia explícitamente.

<Window.Resources>
    <!-- .... other resources .... -->

    <!--A Style that affects all TextBlocks-->
    <Style TargetType="TextBlock">
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="FontFamily" Value="Comic Sans MS"/>
        <Setter Property="FontSize" Value="14"/>
    </Style>
    
    <!--A Style that extends the previous TextBlock Style with an x:Key of TitleText-->
    <Style BasedOn="{StaticResource {x:Type TextBlock}}"
           TargetType="TextBlock"
           x:Key="TitleText">
        <Setter Property="FontSize" Value="26"/>
        <Setter Property="Foreground">
            <Setter.Value>
                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                    <LinearGradientBrush.GradientStops>
                        <GradientStop Offset="0.0" Color="#90DDDD" />
                        <GradientStop Offset="1.0" Color="#5BFFFF" />
                    </LinearGradientBrush.GradientStops>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

Este es un ejemplo de los estilos declarados anteriormente que se usan.

<StackPanel>
    <TextBlock Style="{StaticResource TitleText}" Name="textblock1">My Pictures</TextBlock>
    <TextBlock>Check out my new pictures!</TextBlock>
</StackPanel>

Bloques de texto con estilo

Para obtener más información, vea Crear un estilo para un control.

ControlTemplates

En WPF, el ControlTemplate de un control define la apariencia del control. Puede cambiar la estructura y la apariencia de un control mediante la definición de un nuevo ControlTemplate control y su asignación a un control. En muchos casos, las plantillas proporcionan suficiente flexibilidad para que no tenga que escribir sus propios controles personalizados.

Cada control tiene asignada una plantilla predeterminada a la propiedad Control.Template . La plantilla conecta la presentación visual del control con las funcionalidades del control. Dado que defines una plantilla en XAML, puedes cambiar la apariencia del control sin escribir ningún código. Cada plantilla está diseñada para un control específico, como .Button

Normalmente, declaras una plantilla como un recurso en la Resources sección de un archivo XAML. Al igual que con todos los recursos, se aplican reglas de ámbito.

Las plantillas de control son mucho más implicadas que un estilo. Esto se debe a que la plantilla de control modifica la apariencia visual de todo el control, mientras que un estilo simplemente aplica cambios en las propiedades del control existente. Sin embargo, dado que la plantilla de un control se aplica estableciendo la propiedad Control.Template , puede usar un estilo para definir o establecer una plantilla.

Los diseñadores generalmente le permiten crear una copia de una plantilla existente y modificarla. Por ejemplo, en el diseñador wpF de Visual Studio, seleccione un CheckBox control y, a continuación, haga clic con el botón derecho y seleccione Editar plantilla>Crear una copia. Este comando genera un estilo que define una plantilla.

<Style x:Key="CheckBoxStyle1" TargetType="{x:Type CheckBox}">
    <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual1}"/>
    <Setter Property="Background" Value="{StaticResource OptionMark.Static.Background1}"/>
    <Setter Property="BorderBrush" Value="{StaticResource OptionMark.Static.Border1}"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type CheckBox}">
                <Grid x:Name="templateRoot" Background="Transparent" SnapsToDevicePixels="True">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Border x:Name="checkBoxBorder" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                        <Grid x:Name="markGrid">
                            <Path x:Name="optionMark" Data="F1 M 9.97498,1.22334L 4.6983,9.09834L 4.52164,9.09834L 0,5.19331L 1.27664,3.52165L 4.255,6.08833L 8.33331,1.52588e-005L 9.97498,1.22334 Z " Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="1" Opacity="0" Stretch="None"/>
                            <Rectangle x:Name="indeterminateMark" Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="2" Opacity="0"/>
                        </Grid>
                    </Border>
                    <ContentPresenter x:Name="contentPresenter" Grid.Column="1" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasContent" Value="true">
                        <Setter Property="FocusVisualStyle" Value="{StaticResource OptionMarkFocusVisual1}"/>
                        <Setter Property="Padding" Value="4,-1,0,0"/>

... content removed to save space ...

Editar una copia de una plantilla es una excelente manera de aprender cómo funcionan las plantillas. En lugar de crear una nueva plantilla en blanco, es más fácil editar una copia y cambiar algunos aspectos de la presentación visual.

Para obtener un ejemplo, consulte Creación de una plantilla para un control.

TemplateBinding

Es posible que haya observado que el recurso de plantilla definido en la sección anterior usa la extensión de marcado TemplateBinding. Un TemplateBinding es una forma optimizada de un enlace para escenarios de plantilla, análogo a un enlace construido con {Binding RelativeSource={RelativeSource TemplatedParent}}. TemplateBinding resulta útil para enlazar partes de la plantilla a las propiedades del control. Por ejemplo, cada control tiene una BorderThickness propiedad . Utiliza un TemplateBinding para administrar qué elemento de la plantilla se ve afectado por esta configuración de control.

ContentControl y ItemsControl

Si se declara ContentPresenter en ControlTemplate de un ContentControl, el ContentPresenter se enlazará automáticamente a las propiedades ContentTemplate y Content. Del mismo modo, un ItemsPresenter que está en el ControlTemplate de un ItemsControl se enlazará automáticamente a las propiedades ItemTemplate y Items.

DataTemplates

En esta aplicación de ejemplo, hay un ListBox control enlazado a una lista de fotos.

<ListBox ItemsSource="{Binding Source={StaticResource MyPhotos}}"
         Background="Silver" Width="600" Margin="10" SelectedIndex="0"/>

Esto ListBox actualmente se ve de la siguiente manera.

ListBox antes de aplicar la plantilla

La mayoría de los controles tienen algún tipo de contenido y ese contenido a menudo procede de los datos a los que se enlaza. En este ejemplo, los datos son la lista de fotos. En WPF, se utiliza un DataTemplate para definir la representación visual de los datos. Básicamente, lo que se coloca en un DataTemplate determina el aspecto de los datos en la aplicación representada.

En nuestra aplicación de ejemplo, cada objeto personalizado Photo tiene una Source propiedad de tipo cadena que especifica la ruta de acceso del archivo de la imagen. Actualmente, los objetos de foto aparecen como rutas de acceso de archivo.

public class Photo
{
    public Photo(string path)
    {
        Source = path;
    }

    public string Source { get; }

    public override string ToString() => Source;
}
Public Class Photo
    Sub New(ByVal path As String)
        Source = path
    End Sub

    Public ReadOnly Property Source As String

    Public Overrides Function ToString() As String
        Return Source
    End Function
End Class

Para que las fotos aparezcan como imágenes, se crea un recurso DataTemplate.

<Window.Resources>
    <!-- .... other resources .... -->

    <!--DataTemplate to display Photos as images
    instead of text strings of Paths-->
    <DataTemplate DataType="{x:Type local:Photo}">
        <Border Margin="3">
            <Image Source="{Binding Source}"/>
        </Border>
    </DataTemplate>
</Window.Resources>

Observe que la DataType propiedad es similar a la TargetType propiedad de .Style Si tu DataTemplate está en la sección de recursos, cuando se especifica la propiedad DataType en un tipo y se omite un x:Key, se aplica el DataTemplate cada vez que aparece ese tipo. Siempre tiene la opción de asignar DataTemplate con x:Key y luego establecerlo como StaticResource para las propiedades que aceptan tipos DataTemplate, como la propiedad ItemTemplate o la propiedad ContentTemplate.

Básicamente, en el ejemplo anterior DataTemplate se define que cada vez que hay un objeto Photo, debe aparecer como un elemento Image dentro de un Border. Con este DataTemplate, nuestra aplicación ahora tiene este aspecto.

Imagen de foto

El modelo de plantillas de datos proporciona otras características. Por ejemplo, si muestra datos de recopilación que contienen otras colecciones mediante un tipo HeaderedItemsControl como Menu o TreeView, existe el HierarchicalDataTemplate. Otra característica de plantillas de datos es DataTemplateSelector, que permite elegir un DataTemplate para usarlo en función de la lógica personalizada. Para obtener más información, consulte Información general sobre plantillas de datos, que proporciona una explicación más detallada de las distintas características de plantillas de datos.

Desencadenadores

Un desencadenador establece propiedades o inicia acciones, como una animación, cuando cambia un valor de propiedad o cuando se genera un evento. Style, ControlTemplatey DataTemplate tienen una Triggers propiedad que puede contener un conjunto de desencadenadores. Hay varios tipos de desencadenadores.

PropertyTriggers

Un Trigger que establece valores de propiedad o inicia acciones basadas en el valor de una propiedad se denomina desencadenador de propiedad.

Para demostrar cómo usar desencadenadores de propiedades, puede hacer que cada uno ListBoxItem sea parcialmente transparente a menos que esté seleccionado. El estilo siguiente establece el Opacity valor de en ListBoxItem0.5. Sin embargo, cuando la IsSelected propiedad es true, el Opacity se establece en 1.0.

<Window.Resources>
    <!-- .... other resources .... -->

    <Style TargetType="ListBoxItem">
        <Setter Property="Opacity" Value="0.5" />
        <Setter Property="MaxHeight" Value="75" />
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Trigger.Setters>
                    <Setter Property="Opacity" Value="1.0" />
                </Trigger.Setters>
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>

En este ejemplo, se usa Trigger para establecer un valor de propiedad. Pero tenga en cuenta que la clase Trigger también tiene las propiedades EnterActions y ExitActions que permiten que un desencadenador realice acciones.

Tenga en cuenta que la propiedad de la MaxHeight está establecida en ListBoxItem. En la ilustración siguiente, el tercer elemento es el elemento seleccionado.

Vista de lista con estilo

EventTriggers y Storyboards

Otro tipo de desencadenador es EventTrigger, que inicia un conjunto de acciones en función de la aparición de un evento. Por ejemplo, los objetos siguientes EventTrigger especifican que cuando el puntero del ratón entra en ListBoxItem, la propiedad MaxHeight se anima a un valor de 90 en un periodo de 0.2 segundos. Cuando el mouse se aleja del elemento, la propiedad vuelve al valor original durante un período de 1 segundo. Observe cómo no es necesario especificar un To valor para la MouseLeave animación. Esto se debe a que la animación es capaz de realizar un seguimiento del valor original.

<Style.Triggers>
    <Trigger Property="IsSelected" Value="True">
        <Trigger.Setters>
            <Setter Property="Opacity" Value="1.0" />
        </Trigger.Setters>
    </Trigger>
    <EventTrigger RoutedEvent="Mouse.MouseEnter">
        <EventTrigger.Actions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                        Duration="0:0:0.2"
                        Storyboard.TargetProperty="MaxHeight"
                        To="90"  />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger.Actions>
    </EventTrigger>
    <EventTrigger RoutedEvent="Mouse.MouseLeave">
        <EventTrigger.Actions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                        Duration="0:0:1"
                        Storyboard.TargetProperty="MaxHeight"  />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger.Actions>
    </EventTrigger>
</Style.Triggers>

Para obtener más información, consulte información general sobre guiones gráficos.

En la siguiente ilustración, el mouse apunta al tercer elemento.

Captura de pantalla de ejemplo de estilo

MultiTriggers, DataTriggers y MultiDataTriggers

Además de Trigger y EventTrigger, hay otros tipos de desencadenadores. MultiTrigger permite establecer valores de propiedad en función de varias condiciones. Usa DataTrigger y MultiDataTrigger cuando la propiedad de tu condición está vinculada a datos.

Estados visuales

Los controles siempre están en un estado específico. Por ejemplo, cuando el mouse se mueve sobre la superficie de un control, el control se considera que está en un estado común de MouseOver. Un control sin un estado específico se considera que está en estado común Normal . Los estados se dividen en grupos y los estados mencionados anteriormente forman parte del grupo de estados CommonStates. La mayoría de los controles tienen dos grupos de estado: CommonStates y FocusStates. De cada grupo de estados aplicado a un control, un control siempre está en un estado de cada grupo, como CommonStates.MouseOver y FocusStates.Unfocused. Sin embargo, un control no puede estar en dos estados diferentes dentro del mismo grupo, como CommonStates.Normal y CommonStates.Disabled. Esta es una tabla de estados que la mayoría de los controles reconocen y usan.

Nombre de VisualState Nombre del grupo de estado visual Descripción
Normal CommonStates El estado predeterminado.
MouseOver CommonStates El puntero del mouse se coloca sobre el control.
Pressed CommonStates Se presiona el botón.
Disabled CommonStates El control está deshabilitado.
Focused FocusStates El control tiene el enfoque.
Unfocused FocusStates El control no tiene el foco.

Al definir un System.Windows.VisualStateManager elemento en el elemento raíz de una plantilla de control, puede desencadenar animaciones cuando un control entra en un estado específico. VisualStateManager declara qué combinaciones de VisualStateGroup y VisualState se van a observar. Cuando el control entra en un estado observado, se inicia la animación definida por el VisualStateManager.

Por ejemplo, el siguiente código XAML inspecciona el CommonStates.MouseOver estado para animar el color de relleno del elemento denominado backgroundElement. Cuando el control vuelve al CommonStates.Normal estado, se restaura el color de relleno del elemento denominado backgroundElement .

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

        ...

Para obtener más información sobre guiones gráficos, consulte Información general sobre guiones gráficos.

Recursos compartidos y temas

Una aplicación de WPF típica podría tener varios recursos de interfaz de usuario que se aplican en toda la aplicación. Colectivamente, este conjunto de recursos se puede considerar el tema de la aplicación. WPF proporciona compatibilidad con el empaquetado de recursos de interfaz de usuario como tema mediante un diccionario de recursos que se encapsula como la ResourceDictionary clase .

Los temas de WPF se definen mediante el mecanismo de estilos y plantillas que WPF expone para personalizar los objetos visuales de cualquier elemento.

Los recursos de temas de WPF se almacenan en diccionarios de recursos incrustados. Estos diccionarios de recursos se deben incrustar dentro de un ensamblado firmado y se pueden incrustar en el mismo ensamblado que el propio código o en un ensamblado en paralelo. Para PresentationFramework.dll, el ensamblado que contiene controles WPF, los recursos de tema se encuentran en una serie de ensamblados en paralelo.

El tema se convierte en el último lugar que se debe buscar al buscar el estilo de un elemento. Normalmente, la búsqueda comenzará por caminar por el árbol de elementos que busca un recurso adecuado y, a continuación, buscará en la colección de recursos de la aplicación y, por último, consultará el sistema. Esto permite a los desarrolladores de aplicaciones volver a definir el estilo de cualquier objeto en el nivel de árbol o aplicación antes de alcanzar el tema.

Puede definir diccionarios de recursos como archivos individuales que le permiten reutilizar un tema en varias aplicaciones. También puede crear temas intercambiables definiendo varios diccionarios de recursos que proporcionan los mismos tipos de recursos, pero con valores diferentes. La redefinición de estos estilos u otros recursos en el nivel de aplicación es el enfoque recomendado para el skinning de una aplicación.

Para compartir un conjunto de recursos, incluidos estilos y plantillas, entre aplicaciones, puedes crear un archivo XAML y definir un ResourceDictionary que incluya referencia a un shared.xaml archivo.

<ResourceDictionary.MergedDictionaries>
  <ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>

Es el uso compartido de shared.xaml, que por sí mismo define un ResourceDictionary que contiene un conjunto de recursos de estilo y pincel, lo que permite que los controles en una aplicación tengan un aspecto coherente.

Para obtener más información, consulte diccionarios de recursos combinados.

Si va a crear un tema para el control personalizado, consulte la sección Definición de recursos en el nivel de tema de la información general sobre la creación de controles.

Consulte también