Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
El modelo de plantillas de datos de WPF proporciona una gran flexibilidad para definir la presentación de los datos. Los controles WPF tienen funcionalidad integrada para admitir la personalización de la presentación de datos. En primer lugar, en este tema se muestra cómo definir DataTemplate y, a continuación, se presentan otras características de plantillas de datos, como la selección de plantillas basadas en la lógica personalizada y la compatibilidad con la visualización de datos jerárquicos.
Prerrequisitos
Este tema se centra en las características de plantillas de datos y no es una introducción de los conceptos de enlace de datos. Para obtener información sobre los conceptos básicos del enlace de datos, consulte el Resumen del enlace de datos.
DataTemplate se trata de la presentación de datos y es una de las muchas características proporcionadas por el modelo de plantillas y estilos de WPF. Para obtener una introducción al modelo de estilos y plantillas de WPF, como utilizar un Style para establecer propiedades en los controles, consulte el tema Estilos y plantillas.
Además, es importante comprender Resources
, que son básicamente lo que permite que los objetos como Style y DataTemplate sean reutilizables. Para obtener más información sobre los recursos, consulta Recursos XAML.
Conceptos básicos de plantillas de datos
Para demostrar por qué DataTemplate es importante, veamos un ejemplo de enlace de datos. En este ejemplo, tenemos un ListBox que está enlazado a una lista de Task
objetos . Cada Task
objeto tiene una TaskName
(cadena), una Description
(cadena), una Priority
(int) y una propiedad de tipo TaskType
, que es un Enum
objeto con valores Home
y Work
.
<Window x:Class="SDKSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://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 DataTemplate, nuestro ListBox tiene el siguiente aspecto actualmente:
Lo que ocurre es que, sin instrucciones específicas, ListBox realiza una llamada por defecto a ToString
al intentar mostrar los objetos en la colección. Por lo tanto, si el objeto sobrescribe el método Task
, entonces ToString
muestra la representación de cadena de cada objeto de origen en la colección subyacente.
Por ejemplo, si la Task
clase invalida el ToString
método de esta manera, donde name
es el campo de la TaskName
propiedad :
public override string ToString()
{
return name.ToString();
}
Public Overrides Function ToString() As String
Return _name.ToString()
End Function
A continuación, ListBox se ve de la siguiente manera:
Sin embargo, eso es limitado e inflexible. Además, si está enlazando a datos XML, no podrá sobrescribir ToString
.
Definición de una Plantilla de Datos sencilla
La solución consiste en definir un DataTemplate. Una manera de hacerlo es establecer la ItemTemplate propiedad del ListBox a un DataTemplate. Lo que especifiques en tu DataTemplate se convierte en la estructura visual de tu objeto de datos. Lo siguiente DataTemplate es bastante sencillo. Damos instrucciones para que cada artículo aparezca como tres elementos TextBlock dentro de un StackPanel. Cada TextBlock elemento está enlazado a una propiedad de la Task
clase .
<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 de 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ías XPath a @TaskName
(si TaskName
es un atributo del nodo XML).
Ahora la apariencia de nuestro ListBox es similar a la siguiente:
Creación de DataTemplate como un recurso
En el ejemplo anterior, definimos la 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 usar myTaskTemplate
como recurso, como en el ejemplo siguiente:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"/>
Como myTaskTemplate
es un recurso, ahora puede usarlo en otros controles que tienen una propiedad que acepta un tipo DataTemplate. Como se muestra anteriormente, para ItemsControl objetos, como ListBox, es la ItemTemplate propiedad . Para los objetos ContentControl, es la propiedad ContentTemplate.
La propiedad DataType
La DataTemplate clase tiene una DataType propiedad muy similar a la TargetType propiedad de la Style clase . Por lo tanto, en lugar de especificar un x:Key
para en el DataTemplate 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>
Esto DataTemplate se aplica automáticamente a todos los Task
objetos. Tenga en cuenta que en este caso el x:Key
se establece implícitamente. Por lo tanto, si asigna un valor a este DataTemplate, sobrescribe el valor implícito de x:Key
, y x:Key
no se aplicará automáticamente.
Si va a enlazar un objeto ContentControl a una colección de Task
objetos , ContentControl no usa el elemento anterior DataTemplate automáticamente. Esto se debe a que el enlace de una ContentControl necesita más información para distinguir si desea enlazar a una colección completa o a los objetos individuales. Si su ContentControl está realizando el seguimiento de la selección de un ItemsControl tipo, puede establecer la propiedad Path de la vinculación ContentControl a "/
" para indicar que le interesa el elemento actual. Para obtener un ejemplo, vea Enlazar a una colección y mostrar información basada en la selección. De lo contrario, debe especificar explícitamente DataTemplate al establecer la propiedad ContentTemplate.
La DataType propiedad es especialmente útil cuando se tiene un CompositeCollection de diferentes tipos de objetos de datos. Para obtener un ejemplo, consulte Implementar un CompositeCollection.
Agregar más a DataTemplate
Actualmente los datos aparecen con la información necesaria, pero definitivamente hay espacio para mejorar. Vamos a mejorar la presentación agregando un Border, a Gridy algunos TextBlock elementos que describen 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 ListBox con este DataTemplate modificado.
Podemos establecer HorizontalContentAlignment en Stretch en el ListBox para asegurarnos 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 ListBox ahora tiene el siguiente aspecto:
Aplicar valores a las propiedades usando DataTriggers
La presentación actual no nos indica si Task
es una tarea doméstica o una tarea de oficina. Recuerde que el Task
objeto tiene una TaskType
propiedad de tipo TaskType
, que es una enumeración con valores Home
y Work
.
En el ejemplo siguiente, el DataTrigger establece el 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>
Nuestra aplicación ahora es similar a la siguiente. Las tareas domésticas aparecen con un borde amarillo y las tareas de oficina aparecen con un borde acuático:
En este ejemplo, el DataTrigger utiliza el Setter para definir un valor de propiedad. Las clases de desencadenador también tienen las EnterActions propiedades y ExitActions que permiten iniciar un conjunto de acciones como animaciones. Además, también hay una MultiDataTrigger clase 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 BorderBrush propiedad a la TaskType
propiedad y usar un convertidor de valores para devolver el color en función del TaskType
valor. La creación del efecto anterior mediante un convertidor es ligeramente más eficaz en términos de rendimiento. Además, la creación de su propio convertidor le ofrece más flexibilidad porque proporciona su propia lógica. En última instancia, la técnica que elija dependerá de su escenario y de su preferencia. Para obtener información sobre cómo escribir un convertidor, consulte IValueConverter.
¿Qué pertenece a una clase DataTemplate?
En el ejemplo anterior, colocamos el desencadenador dentro de DataTemplate mediante la DataTemplate.Triggers propiedad . El Setter del desencadenador establece el valor de una propiedad de un elemento (el Border elemento) que está dentro del DataTemplate. Sin embargo, si las propiedades con las que Setters
se refiere no son propiedades de elementos que están dentro del actual DataTemplate, puede ser más adecuado establecer las propiedades mediante un Style que es para la ListBoxItem clase (si el control que está enlazando es un ListBox). Por ejemplo, si desea que Trigger anime el Opacity valor del elemento cuando el ratón apunta a un elemento, se definen desencadenadores dentro de un ListBoxItem estilo. Para obtener un ejemplo, vea la demostración Introducción a la aplicación de estilos y plantillas.
En general, tenga en cuenta que el DataTemplate se aplica a cada uno de los ListBoxItem generados (para más información sobre cómo y dónde se aplica realmente, consulte la página ItemTemplate). Su DataTemplate solo se ocupa de la presentación y apariencia de los objetos de datos. En la mayoría de los casos, todos los demás aspectos de la presentación, como el aspecto de un elemento cuando se selecciona o cómo ListBox establece los elementos, no pertenecen a la definición de .DataTemplate Para obtener un ejemplo, consulte la sección Aplicación de estilos y plantillas de ItemsControl.
Elección de una dataTemplate basada en las propiedades del objeto de datos
En la sección de la propiedad DataType, discutimos que puede definir diferentes plantillas de datos para distintos objetos de datos. Esto resulta especialmente útil cuando se tiene un CompositeCollection que contiene diferentes tipos o colecciones con elementos de diferentes tipos. En la sección Use DataTriggers to Apply Property Values , hemos mostrado que si tiene una colección del mismo tipo de objetos de datos, puede crear un DataTemplate y a continuación, usar 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 proporcionan flexibilidad para reconstruir la estructura de los objetos de datos. Algunos escenarios pueden requerir que cree otro DataTemplate para los objetos de datos que sean del mismo tipo, pero que tengan propiedades diferentes.
Por ejemplo, cuando un Task
objeto tiene un Priority
valor de 1
, puede que desee darle un aspecto completamente diferente para servir como una alerta para usted mismo. En ese caso, se crea un DataTemplate para la presentación de los objetos de alta prioridad Task
. Vamos a agregar lo siguiente 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>
En este ejemplo se usa la propiedad DataTemplate.Resources . Los recursos definidos en esa sección son compartidos por los elementos dentro del DataTemplate.
Para proporcionar lógica para elegir qué DataTemplate usar en función del Priority
valor del objeto de datos, cree una subclase de DataTemplateSelector e invalide el SelectTemplate método . En el ejemplo siguiente, el SelectTemplate método proporciona lógica para devolver la plantilla adecuada en función del valor de la Priority
propiedad. La plantilla que se va a devolver se encuentra en los recursos del elemento envolvente Window .
using System.Windows;
using System.Windows.Controls;
namespace SDKSample
{
public class TaskListDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is Task)
{
Task taskitem = item as Task;
if (taskitem.Priority == 1)
return
element.FindResource("importantTaskTemplate") as DataTemplate;
else
return
element.FindResource("myTaskTemplate") as DataTemplate;
}
return null;
}
}
}
Namespace SDKSample
Public Class TaskListDataTemplateSelector
Inherits DataTemplateSelector
Public Overrides Function SelectTemplate(ByVal item As Object, ByVal container As DependencyObject) As DataTemplate
Dim element As FrameworkElement
element = TryCast(container, FrameworkElement)
If element IsNot Nothing AndAlso item IsNot Nothing AndAlso TypeOf item Is Task Then
Dim taskitem As Task = TryCast(item, Task)
If taskitem.Priority = 1 Then
Return TryCast(element.FindResource("importantTaskTemplate"), DataTemplate)
Else
Return TryCast(element.FindResource("myTaskTemplate"), DataTemplate)
End If
End If
Return Nothing
End Function
End Class
End Namespace
A continuación, podemos declarar como TaskListDataTemplateSelector
un recurso:
<Window.Resources>
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>
Para usar el recurso del selector de plantillas, asígnelo a la ItemTemplateSelector propiedad de ListBox. La ListBox llama al método SelectTemplate del TaskListDataTemplateSelector
para cada uno de los elementos de la colección subyacente. La llamada pasa el objeto de datos como parámetro item. El DataTemplate que es devuelto por el método se aplica luego a ese objeto de datos.
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"/>
Con el selector de plantillas en su lugar, ListBox ahora aparece como sigue:
Esto concluye nuestra discusión de este ejemplo. Para obtener el ejemplo completo, consulte Introducción al ejemplo de plantillas de datos.
Aplicación de estilos y plantillas a ItemsControl
ItemsControl Aunque no sea el único tipo de control en el que se puede utilizar DataTemplate, es muy común enlazar una colección a ItemsControl. En la sección Qué Pertenece a una DataTemplate, analizamos que la definición de su DataTemplate solo debe centrarse en la presentación de datos. Para saber cuándo no es adecuado usar un DataTemplate elemento , es importante comprender las diferentes propiedades de estilo y plantilla proporcionadas por .ItemsControl El ejemplo siguiente está diseñado para ilustrar la función de cada una de estas propiedades. En este ejemplo, ItemsControl se enlaza a la misma colección Tasks
que en el ejemplo anterior. Con fines de demostración, todos los estilos y plantillas de este ejemplo se declaran en línea.
<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:
Tenga en cuenta que, en lugar de usar ItemTemplate, puede usar .ItemTemplateSelector Consulte la sección anterior para obtener un ejemplo. Del mismo modo, en lugar de usar ItemContainerStyle, tiene la opción de usar ItemContainerStyleSelector.
Otras dos propiedades relacionadas con el estilo de que ItemsControl no se muestran aquí son GroupStyle y GroupStyleSelector.
Compatibilidad con datos jerárquicos
Hasta ahora solo hemos visto cómo enlazar y mostrar una sola colección. A veces tienes una colección que contiene otras colecciones. La HierarchicalDataTemplate clase está diseñada para usarse con HeaderedItemsControl tipos para mostrar estos datos. En el ejemplo siguiente, ListLeagueList
es una lista de League
objetos . Cada League
objeto tiene una Name
y una colección de Division
objetos. Cada Division
tiene un Name
objeto y una colección de Team
objetos, y cada Team
objeto tiene un Name
.
<Window x:Class="SDKSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://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>
El ejemplo muestra que con el uso de HierarchicalDataTemplate, puede mostrar fácilmente datos de lista que contienen otras listas. A continuación se muestra una captura de pantalla del ejemplo.
Consulte también
.NET Desktop feedback