Información general sobre plantillas de datos
Actualización: noviembre 2007
El modelo de plantillas de datos de WPF proporciona gran flexibilidad para definir la presentación de los datos. Los controles de WPF tienen funcionalidad integrada para admitir la personalización de la presentación de datos. Este tema muestra primero cómo definir un objeto DataTemplate y, a continuación, presenta otras características de las plantillas de datos, tales como la selección de plantillas en función de lógica personalizada y la compatibilidad para la presentación de datos jerárquicos.
Este tema contiene las secciones siguientes.
- Requisitos previos
- Conceptos básicos sobre plantillas de datos
- Agregar a la plantilla de datos
- Elegir una plantilla de datos en función de propiedades del objeto de datos
- Estilos y plantillas para ItemsControl
- Compatibilidad con datos jerárquicos
- Temas relacionados
Requisitos previos
Este tema se centra en las características de plantillas de datos y no es una introducción a los conceptos de enlace de datos. Para obtener información sobre los conceptos básicos de enlace de datos, vea Información general sobre el enlace de datos.
DataTemplate está relacionado con la presentación de datos y es una de las diversas características que proporciona el modelo de estilos y plantillas de WPF. Para ver una introducción al modelo de estilos y plantillas de WPF, por ejemplo cómo utilizar un objeto Style para establecer propiedades de controles, vea el tema Aplicar estilos y plantillas.
Además, es importante tener un conocimiento sobre Resources, que son esencialmente los que permiten que objetos tales como Style y DataTemplate sean reutilizables. Para obtener más información sobre recursos, consulte Información general sobre recursos.
Conceptos básicos sobre plantillas de datos
Este tema contiene las subsecciones siguientes.
- Sin DataTemplate
- Definir una plantilla de datos simple
- Crear la plantilla de datos como un recurso
- La propiedad DataType
Para mostrar por qué DataTemplate es importante, examinaremos un ejemplo de enlace de datos. En este ejemplo, tenemos un objeto ListBox que se enlaza a una lista de objetos Task. Cada objeto Task tiene TaskName (cadena), Description (cadena), Priority (int) y una propiedad de tipo TaskType, que es una Enum con valores Home y Work.
<Window x:Class="SDKSample.Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SDKSample"
Title="Introduction to Data Templating Sample">
<Window.Resources>
<local:Tasks x:Key="myTodoList"/>
...
</Window.Resources>
<StackPanel>
<TextBlock Name="blah" FontSize="20" Text="My Task List:"/>
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"/>
...
</StackPanel>
</Window>
Sin DataTemplate
Sin un objeto DataTemplate, nuestro objeto ListBox es similar, actualmente, a:
Lo que ocurre es que, sin ninguna instrucción concreta, el objeto ListBox llama de forma predeterminada a ToString al intentar mostrar los objetos de la colección. Por consiguiente, si el objeto Task reemplaza el método ToString, el objeto ListBox muestra la representación de cadena de cada objeto de origen de la colección subyacente.
Por ejemplo, si la clase Task reemplaza el método ToString de esta manera, donde name es el campo para la propiedad TaskName:
public override string ToString()
{
return name.ToString();
}
Entonces, el objeto ListBox tiene un aspecto similar al siguiente:
Sin embargo, eso resulta limitante e inflexible. Además, si se está enlazando a datos XML, no podría reemplazar ToString.
Definir una plantilla de datos simple
La solución es definir un objeto DataTemplate. Una forma de hacerlo es establecer la propiedad ItemTemplate del objeto ListBox en un objeto DataTemplate. Lo que especifique en el objeto DataTemplate se convertirá en la estructura visual del objeto de datos. El objeto DataTemplate siguiente es bastante simple. Estamos proporcionando instrucciones para que cada elemento aparezca como tres elementos TextBlock dentro de un objeto StackPanel. Cada elemento TextBlock se enlaza a una propiedad de la clase Task.
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Los datos subyacentes para los ejemplos de este tema son una colección de objetos CLR. Si está enlazando a datos XML, los conceptos fundamentales son los mismos, pero hay una ligera diferencia sintáctica. Por ejemplo, en lugar de tener Path=TaskName, establecería XPath en @TaskName (si TaskName es un atributo del nodo XML ).
Ahora, nuestro control ListBox tiene el aspecto siguiente:
Crear la plantilla de datos como un recurso
En el ejemplo anterior, definimos el objeto DataTemplate en línea. Es más común definirlo en la sección de recursos para que pueda ser un objeto reutilizable, como en el ejemplo siguiente:
<Window.Resources>
...
<DataTemplate x:Key="myTaskTemplate">
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
...
</Window.Resources>
Ahora puede utilizar myTaskTemplate como recurso, tal como se muestra en el ejemplo siguiente:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"/>
Dado que myTaskTemplate es un recurso, ahora puede utilizarlo en otros controles que tengan una propiedad que admita un tipo DataTemplate. Como antes se mostró, para objetos ItemsControl, tales como el control ListBox, es la propiedad ItemTemplate. Para objetos ContentControl, es la propiedad ContentTemplate.
La propiedad DataType
La clase DataTemplate tiene una propiedad DataType que es muy similar a la propiedad TargetType de la clase Style. Por consiguiente, en lugar de especificar x:Key para el objeto DataTemplate en el ejemplo anterior, puede hacer lo siguiente:
<DataTemplate DataType="{x:Type local:Task}">
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
Este objeto DataTemplate se aplica automáticamente a todos los objetos Task. Tenga en cuenta que, en este caso, x:Key se establece implícitamente. Por consiguiente, si asigna un valor x:Key a este objeto DataTemplate, estará reemplazando el valor implícito de x:Key y el objeto DataTemplate no se aplicará automáticamente.
Si está enlazando un objeto ContentControl a una colección de los objetos Task, el objeto ContentControl no utiliza automáticamente el objeto DataTemplate anterior. Esto se debe a que el enlace en un control ContentControl necesita más información para poder distinguir si el usuario desea enlazar a una colección completa o a los objetos individuales. Si el control ContentControl realiza un seguimiento de la selección de un tipo de ItemsControl, puede establecer la propiedad Path del enlace de ContentControl en "/" para indicar que le interesa el elemento actual. Para consultar un ejemplo, vea Cómo: Enlazar a una colección y mostrar información basada en la selección. De lo contrario, debe especificar explícitamente el objeto DataTemplate estableciendo la propiedad ContentTemplate.
La propiedad DataType resulta particularmente útil en el caso de una colección CompositeCollection de diferentes tipos de objetos de datos. Para consultar un ejemplo, vea Cómo: Implementar una CompositeCollection.
Agregar a la plantilla de datos
Actualmente, los datos aparecen con la información necesaria, pero está claro que hay espacio para la mejora. Podemos mejorar la presentación agregando un elemento Border, un elemento Grid y algunos elementos TextBlock que describan los datos que se muestran.
<DataTemplate x:Key="myTaskTemplate">
<Border Name="border" BorderBrush="Aqua" BorderThickness="1"
Padding="5" Margin="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Task Name:"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=TaskName}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Description:"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Description}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Priority:"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Path=Priority}"/>
</Grid>
</Border>
...
</DataTemplate>
La captura de pantalla siguiente muestra el control ListBox con este objeto DataTemplatemodificado:
Podemos establecer HorizontalContentAlignment en Stretch en el control ListBox para asegurarse de que el ancho de los elementos ocupe todo el espacio:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"
HorizontalContentAlignment="Stretch"/>
Con la propiedad HorizontalContentAlignment establecida en Stretch, el control ListBox tiene ahora este aspecto:
Utilizar DataTriggers para aplicar valores de propiedad
La presentación actual no nos indica si Task es una tarea doméstica o una tarea de oficina. Recuerde que el objeto Task tiene una propiedad TaskType de tipo TaskType, que es una enumeración con valores Home y Work.
En el ejemplo siguiente, el objeto DataTrigger establece el valor de BorderBrush del elemento denominado border en Yellow si la propiedad TaskType es TaskType.Home.
<DataTemplate x:Key="myTaskTemplate">
...
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=TaskType}">
<DataTrigger.Value>
<local:TaskType>Home</local:TaskType>
</DataTrigger.Value>
<Setter TargetName="border" Property="BorderBrush" Value="Yellow"/>
</DataTrigger>
</DataTemplate.Triggers>
...
</DataTemplate>
Ahora, nuestra aplicación tiene el aspecto siguiente. Las tareas domésticas aparecen con un borde amarillo y las tareas de oficina con un borde aguamarina:
En este ejemplo, el objeto DataTrigger utiliza un objeto Setter para establecer un valor de propiedad. Las clases de desencadenador también tienen propiedades EnterActions y ExitActions, que permiten iniciar un conjunto de acciones tales como animaciones. Además, también hay una clase MultiDataTrigger que permite aplicar cambios en función de varios valores de propiedad enlazados a datos.
Una manera alternativa de lograr el mismo efecto es enlazar la propiedad BorderBrush a la propiedad TaskType y utilizar un convertidor de valores para devolver el color en función del valor TaskType. Para ver un ejemplo similar, vea Cómo: Alternar el color de fondo de las filas de un control ListView. La creación del efecto anterior mediante un convertidor es ligeramente más eficaz por lo que se refiere a rendimiento. Además, la creación de un convertidor propio ofrece más flexibilidad, dado que se proporciona lógica propia. En último término, la técnica que elija dependerá del escenario y de sus preferencias. Para obtener más información sobre cómo escribir un convertidor, vea IValueConverter.
¿Cuáles son los elementos de una plantilla de datos?
En el ejemplo anterior, colocamos el desencadenador dentro del objeto DataTemplate utilizando la propiedad DataTemplate.Triggers El objeto Setter del desencadenador establece el valor de una propiedad de un elemento (el elemento Border) que se encuentra dentro del objeto DataTemplate. Sin embargo, si las propiedades que afectan a Setters no son propiedades de elementos que están dentro del objeto DataTemplateactual, puede que sea más conveniente establecer las propiedades mediante un objeto Style para la clase (si el control que está enlazando es un control ListBox) ListBoxItem. Por ejemplo, si desea que el objeto Trigger anime el valor Opacity del elemento cuando el mouse señale a un elemento, defina desencadenadores dentro de un estilo ListBoxItem. Para obtener un ejemplo, vea Ejemplo Introduction to Styling and Templating.
En general, tenga en cuenta que el objeto DataTemplate se aplica a cada uno de los elementos ListBoxItem generados (para obtener más información sobre cómo y donde se aplica realmente, vea la página ItemTemplate). El objeto DataTemplate solamente está relacionado con la presentación y la apariencia de los objetos de datos. En la mayoría de los casos, todos los demás aspectos de la presentación, tales como el aspecto que tiene un elemento cuando se selecciona o cómo dispone los elementos el control ListBox, no pertenecen a la definición de un objeto DataTemplate. Para obtener un ejemplo, vea la sección Estilos y plantillas para ItemsControl.
Elegir una plantilla de datos en función de propiedades del objeto de datos
En la sección La propiedad DataType, explicamos que puede definir diferentes plantillas de datos para diferentes objetos de datos. Esto es especialmente útil cuando se tiene una colección CompositeCollection de diferentes tipos o colecciones con elementos de diferentes tipos. En la sección Utilizar DataTriggers para aplicar valores de propiedad hemos mostrado que si se tiene una colección del mismo tipo de objetos de datos se puede crear un objeto DataTemplate y, a continuación, utilizar desencadenadores para aplicar cambios en función de los valores de propiedad de cada objeto de datos. Sin embargo, los desencadenadores permiten aplicar valores de propiedad o iniciar animaciones, pero no ofrecen la flexibilidad necesaria para reconstruir la estructura de los objetos de datos. Algunos escenarios pueden exigirle que cree un objeto DataTemplate diferente para objetos de datos que sean del mismo tipo pero tengan propiedades diferentes.
Por ejemplo, cuando un objeto Task tiene un valor Priority de 1, quizá desee darle una apariencia completamente diferente para actuar como una alerta para usted. En ese caso, debe crear un objeto DataTemplate para la presentación de los objetos Task prioritarios. Agreguemos el siguiente objeto DataTemplate a la sección de recursos:
<DataTemplate x:Key="importantTaskTemplate">
<DataTemplate.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="20"/>
</Style>
</DataTemplate.Resources>
<Border Name="border" BorderBrush="Red" BorderThickness="1"
Padding="5" Margin="5">
<DockPanel HorizontalAlignment="Center">
<TextBlock Text="{Binding Path=Description}" />
<TextBlock>!</TextBlock>
</DockPanel>
</Border>
</DataTemplate>
Observe que este ejemplo utiliza la propiedad DataTemplate.Resources Los recursos definidos en esa sección son compartidos por los elementos del objeto DataTemplate.
Para proporcionar lógica que permita de elegir qué objeto DataTemplate se debe utilizar en función del valor Priority del objeto de datos, cree una subclase de DataTemplateSelector e invalide el método SelectTemplate. En el ejemplo siguiente, el método SelectTemplate proporciona lógica para devolver la plantilla adecuada en función del valor de la propiedad Priority. La plantilla que se devuelve se encuentra en los recursos del elemento Window envolvente.
using System.Windows;
using System.Windows.Controls;
namespace SDKSample
{
public class TaskListDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
if (item != null && item is Task)
{
Task taskitem = item as Task;
Window window = Application.Current.MainWindow;
if (taskitem.Priority == 1)
return
window.FindResource("importantTaskTemplate") as DataTemplate;
else
return
window.FindResource("myTaskTemplate") as DataTemplate;
}
return null;
}
}
}
Podemos declarar entonces TaskListDataTemplateSelector como recurso:
<Window.Resources>
...
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
...
</Window.Resources>
Para utilizar el recurso selector de plantillas, asígnelo a la propiedad ItemTemplateSelector del objeto ListBox. El objeto ListBox llama al método SelectTemplate de TaskListDataTemplateSelector para cada uno de los elementos de la colección subyacente. La llamada pasa el objeto de datos como parámetro de elemento. El objeto DataTemplate devuelto por el método se aplica a continuación a ese objeto de datos.
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"/>
Con el selector de la plantilla en su lugar, el control ListBox aparece ahora como sigue:
Con esto concluye la explicación de este ejemplo. Para obtener el ejemplo completo, vea Introducción al ejemplo Data Templating.
Estilos y plantillas para ItemsControl
Aunque el control ItemsControl no es el único tipo de control con el que se puede utilizar un control DataTemplate, es un escenario muy común para enlazar un control ItemsControl a una colección. En la sección ¿Cuáles son los elementos de una plantilla de datos? explicamos que la definición del objeto DataTemplate sólo debe tener relación con la presentación de datos. Para saber cuándo no es conveniente utilizar un objeto DataTemplate, es importante entender las diferentes propiedades de estilo y de plantilla que proporciona el control ItemsControl. El ejemplo siguiente se ha diseñado para mostrar la función de cada una de estas propiedades. El control ItemsControl de este ejemplo está enlazado a la misma colección Tasks que en el ejemplo anterior. A efectos de la demostración, los estilos y las plantillas del ejemplo están todos declarados en el propio código.
<ItemsControl Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}">
<!--The ItemsControl has no default visual appearance.
Use the Template property to specify a ControlTemplate to define
the appearance of an ItemsControl. The ItemsPresenter uses the specified
ItemsPanelTemplate (see below) to layout the items. If an
ItemsPanelTemplate is not specified, the default is used. (For ItemsControl,
the default is an ItemsPanelTemplate that specifies a StackPanel.-->
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<Border BorderBrush="Aqua" BorderThickness="1" CornerRadius="15">
<ItemsPresenter/>
</Border>
</ControlTemplate>
</ItemsControl.Template>
<!--Use the ItemsPanel property to specify an ItemsPanelTemplate
that defines the panel that is used to hold the generated items.
In other words, use this property if you want to affect
how the items are laid out.-->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!--Use the ItemTemplate to set a DataTemplate to define
the visualization of the data objects. This DataTemplate
specifies that each data object appears with the Proriity
and TaskName on top of a silver ellipse.-->
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="18"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataTemplate.Resources>
<Grid>
<Ellipse Fill="Silver"/>
<StackPanel>
<TextBlock Margin="3,3,3,0"
Text="{Binding Path=Priority}"/>
<TextBlock Margin="3,0,3,7"
Text="{Binding Path=TaskName}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<!--Use the ItemContainerStyle property to specify the appearance
of the element that contains the data. This ItemContainerStyle
gives each item container a margin and a width. There is also
a trigger that sets a tooltip that shows the description of
the data object when the mouse hovers over the item container.-->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Width" Value="100"/>
<Setter Property="Control.Margin" Value="5"/>
<Style.Triggers>
<Trigger Property="Control.IsMouseOver" Value="True">
<Setter Property="Control.ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=Content.Description}"/>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
A continuación se muestra una captura de pantalla del ejemplo cuando se representa:
Observe que en lugar de utilizar ItemTemplate, puede utilizar ItemTemplateSelector. Consulte la sección anterior para ver un ejemplo. De igual forma, en lugar de utilizar ItemContainerStyle, tiene la opción de utilizar ItemContainerStyleSelector.
Otras dos propiedades relacionadas con el estilo del control ItemsControl que no se muestran aquí son GroupStyle y GroupStyleSelector.
Compatibilidad con datos jerárquicos
Hasta ahora solamente hemos examinado cómo enlazar y mostrar una única colección. En ocasiones tendrá colecciones que contengan otras colecciones. La clase HierarchicalDataTemplate se ha diseñado para utilizarla con tipos HeaderedItemsControl para mostrar tales datos. En el ejemplo siguiente, ListLeagueList es una lista de objetos League. Cada objeto League tiene un Name y una colección de objetos Division. Cada Division tiene un Name y una colección de objetos Team, y cada objeto Team tiene un Name.
<Window x:Class="SDKSample.Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Title="HierarchicalDataTemplate Sample"
xmlns:src="clr-namespace:SDKSample">
<DockPanel>
<DockPanel.Resources>
<src:ListLeagueList x:Key="MyList"/>
<HierarchicalDataTemplate DataType = "{x:Type src:League}"
ItemsSource = "{Binding Path=Divisions}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType = "{x:Type src:Division}"
ItemsSource = "{Binding Path=Teams}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type src:Team}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</DockPanel.Resources>
<Menu Name="menu1" DockPanel.Dock="Top" Margin="10,10,10,10">
<MenuItem Header="My Soccer Leagues"
ItemsSource="{Binding Source={StaticResource MyList}}" />
</Menu>
<TreeView>
<TreeViewItem ItemsSource="{Binding Source={StaticResource MyList}}" Header="My Soccer Leagues" />
</TreeView>
</DockPanel>
</Window>
En el ejemplo se muestra que, con el uso de HierarchicalDataTemplate, es fácil mostrar datos de listas que contengan otras listas. A continuación, se muestra una captura de pantalla del ejemplo.
Para obtener el ejemplo completo, vea Ejemplo Displaying Hierarchical Data.
Vea también
Tareas
Cómo: Buscar elementos generados por un objeto DataTemplate
Conceptos
Optimizar el rendimiento: Enlace de datos
Información general sobre el enlace de datos
Información general sobre plantillas y estilos de encabezado de columna en modo GridView