Introducción al enlace de datos (WPF en .NET)

El enlace de datos en Windows Presentation Foundation (WPF) ofrece una manera sencilla y coherente de que las aplicaciones presenten datos e interactúen con ellos. Los elementos se pueden enlazar a datos desde diferentes tipos de orígenes de datos en forma de objetos .NET y XML. Cualquier control ContentControl, como Button, y cualquier control ItemsControl, como ListBox y ListView, tienen funciones integradas para habilitar el estilo flexible de elementos de datos individuales o colecciones de elementos de datos. Se pueden generar vistas de ordenación, filtrado ya agrupación encima de los datos.

El enlace de datos de WPF ofrece varias ventajas con respecto a los modelos tradicionales. Por ejemplo, un amplio espectro de propiedades admite de forma inherente el enlace de datos, la representación de los datos es flexible y hay una separación bien definida entre la lógica de negocio y la interfaz de usuario.

En este artículo se describen primero los conceptos fundamentales para el enlace de datos de WPF y, después, se explica el uso de la clase Binding y otras características del enlace de datos.

Importante

La documentación de la guía de escritorio para .NET 7 y .NET 6 está en proceso de elaboración.

¿Qué es el enlace de datos?

El enlace de datos es el proceso que establece una conexión entre la interfaz de usuario de la aplicación y los datos. Si el enlace está configurado correctamente y los datos proporcionan la notificaciones adecuadas, al cambiar los valores de los datos, los elementos enlazados a los mismos reflejarán de manera automática dichos cambios. El enlace de datos también puede implicar la actualización automática de los datos que subyacen a una representación externa de los datos de un elemento, cuando esta representación cambia. Por ejemplo, si el usuario modifica el valor en un elemento TextBox, el valor de los datos subyacentes se actualiza automáticamente para reflejar ese cambio.

Un uso típico del enlace de datos es colocar los datos de configuración locales o de servidor en formularios o en otros controles de la interfaz de usuario. En WPF, este concepto se expande para incluir el enlace de un gran número de propiedades a diferentes tipos de orígenes de datos. En WPF, las propiedades de dependencia de los elementos se pueden enlazar a objetos .NET (incluidos los objetos ADO.NET u objetos asociados a propiedades web y servicios web) y datos XML.

Conceptos básicos del enlace de datos

Independientemente del elemento que se vaya a enlazar y de la naturaleza del origen de datos, cada enlace sigue siempre el modelo que se muestra en esta imagen.

Diagrama que muestra el modelo básico de enlace de datos.

Aquí vemos que el enlace de datos es básicamente el puente entre el destino del enlace y el origen del enlace. En la imagen se muestran estos conceptos fundamentales del enlace de datos de WPF:

  • Normalmente, cada enlace tiene cuatro componentes:

    • un objeto de destino de enlace;
    • una propiedad de destino;
    • un origen de enlace,
    • una ruta de acceso al valor del origen de enlace que se va a usar.

    Por ejemplo, si enlaza el contenido de TextBox a la propiedad Employee.Name, configuraría el enlace como en la tabla siguiente:

    Configuración Valor
    Destino TextBox
    Propiedad de destino Text
    Objeto de origen Employee
    Ruta de acceso del valor del objeto de origen Name
  • La propiedad de destino debe ser una propiedad de dependencia.

    La mayoría de las propiedades UIElement son propiedades de dependencia y la mayoría de las propiedades de dependencia, excepto las de solo lectura, admiten el enlace de datos de forma predeterminada. Solo los tipos derivados de DependencyObject pueden definir propiedades de dependencia. Todos los tipos UIElement se derivan de DependencyObject.

  • Los orígenes de enlace no están restringidos a objetos .NET personalizados.

    Aunque no se especifica en la imagen, hay que destacar que el objeto de origen de enlace no está restringido a ser un objeto .NET personalizado. El enlace de datos de WPF admite datos en forma de objetos .NET, XML e incluso objetos de elementos XAML. Para proporcionar algunos ejemplos, el origen de enlace puede ser UIElement, cualquier objeto de lista, un objeto de servicios web o ADO.NET o un XmlNode que contenga los datos XML. Para más información, vea Información general sobre orígenes de enlaces.

Es importante tener en cuenta que cuando establece un enlace, enlaza un destino de enlace a un origen de enlace. Por ejemplo, si se muestran algunos datos XML subyacentes en ListBox mediante el enlace de datos, se enlaza ListBox a los datos XML.

Para establecer un enlace, se usa el objeto Binding. En el resto de este artículo se describen muchos de los conceptos asociados a algunas de las propiedades y el uso del objeto Binding.

Contexto de datos

Cuando el enlace de datos se declara en elementos XAML, resuelven el enlace de datos observando su propiedad DataContext inmediata. El contexto de datos suele ser el objeto de origen de enlace para la evaluación de la ruta de acceso del valor del origen de enlace. Puede invalidar este comportamiento en el enlace y establecer un valor de objeto de origen de enlace específico. Si la propiedad DataContext del objeto que hospeda el enlace no está establecida, se comprueba la propiedad DataContext del elemento principal, y así sucesivamente, hasta la raíz del árbol de objetos XAML. En resumen, el contexto de datos utilizado para resolver el enlace se hereda del elemento primario a menos que se establezca explícitamente en el objeto.

Los enlaces se pueden configurar para resolverse con un objeto específico, en lugar de usar el contexto de datos para la resolución de enlaces. La especificación directa de un objeto de origen se usa cuando, por ejemplo, se enlaza el color de primer plano de un objeto al color de fondo de otro objeto. El contexto de datos no es necesario, ya que el enlace se resuelve entre esos dos objetos. Inversamente, los enlaces que no están enlazados a objetos de origen específicos usan la resolución de contexto de datos.

Cuando cambia la propiedad DataContext, se vuelven a evaluar todos los enlaces que podrían verse afectados por el contexto de datos.

Dirección del flujo de datos

Tal y como indica la flecha de la imagen anterior, el flujo de datos de un enlace puede ir del destino al origen del enlace (por ejemplo, el valor de origen cambia cuando un usuario modifica el valor de TextBox) y del origen al destino del enlace (por ejemplo, el contenido de TextBox se actualiza con los cambios en el origen del enlace) si el origen del enlace proporciona las notificaciones correspondientes.

Tal vez le interesa que la aplicación permita que los usuarios cambien los datos y los propaguen al objeto de origen. O bien, no puede permitir a los usuarios actualizar los datos de origen. Puede controlar el flujo de datos si configura Binding.Mode.

En esta imagen se muestran los distintos tipos de flujo de datos:

Flujo de datos de enlace de datos

  • El enlace OneWay permite que los cambios en la propiedad de origen actualicen automáticamente la propiedad de destino, pero los cambios realizados en la propiedad de destino no se propagan hacia la propiedad de origen. Este tipo de enlace es adecuado si el control que se está enlazando es implícitamente de solo lectura. Por ejemplo, podría enlazar a un origen, como un tablero de cotizaciones, o quizás la propiedad de destino no tenga ninguna interfaz de control para realizar modificaciones, como un color de fondo enlazado a datos de una tabla. Si no es necesario supervisar los cambios de la propiedad de destino, el uso del modo de enlace OneWay evita la sobrecarga del modo de enlace TwoWay.

  • El enlace TwoWay realiza cambios en la propiedad de origen o en la propiedad de destino para que actualice automáticamente la otra. Este tipo de enlace es adecuado para formularios modificables u otros casos de interfaz de usuario completamente interactiva. La mayoría de las propiedades tienen como valor predeterminado el enlace OneWay, pero algunas propiedades de dependencia (normalmente, las propiedades de los controles editables por el usuario, como TextBox.Text y CheckBox.IsChecked) tienen como valor predeterminado el enlace TwoWay.

    Una manera de determinar mediante programación si una propiedad de dependencia se enlaza de forma predeterminada de modo unidireccional o bidireccional es obtener los metadatos de la propiedad con DependencyProperty.GetMetadata. El tipo de valor devuelto de este método es PropertyMetadata, que no contiene ningún metadato sobre el enlace. Sin embargo, si este tipo se puede convertir al FrameworkPropertyMetadata derivado, se puede comprobar el valor booleano de la propiedad FrameworkPropertyMetadata.BindsTwoWayByDefault. En el siguiente ejemplo de código se muestra cómo obtener los metadatos de la propiedad TextBox.Text:

    public static void PrintMetadata()
    {
        // Get the metadata for the property
        PropertyMetadata metadata = TextBox.TextProperty.GetMetadata(typeof(TextBox));
    
        // Check if metadata type is FrameworkPropertyMetadata
        if (metadata is FrameworkPropertyMetadata frameworkMetadata)
        {
            System.Diagnostics.Debug.WriteLine($"TextBox.Text property metadata:");
            System.Diagnostics.Debug.WriteLine($"  BindsTwoWayByDefault: {frameworkMetadata.BindsTwoWayByDefault}");
            System.Diagnostics.Debug.WriteLine($"  IsDataBindingAllowed: {frameworkMetadata.IsDataBindingAllowed}");
            System.Diagnostics.Debug.WriteLine($"        AffectsArrange: {frameworkMetadata.AffectsArrange}");
            System.Diagnostics.Debug.WriteLine($"        AffectsMeasure: {frameworkMetadata.AffectsMeasure}");
            System.Diagnostics.Debug.WriteLine($"         AffectsRender: {frameworkMetadata.AffectsRender}");
            System.Diagnostics.Debug.WriteLine($"              Inherits: {frameworkMetadata.Inherits}");
        }
    
        /*  Displays:
         *  
         *  TextBox.Text property metadata:
         *    BindsTwoWayByDefault: True
         *    IsDataBindingAllowed: True
         *          AffectsArrange: False
         *          AffectsMeasure: False
         *           AffectsRender: False
         *                Inherits: False
        */
    }
    
    Public Shared Sub PrintMetadata()
    
        Dim metadata As PropertyMetadata = TextBox.TextProperty.GetMetadata(GetType(TextBox))
        Dim frameworkMetadata As FrameworkPropertyMetadata = TryCast(metadata, FrameworkPropertyMetadata)
    
        If frameworkMetadata IsNot Nothing Then
    
            System.Diagnostics.Debug.WriteLine($"TextBox.Text property metadata:")
            System.Diagnostics.Debug.WriteLine($"  BindsTwoWayByDefault: {frameworkMetadata.BindsTwoWayByDefault}")
            System.Diagnostics.Debug.WriteLine($"  IsDataBindingAllowed: {frameworkMetadata.IsDataBindingAllowed}")
            System.Diagnostics.Debug.WriteLine($"        AffectsArrange: {frameworkMetadata.AffectsArrange}")
            System.Diagnostics.Debug.WriteLine($"        AffectsMeasure: {frameworkMetadata.AffectsMeasure}")
            System.Diagnostics.Debug.WriteLine($"         AffectsRender: {frameworkMetadata.AffectsRender}")
            System.Diagnostics.Debug.WriteLine($"              Inherits: {frameworkMetadata.Inherits}")
    
            '  Displays:
            '
            '  TextBox.Text property metadata:
            '    BindsTwoWayByDefault: True
            '    IsDataBindingAllowed: True
            '          AffectsArrange: False
            '          AffectsMeasure: False
            '           AffectsRender: False
            '                Inherits: False
        End If
    
    
    End Sub
    
  • OneWayToSource es lo contrario del enlace OneWay: actualiza la propiedad de origen cuando cambia la propiedad de destino. Podría usar este tipo de enlace si, por ejemplo, solo necesita volver a evaluar el valor de origen de la interfaz de usuario.

  • En la imagen no se muestra el enlace OneTime, que permite que la propiedad de origen inicialice la propiedad de destino, pero no se propagan los cambios posteriores. Si cambia el contexto de los datos o cambia el objeto del contexto de los datos, este cambio no se refleja en la propiedad de destino. Este tipo de enlace es adecuado si una instantánea del estado actual es adecuada o los datos son realmente estáticos. Este tipo de enlace también es útil si quiere inicializar la propiedad de destino con algún valor de una propiedad de origen y no se conoce el contexto de datos de antemano. Este modo es básicamente de una forma más sencilla de enlace OneWay que ofrece un mejor rendimiento en casos donde el valor de origen no cambia.

Para detectar cambios en el origen (aplicables a los enlaces OneWay y TwoWay), el origen debe implementar un mecanismo de notificación de cambio de propiedad adecuado, como INotifyPropertyChanged. Vea Cómo: Implementar la notificación de cambio de propiedad (.NET Framework) para ver un ejemplo de una implementación de INotifyPropertyChanged.

La propiedad Binding.Mode proporciona más información sobre los modos de enlace y un ejemplo de cómo especificar la dirección de un enlace.

Qué desencadena la actualización del origen

Los enlaces que son TwoWay o OneWayToSource escuchan los cambios en la propiedad de destino y los propagan de nuevo al origen, lo que se conoce como actualizar el origen. Por ejemplo, puede modificar el texto de un control TextBox para cambiar el valor de origen subyacente.

Pero, ¿el valor del origen se actualiza mientras edita el texto o después de terminar de editarlo y cuando el control pierde el foco? La propiedad Binding.UpdateSourceTrigger determina qué desencadena la actualización del origen. En los puntos de las flechas de la derecha en la imagen se muestra el rol de la propiedad Binding.UpdateSourceTrigger.

Diagrama que muestra el rol de la propiedad UpdateSourceTrigger.

Si el valor UpdateSourceTrigger es UpdateSourceTrigger.PropertyChanged, el valor al que apunta la flecha derecha de TwoWay o los enlaces de OneWayToSource se actualiza en cuanto cambia la propiedad de destino. Pero si el valor de UpdateSourceTrigger es LostFocus, ese valor solo se actualiza con el nuevo valor cuando la propiedad de destino pierde el foco.

De forma similar a la propiedad Mode, las distintas propiedades de dependencia tienen valores de UpdateSourceTrigger predeterminados diferentes. El valor predeterminado para la mayoría de las propiedades de dependencia es PropertyChanged, que provoca que el valor de propiedad de origen cambie al instante al cambiar el valor de propiedad de destino. Los cambios instantáneos son aceptables para CheckBox y otros controles sencillos. Pero para los campos de texto, la actualización cada vez que se pulsa una tecla puede disminuir el rendimiento y deniega al usuario la oportunidad habitual de retroceder y corregir los errores tipográficos antes confirmar el nuevo valor. Por ejemplo, el valor predeterminado de la propiedad TextBox.Text es el valor UpdateSourceTrigger de LostFocus, que hace que el valor de origen cambie solo cuando el elemento de control pierde el foco, no cuando se cambia la propiedad TextBox.Text. Vea la página de la propiedad UpdateSourceTrigger para obtener información sobre cómo buscar el valor predeterminado de una propiedad de dependencia.

En esta tabla se muestra un escenario de ejemplo para cada valor de UpdateSourceTrigger en el que se usa TextBox como ejemplo.

Valor UpdateSourceTrigger Cuándo se actualiza el valor de origen Escenario de ejemplo de TextBox
LostFocus (valor predeterminado para TextBox.Text) Cuando el control TextBox pierde el foco. Un control TextBox que está asociado a la lógica de validación (vea Validación de datos más adelante).
PropertyChanged A medida que escribe en TextBox. Controles TextBox en una ventana de sala de chat.
Explicit Cuando la aplicación llama a UpdateSource. Controles TextBox en un formulario modificable (solo se actualizan los valores de origen cuando el usuario presiona el botón de envío).

Si quiere consultar un ejemplo, vea Cómo: controlar cuándo el texto de TextBox actualiza el origen (.NET Framework).

Ejemplo de enlace de datos

Para ver un ejemplo de enlace de datos, eche un vistazo a la siguiente interfaz de usuario de la aplicación Data Binding Demo, que muestra la lista de elementos de una subasta.

Captura de pantalla de enlace de datos de ejemplo

La aplicación muestra las características siguientes de enlace de datos:

  • El contenido de ListBox está enlazado a una colección de objetos AuctionItem. Un objeto AuctionItem tiene propiedades como Description, StartPrice, StartDate, Category y SpecialFeatures.

  • A los datos (objetos AuctionItem) mostrados en ListBox se les ha aplicado una plantilla para que la descripción y el precio actual se muestren para cada artículo. La plantilla se crea mediante DataTemplate. Además, la apariencia de cada artículo depende del valor de SpecialFeatures del elemento AuctionItem que se muestra. Si el valor de SpecialFeatures de AuctionItem es Color, el artículo tiene un borde azul. Si el valor es Highlight, el artículo tiene un borde naranja y una estrella. En la sección Plantillas de datos se proporciona información sobre las plantillas de datos.

  • El usuario puede agrupar, filtrar u ordenar los datos mediante los elementos CheckBoxes proporcionados. En la imagen anterior, se seleccionan los elementos CheckBoxesGroup by category (Agrupar por categoría) y Sort by category and date (Ordenar por categoría y fecha). Es posible que haya observado que los datos se agrupan en función de la categoría del producto, y el nombre de las categorías se muestra en orden alfabético. Aunque no se aprecia muy bien en la imagen, los artículos están ordenados también por fecha de inicio dentro de cada categoría. La ordenación se realiza mediante una vista de colección. En la sección Enlace a colecciones se describen las vistas de colección.

  • Cuando el usuario selecciona un elemento, ContentControl muestra los detalles del elemento seleccionado. Esta experiencia recibe el nombre de escenario principal-detalle . En la sección Escenario principal-detalle se explica este tipo de enlace.

  • El tipo de la propiedad StartDate es DateTime, que devuelve una fecha que incluye la hora en milisegundos. En esta aplicación, se ha usado un convertidor personalizado para que se muestre una cadena de fecha más corta. En la sección Conversión de datos se explican los convertidores.

Cuando el usuario selecciona el botón Add product (Agregar producto), se muestra este formulario:

Página Agregar lista de productos

El usuario puede modificar los campos del formulario, obtener una vista previa de la lista de productos mediante los paneles de vista previa breve o detallada y, después, seleccionar Submit para agregar la nueva lista de productos. Todas las opciones de agrupación, filtrado y ordenación existentes se aplicarán a la nueva entrada. En este caso en concreto, el artículo especificado en la imagen anterior se mostrará como el segundo artículo dentro de la categoría Computer.

En esta imagen no se muestra la lógica de validación proporcionada en TextBox de Start Date (Fecha de inicio). Si el usuario escribe una fecha no válida (formato no válido o fecha pasada), se le notificará con ToolTip y un signo de exclamación rojo junto a TextBox. En la sección Validación de datos se describe cómo crear lógica de validación.

Antes de describir las características de enlace de datos que hemos mencionado, primero explicaremos los conceptos fundamentales imprescindibles para comprender el enlace de datos de WPF.

Creación de un enlace

Para recapitular algunos conceptos descritos en las secciones anteriores, recordemos que un enlace se establece mediante el objeto Binding y que cada enlace tiene normalmente cuatro componentes: un destino de enlace, una propiedad de destino, un origen de enlace y una ruta de acceso al valor de origen que se va a usar. En esta sección describe cómo configurar un enlace.

Los orígenes de enlace están vinculados al DataContext activo del elemento. Los elementos heredan automáticamente su DataContext si no han definido uno explícitamente.

Considere el ejemplo siguiente, en el que el objeto de origen del enlace es una clase denominada MyData que se define en el espacio de nombres SDKSample. A efectos de demostración, MyData tiene una propiedad de cadena denominada ColorName, cuyo valor se establece en "Red". Por lo tanto, este ejemplo genera un botón con un fondo rojo.

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:c="clr-namespace:SDKSample">
    <DockPanel.Resources>
        <c:MyData x:Key="myDataSource"/>
    </DockPanel.Resources>
    <DockPanel.DataContext>
        <Binding Source="{StaticResource myDataSource}"/>
    </DockPanel.DataContext>
    <Button Background="{Binding Path=ColorName}"
            Width="150" Height="30">
        I am bound to be RED!
    </Button>
</DockPanel>

Para más información sobre la sintaxis de declaración de enlaces y ejemplos sobre cómo configurar un enlace en código, vea Información general sobre declaraciones de enlaces.

Si aplicamos este ejemplo a nuestro diagrama básico, la ilustración resultante tendrá el siguiente aspecto. Aquí se describe un enlace OneWay porque la propiedad Background admite el enlace OneWay de forma predeterminada.

Diagrama que muestra Background del enlace de datos.

Es posible que se pregunte por qué funciona este enlace aunque la propiedad ColorName sea de tipo String, mientras que la propiedad Background es de tipo Brush. El enlace usa la conversión predeterminada de tipos, que se explica en la sección Conversión de datos.

Especificar el origen del enlace

Si se fija en el ejemplo anterior, para especificar el origen del enlace se establece la propiedad DockPanel.DataContext. Después, Button hereda el valor DataContext de DockPanel, que es su elemento primario. Recordemos que el objeto de origen del enlace es uno de los cuatro componentes necesarios de un enlace. Por tanto, si no se especifica el objeto de origen del enlace, el enlace no funcionará.

Hay varias formas de especificar el objeto de origen del enlace. Es útil usar la propiedad DataContext en un elemento primario cuando se enlazan varias propiedades al mismo origen. Sin embargo, a veces puede ser más adecuado especificar el origen del enlace en declaraciones de enlace individuales. En el ejemplo anterior, en lugar de usar la propiedad DataContext, puede especificar el origen del enlace si establece la propiedad Binding.Source directamente en la declaración de enlace del botón, como en este ejemplo.

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:c="clr-namespace:SDKSample">
    <DockPanel.Resources>
        <c:MyData x:Key="myDataSource"/>
    </DockPanel.Resources>
    <Button Background="{Binding Source={StaticResource myDataSource}, Path=ColorName}"
            Width="150" Height="30">
        I am bound to be RED!
    </Button>
</DockPanel>

Además de establecer la propiedad DataContext en un elemento directamente, heredar el valor DataContext de un antecesor (como el botón del primer ejemplo) y especificar explícitamente el origen de enlace estableciendo la propiedad Binding.Source en el enlace (por ejemplo, el botón del último ejemplo), también puede usar la propiedad Binding.ElementName o la propiedad Binding.RelativeSource para especificar el origen del enlace. La propiedad ElementName es útil cuando se enlaza a otros elementos de la aplicación, como cuando se usa un control deslizante para ajustar el ancho de un botón. La propiedad RelativeSource es útil cuando el enlace se especifica en ControlTemplate o Style. Para más información, vea Información general sobre orígenes de enlaces.

Especificar la ruta de acceso al valor

Si el origen del enlace es un objeto, use la propiedad Binding.Path para especificar el valor que se va a usar para el enlace. Si va a enlazar a datos XML, use la propiedad Binding.XPath para especificar el valor. En algunos casos, puede aplicarse para usar la propiedad Path incluso cuando los datos son XML. Por ejemplo, si quiere acceder a la propiedad Name de un XmlNode devuelto (como resultado de una consulta XPath), debe usar la propiedad Path además de la propiedad XPath.

Para más información, vea las propiedades Path y XPath.

Aunque hemos recalcado que uno de los cuatro componentes necesarios de un enlace es el valor de Path al valor que se va a usar, en los escenarios en los que se enlaza a un objeto completo, el valor que se usará será el mismo que el objeto de origen del enlace. En esos casos, puede elegir no especificar Path. Considere el ejemplo siguiente.

<ListBox ItemsSource="{Binding}"
         IsSynchronizedWithCurrentItem="true"/>

En el ejemplo anterior se utiliza la sintaxis de enlace vacía: {Binding}. En este caso, ListBox hereda DataContext de un elemento DockPanel primario (no se muestra en este ejemplo). Cuando no se especifica la ruta de acceso, el valor predeterminado es enlazar al objeto completo. En otras palabras, en este ejemplo, la ruta de acceso se ha omitido porque se enlaza la propiedad ItemsSource a todo el objeto. (Vea la sección Enlace a colecciones para saber más).

Además del enlace a una colección, este escenario es útil también cuando se desea enlazar a un objeto completo en lugar de simplemente a una propiedad individual de un objeto. Por ejemplo, si el objeto de origen es de tipo String, puede que solo quiera enlazar a la propia cadena. Otro escenario común es cuando se desea enlazar un elemento a un objeto con varias propiedades.

Puede ser necesario aplicar lógica personalizada para que los datos sean significativos para la propiedad de destino enlazada. La lógica personalizada puede consistir en un convertidor personalizado si no existe la conversión de tipos predeterminada. Vea Conversión de datos para más información sobre los convertidores.

Binding and BindingExpression

Antes de entrar en otras características y usos del enlace de datos, resulta útil presentar la clase BindingExpression. Como hemos visto en las secciones anteriores, la clase Binding es la clase de alto nivel para la declaración de un enlace; proporciona muchas propiedades que permiten especificar las características de un enlace. Una clase relacionada, BindingExpression, es el objeto subyacente que mantiene la conexión entre el origen y el destino. Un enlace contiene toda la información que se puede compartir entre varias expresiones de enlace. Un BindingExpression es una expresión de instancia que no se puede compartir y contiene toda la información de instancia del Binding.

En el ejemplo siguiente, myDataObject es una instancia de la clase MyData, myBinding es el objeto Binding de origen y MyData es una clase definida que contiene una propiedad de cadena denominada ColorName. Aquí se enlaza el contenido de texto de myText, una instancia de TextBlock, a ColorName.

// Make a new source
var myDataObject = new MyData();
var myBinding = new Binding("ColorName")
{
    Source = myDataObject
};

// Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding);
' Make a New source
Dim myDataObject As New MyData
Dim myBinding As New Binding("ColorName")
myBinding.Source = myDataObject

' Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding)

Puede usar el mismo objeto myBinding para crear otros enlaces. Por ejemplo, puede usar el objeto myBinding para enlazar el contenido de texto de una casilla a ColorName. En ese escenario, habrá dos instancias de BindingExpression que comparten el objeto myBinding.

Se devuelve un objeto BindingExpression mediante una llamada a GetBindingExpression en un objeto enlazado a datos. En los artículos siguientes se muestran algunos de los usos de la clase BindingExpression:

Conversión de datos

En la sección Crear un enlace, el botón es rojo porque su propiedad Background está enlazada a una propiedad de cadena con el valor "Red". Este valor de cadena funciona porque hay un convertidor de tipos en el tipo Brush para convertir el valor de cadena en Brush.

Veamos qué ocurre en la imagen de la sección Crear un enlace al agregar esta información.

Diagrama que muestra la propiedad Default del enlace de datos.

Pero ¿y si en lugar de una propiedad de tipo String, el objeto del origen del enlace tiene una propiedad Color de tipo Color? En ese caso, para que el enlace funcione tendrá que convertir el valor de propiedad Color en algo que la propiedad Background pueda aceptar. Necesitaría crear un convertidor personalizado implementando la interfaz IValueConverter, como en este ejemplo.

[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Color color = (Color)value;
        return new SolidColorBrush(color);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
}
<ValueConversion(GetType(Color), GetType(SolidColorBrush))>
Public Class ColorBrushConverter
    Implements IValueConverter
    Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
        Dim color As Color = CType(value, Color)
        Return New SolidColorBrush(color)
    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
        Return Nothing
    End Function
End Class

Vea IValueConverter para obtener más información.

Ahora se ha usado el convertidor personalizado en lugar de la conversión predeterminada y este es el aspecto que tiene nuestro diagrama:

Diagrama que muestra el convertidor personalizado del enlace de datos.

Recordemos que las conversiones predeterminadas pueden estar disponibles si el tipo que se va a enlazar contiene convertidores de tipo. Este comportamiento dependerá de los convertidores de tipos disponibles en el destino. Si no está seguro, cree su propio convertidor.

Estos son algunos escenarios típicos en los que sería lógico implementar un convertidor de datos:

  • Los datos se muestran de forma diferente, dependiendo de la referencia cultural. Por ejemplo, tal vez quiera implementar un convertidor de monedas o un convertidor de fechas y horas del calendario en función de las convenciones usadas en una determinada referencia cultural.

  • Los datos que se utilizan no están diseñados necesariamente para cambiar el valor textual de una propiedad, sino para cambiar otro valor, como el origen de una imagen, o el color o estilo del texto que se va a mostrar. En este caso se pueden utilizar convertidores para convertir el enlace de una propiedad que tal vez no sea adecuada, como el enlace de un campo de texto a la propiedad Background de una celda de tabla.

  • Hay varios controles o varias propiedades de controles enlazados a los mismos datos. En ese caso, el enlace principal podría mostrar simplemente el texto, mientras que los otros enlaces controlan los aspectos de presentación, pero se sigue utilizando el mismo enlace como información de origen.

  • Una propiedad de destino tiene una colección de enlaces, que se denomina MultiBinding. Para MultiBinding, se usa un elemento IMultiValueConverter personalizado para generar un valor final a partir de los valores de los enlaces. Por ejemplo, el color podría calcularse a partir de los valores de rojo, azul y verde, que pueden ser valores de los mismos o de diferentes objetos de origen del enlace. Vea MultiBinding para más ejemplos e información.

Enlace a colecciones

Un objeto de origen del enlace se puede tratar como un objeto único cuyas propiedades contienen los datos, o como una recolección de datos de objetos polimórficos que suelen estar agrupados (como el resultado de una consulta a una base de datos). Hasta ahora solo hemos hablado del enlace a objetos individuales. Pero es muy común el enlace a una recopilación de datos. Por ejemplo, un escenario común es usar ItemsControl como un elemento ListBox, ListView o TreeView para mostrar una recopilación de datos, como en la aplicación que se muestra en la sección ¿Qué es el enlace de datos?.

Afortunadamente, nuestro diagrama básico aún sigue siendo válido. Si enlaza ItemsControl a una colección, el diagrama tiene este aspecto.

Diagrama que muestra el objeto ItemsControl del enlace de datos.

Como se muestra en este diagrama, para enlazar ItemsControl a un objeto de colección, debe usarse la propiedad ItemsControl.ItemsSource. Puede considerar ItemsSource como el contenido de ItemsControl. El enlace es OneWay porque la propiedad ItemsSource admite el enlace OneWay de forma predeterminada.

Implementación de colecciones

Puede enumerar en cualquier colección que implemente la interfaz IEnumerable. Pero para poder configurar enlaces dinámicos para que las inserciones o las eliminaciones en la colección actualicen la interfaz de usuario automáticamente, la colección debe implementar la interfaz INotifyCollectionChanged. Esta interfaz expone un evento que debe provocarse siempre que se realicen cambios en la colección subyacente.

WPF proporciona la clase ObservableCollection<T>, que es una implementación integrada de una recopilación de datos que expone la interfaz INotifyCollectionChanged. Para permitir totalmente la transferencia de valores de datos de los objetos de origen a los destinos, cada objeto de la colección que admite propiedades enlazables debe implementar también la interfaz INotifyPropertyChanged. Para más información, vea Información general sobre orígenes de enlaces.

Antes de implementar su propia colección, considere la posibilidad de usar ObservableCollection<T> o una de las clases de colección existentes, como List<T>, Collection<T> y BindingList<T>, entre muchas otras. Si cuenta con un escenario avanzado y quiere implementar su propia colección, considere la posibilidad de usar IList, que proporciona una colección no genérica de objetos a los que se puede acceder individualmente por índice y, por tanto, proporciona el máximo rendimiento.

Vistas de colecciones

Una vez que ItemsControl esté enlazado a una recopilación de datos, puede ser interesante ordenar, filtrar o agrupar los datos. Para hacerlo, use las vistas de colección, que son clases que implementan la interfaz ICollectionView.

¿Qué son las vistas de colección?

Una vista de colección es un nivel situado encima de la colección de origen del enlace, que le permite navegar y mostrar la colección de origen en función de las consultas de ordenación, filtrado y agrupación, sin tener que cambiar la propia colección de origen subyacente. Una vista de colección también contiene un puntero al elemento actual de la colección. Si la colección de origen implementa la interfaz INotifyCollectionChanged, los cambios generados por el evento CollectionChanged se propagan a las vistas.

Dado que las vistas no cambian las colecciones de origen subyacente, cada colección de origen puede tener varias vistas asociadas. Por ejemplo, puede tener una colección de objetos Task. El uso de vistas le permite mostrar los mismos datos de formas diferentes. Por ejemplo, en el lado izquierdo de la página es posible que desee mostrar las tareas ordenadas por prioridad y, en el lado derecho, agrupadas por área.

Procedimiento para crear una vista

Una manera de crear y utilizar una vista es crear directamente una instancia del objeto de vista y utilizar a continuación esa instancia como el origen del enlace. Por ejemplo, considere la aplicación Data binding demo que se muestra en la sección ¿Qué es el enlace de datos?. La aplicación se implementa de tal forma que ListBox se enlaza a una vista sobre la recopilación de datos en lugar de la colección de datos directamente. El ejemplo siguiente se extrae de la aplicación Data binding demo. La clase CollectionViewSource es el proxy XAML de una clase que hereda de CollectionView. En este ejemplo concreto, el elemento Source de la vista se enlaza a la colección AuctionItems (de tipo ObservableCollection<T>) del objeto de aplicación actual.

<Window.Resources>
    <CollectionViewSource 
      Source="{Binding Source={x:Static Application.Current}, Path=AuctionItems}"   
      x:Key="listingDataView" />
</Window.Resources>

Después, el recurso listingDataView actúa como el origen del enlace para los elementos de la aplicación, como ListBox.

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 
         ItemsSource="{Binding Source={StaticResource listingDataView}}" />

Para crear otra vista para la misma colección, puede crear otra instancia de CollectionViewSource y asignarle un nombre de x:Key diferente.

En la tabla siguiente se muestran los tipos de datos de vista que se crean como vista de colección predeterminada o por CollectionViewSource, según el tipo de colección de origen.

Tipo de colección de origen Tipo de vista de colección Notas
IEnumerable Un tipo interno basado en CollectionView No se pueden agrupar los elementos.
IList ListCollectionView Más rápido.
IBindingList BindingListCollectionView

Uso de una vista predeterminada

Especificar una vista de colección como origen de enlace es una forma de crear y utilizar una vista de colección. WPF también crea una vista de colección predeterminada para cada colección utilizada como origen de enlace. Si enlaza directamente a una colección, WPF enlaza a su vista predeterminada. Todos los enlaces a una misma colección comparten esta vista predeterminada, de modo que si se realiza un cambio en una vista predeterminada a través de un control enlazado o mediante código (como un cambio de ordenación o en el puntero de elemento actual, que se describe más adelante), este se refleja en el resto de los enlaces a la misma colección.

Para obtener la vista predeterminada, use el método GetDefaultView. Para ver un ejemplo, lea Cómo: Obtener la vista predeterminada de una recolección de datos (.NET Framework).

Vistas de colección con DataTables de ADO.NET

Para mejorar el rendimiento, las vistas de colección para objetos DataTable o DataView de ADO.NET delegan la ordenación y el filtrado en DataView; esto hace que la ordenación y el filtrado se compartan entre todas las vistas de colección del origen de datos. Para permitir que cada vista de colección se ordene y se filtre de manera independiente, inicialice cada vista de colección con su propio objeto DataView.

Ordenar

Como se mencionó anteriormente, las vistas pueden aplicar un criterio de ordenación a una colección. Cuando este criterio existe en la colección subyacente, los datos pueden o no tener un orden relevante inherente. La vista de la colección le permite aplicar un orden o cambiar el orden predeterminado, en función de los criterios de comparación especificados. Dado que se trata de una vista basada en el cliente de los datos, un escenario común es que el usuario podría querer ordenar columnas de datos tabulares según el valor al que corresponde la columna. Con las vistas, se puede aplicar esta ordenación controlada por el usuario, sin tener que realizar ningún cambio en la colección subyacente ni tener tampoco que volver a consultar el contenido de la colección. Para ver un ejemplo, lea Procedimiento para ordenar una columna GridView cuando se hace clic en un encabezado (.NET Framework).

En este ejemplo se muestra la lógica de ordenación de CheckBox "Sort by category and date" (Ordenar por categoría y fecha) de la interfaz de usuario de la aplicación en la sección ¿Qué es el enlace de datos?.

private void AddSortCheckBox_Checked(object sender, RoutedEventArgs e)
{
    // Sort the items first by Category and then by StartDate
    listingDataView.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending));
    listingDataView.SortDescriptions.Add(new SortDescription("StartDate", ListSortDirection.Ascending));
}
Private Sub AddSortCheckBox_Checked(sender As Object, e As RoutedEventArgs)
    ' Sort the items first by Category And then by StartDate
    listingDataView.SortDescriptions.Add(New SortDescription("Category", ListSortDirection.Ascending))
    listingDataView.SortDescriptions.Add(New SortDescription("StartDate", ListSortDirection.Ascending))
End Sub

Filtrado

Las vistas también pueden aplicar un filtro a una colección, por lo que la vista muestra solo un subconjunto determinado de la colección completa. Podría filtrar los datos en función de una condición. Por ejemplo, como ocurre en la aplicación de la sección ¿Qué es el enlace de datos?, el control CheckBox "Show only bargains" (Mostrar solo ofertas) contiene lógica para filtrar los artículos que cuestan 25 dólares estadounidenses o más. El código de abajo se ejecuta para establecer ShowOnlyBargainsFilter como el controlador de eventos Filter cuando se selecciona ese CheckBox.

private void AddFilteringCheckBox_Checked(object sender, RoutedEventArgs e)
{
    if (((CheckBox)sender).IsChecked == true)
        listingDataView.Filter += ListingDataView_Filter;
    else
        listingDataView.Filter -= ListingDataView_Filter;
}
Private Sub AddFilteringCheckBox_Checked(sender As Object, e As RoutedEventArgs)
    Dim checkBox = DirectCast(sender, CheckBox)

    If checkBox.IsChecked = True Then
        AddHandler listingDataView.Filter, AddressOf ListingDataView_Filter
    Else
        RemoveHandler listingDataView.Filter, AddressOf ListingDataView_Filter
    End If
End Sub

El controlador de eventos ShowOnlyBargainsFilter se implementa así:

private void ListingDataView_Filter(object sender, FilterEventArgs e)
{
    // Start with everything excluded
    e.Accepted = false;

    // Only inlcude items with a price less than 25
    if (e.Item is AuctionItem product && product.CurrentPrice < 25)
        e.Accepted = true;
}
Private Sub ListingDataView_Filter(sender As Object, e As FilterEventArgs)

    ' Start with everything excluded
    e.Accepted = False

    Dim product As AuctionItem = TryCast(e.Item, AuctionItem)

    If product IsNot Nothing Then

        ' Only include products with prices lower than 25
        If product.CurrentPrice < 25 Then e.Accepted = True

    End If

End Sub

Si directamente usa una de las clases de CollectionView en lugar de CollectionViewSource, usaría la propiedad Filter para especificar una devolución de llamada. Para ver un ejemplo, lea Filtrado de datos en una vista (.NET Framework).

Agrupar

Salvo la clase interna que ve una colección IEnumerable, todas las vistas de colección admiten la agrupación, que permite al usuario dividir la colección en la vista de colección en grupos lógicos. Los grupos pueden ser explícitos, donde el usuario proporciona una lista de grupos, o implícitos, donde los grupos se generan dinámicamente en función de los datos.

En este ejemplo se muestra la lógica del control CheckBox "Group by category" (Agrupar por categoría).

// This groups the items in the view by the property "Category"
var groupDescription = new PropertyGroupDescription();
groupDescription.PropertyName = "Category";
listingDataView.GroupDescriptions.Add(groupDescription);
' This groups the items in the view by the property "Category"
Dim groupDescription = New PropertyGroupDescription()
groupDescription.PropertyName = "Category"
listingDataView.GroupDescriptions.Add(groupDescription)

Para obtener otro ejemplo de agrupación, consulte Agrupación de elementos en un control ListView que implemente un modo GridView (.NET Framework).

Punteros de elemento actual

Las vistas admiten también la noción de elemento actual. Puede navegar por los objetos en una vista de colección. A medida que navega por los objetos, mueve un puntero de elemento que le permite recuperar el objeto ubicado concretamente en esa posición en la colección. Para ver un ejemplo, lea Cómo: Navegar por los objetos de una colección de datos mediante CollectionView (.NET Framework).

Dado que WPF solo se enlaza a una colección mediante una vista (una vista especificada por el usuario o la vista predeterminada de la colección), todos los enlaces a las colecciones tienen un puntero de elemento actual. Al enlazar a una vista, el carácter de barra diagonal ("/") de un valor Path designa el elemento actual de la vista. En el ejemplo siguiente, el contexto de datos es una vista de colección. La primera línea enlaza a la colección. La segunda línea enlaza al elemento actual de la colección. La tercera línea enlaza a la propiedad Description del elemento actual de la colección.

<Button Content="{Binding }" />
<Button Content="{Binding Path=/}" />
<Button Content="{Binding Path=/Description}" />

La sintaxis de barra diagonal y propiedad también puede apilarse para recorrer una jerarquía de colecciones. En el ejemplo siguiente se enlaza al elemento actual de una colección denominada Offices, que es una propiedad del elemento actual de la colección de origen.

<Button Content="{Binding /Offices/}" />

Toda ordenación o filtrado que se aplique a la colección puede afectar al puntero del elemento actual. La ordenación conserva el puntero del elemento actual en el último elemento seleccionado, pero se reestructura la vista de colección a su alrededor. (Quizás el elemento seleccionado estaba antes al principio de la lista, pero ahora puede que esté en alguna parte del medio). El filtrado conserva el elemento seleccionado si la selección permanece en la vista después del filtrado. De lo contrario, el puntero del elemento actual se establece en el primer elemento de la vista de colección filtrada.

Escenario de enlace principal-detalle

La noción de elemento actual no es solo útil para la navegación de elementos en una colección, sino también para el escenario de enlace principal-detalle. Piense de nuevo en la interfaz de usuario de aplicación de la sección ¿Qué es el enlace de datos?. En esa aplicación, la selección dentro de ListBox determina el contenido que se muestra en el control ContentControl. Dicho de otro modo, cuando se selecciona un elemento ListBox, el control ContentControl muestra los detalles del elemento seleccionado.

Puede implementar el escenario principal-detalle simplemente con dos o más controles enlazados a la misma vista. En este ejemplo de Data binding demo se muestra el marcado de ListBox y de ContentControl que se ven en la interfaz de usuario de la aplicación en la sección ¿Qué es el enlace de datos?.

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 
         ItemsSource="{Binding Source={StaticResource listingDataView}}" />
<ContentControl Name="Detail" Grid.Row="3" Grid.ColumnSpan="3"
                Content="{Binding Source={StaticResource listingDataView}}"
                ContentTemplate="{StaticResource detailsProductListingTemplate}" 
                Margin="9,0,0,0"/>

Observe que ambos controles están enlazados al mismo origen, el recurso estático listingDataView (vea la definición de este recurso en la sección Procedimiento para crear una vista). Este enlace funciona porque cuando se enlaza un objeto singleton (ContentControl en este caso) a una vista de colección, se enlaza automáticamente al objeto CurrentItem de la vista. Los objetos CollectionViewSource sincronizan automáticamente la moneda y la selección. Si el control de lista no está enlazado a un objeto CollectionViewSource como ocurre en este ejemplo, sería necesario establecer su propiedad IsSynchronizedWithCurrentItem en true para que funcione.

Si quiere ver otros ejemplos, lea Cómo: Enlazar a una colección y mostrar información basada en la selección (.NET Framework) y Cómo: Usar el patrón principal-detalle con datos jerárquicos (.NET Framework).

Tal vez haya observado que en el ejemplo anterior se utiliza una plantilla. De hecho, los datos no se mostrarían tal y como queremos si no se usan plantillas (la que usa explícitamente ContentControl y la que usa implícitamente ListBox). Trataremos el tema de la inclusión de datos en plantillas en la siguiente sección.

Plantillas de datos

Si no se usan plantillas de datos, la interfaz de usuario de la aplicación de la sección Ejemplo de enlace de datos tendría este aspecto:

Demo de enlace de datos sin plantillas de datos

Como se muestra en el ejemplo de la sección anterior, los controles ListBox y ContentControl están enlazados al objeto de colección completo (concretamente, a la vista sobre el objeto de colección) de los objetos AuctionItem. Sin instrucciones específicas sobre cómo mostrar la recopilación de datos, ListBox muestra la representación de cadena de cada objeto de la colección subyacente y ContentControl muestra la representación de cadena del objeto al que está enlazado.

Para resolver ese problema, la aplicación define DataTemplates. Como se muestra en el ejemplo de la sección anterior, ContentControl usa explícitamente la plantilla de datos detailsProductListingTemplate. El control ListBox usa implícitamente esta plantilla de datos al mostrar los objetos AuctionItem en la colección.

<DataTemplate DataType="{x:Type src:AuctionItem}">
    <Border BorderThickness="1" BorderBrush="Gray"
            Padding="7" Name="border" Margin="3" Width="500">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="20"/>
                <ColumnDefinition Width="86"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <Polygon Grid.Row="0" Grid.Column="0" Grid.RowSpan="4"
                     Fill="Yellow" Stroke="Black" StrokeThickness="1"
                     StrokeLineJoin="Round" Width="20" Height="20"
                     Stretch="Fill"
                     Points="9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7"
                     Visibility="Hidden" Name="star"/>

            <TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,8,0"
                       Name="descriptionTitle"
                       Style="{StaticResource smallTitleStyle}">Description:</TextBlock>
            
            <TextBlock Name="DescriptionDTDataType" Grid.Row="0" Grid.Column="2"
                       Text="{Binding Path=Description}"
                       Style="{StaticResource textStyleTextBlock}"/>

            <TextBlock Grid.Row="1" Grid.Column="1" Margin="0,0,8,0"
                       Name="currentPriceTitle"
                       Style="{StaticResource smallTitleStyle}">Current Price:</TextBlock>
            
            <StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">
                <TextBlock Text="$" Style="{StaticResource textStyleTextBlock}"/>
                <TextBlock Name="CurrentPriceDTDataType"
                           Text="{Binding Path=CurrentPrice}" 
                           Style="{StaticResource textStyleTextBlock}"/>
            </StackPanel>
        </Grid>
    </Border>
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding Path=SpecialFeatures}">
            <DataTrigger.Value>
                <src:SpecialFeatures>Color</src:SpecialFeatures>
            </DataTrigger.Value>
            <DataTrigger.Setters>
                <Setter Property="BorderBrush" Value="DodgerBlue" TargetName="border" />
                <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
                <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
                <Setter Property="BorderThickness" Value="3" TargetName="border" />
                <Setter Property="Padding" Value="5" TargetName="border" />
            </DataTrigger.Setters>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=SpecialFeatures}">
            <DataTrigger.Value>
                <src:SpecialFeatures>Highlight</src:SpecialFeatures>
            </DataTrigger.Value>
            <Setter Property="BorderBrush" Value="Orange" TargetName="border" />
            <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
            <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
            <Setter Property="Visibility" Value="Visible" TargetName="star" />
            <Setter Property="BorderThickness" Value="3" TargetName="border" />
            <Setter Property="Padding" Value="5" TargetName="border" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

Al usar estos dos objetos DataTemplate, la interfaz de usuario que se obtiene es la que se muestra en la sección ¿Qué es el enlace de datos?. Como puede ver en esa captura de pantalla, además de permitir colocar los datos en los controles, los objetos DataTemplate permiten darle un aspecto atractivo a los datos. Por ejemplo, los objetos DataTrigger se usan en el objeto DataTemplate anterior para que los objetos AuctionItem con el valor SpecialFeatures en HighLight se muestren con un borde naranja y una estrella.

Para obtener más información sobre las plantillas de datos, vea Información general sobre plantillas de datos (.NET Framework).

Validación de datos

La mayoría de las aplicaciones que toman datos proporcionados por el usuario necesitan lógica de validación para asegurarse de que el usuario ha escrito la información esperada. Las comprobaciones de validación pueden basarse en el tipo, el intervalo, el formato u otros requisitos específicos de la aplicación. En esta sección se describe cómo funciona la validación de datos en WPF.

Asociar reglas de validación a un enlace

El modelo de enlace de datos de WPF permite asociar ValidationRules con el objeto Binding. Por ejemplo, en este ejemplo se enlaza un objeto TextBox a una propiedad StartPrice y se agrega un objeto ExceptionValidationRule a la propiedad Binding.ValidationRules.

<TextBox Name="StartPriceEntryForm" Grid.Row="2"
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartPrice" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <ExceptionValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Un objeto ValidationRule comprueba si el valor de una propiedad es válido. WPF tiene dos tipos de objetos ValidationRule integrados:

También puede crear su propia regla de validación si deriva de la clase ValidationRule e implementa el método Validate. En este ejemplo se muestra la regla usada por el control TextBox "Start Date" (Fecha de inicio) de Add Product Listing (Agregar lista de productos) de la sección ¿Qué es el enlace de datos?.

public class FutureDateRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        // Test if date is valid
        if (DateTime.TryParse(value.ToString(), out DateTime date))
        {
            // Date is not in the future, fail
            if (DateTime.Now > date)
                return new ValidationResult(false, "Please enter a date in the future.");
        }
        else
        {
            // Date is not a valid date, fail
            return new ValidationResult(false, "Value is not a valid date.");
        }

        // Date is valid and in the future, pass
        return ValidationResult.ValidResult;
    }
}
Public Class FutureDateRule
    Inherits ValidationRule

    Public Overrides Function Validate(value As Object, cultureInfo As CultureInfo) As ValidationResult

        Dim inputDate As Date

        ' Test if date is valid
        If Date.TryParse(value.ToString, inputDate) Then

            ' Date is not in the future, fail
            If Date.Now > inputDate Then
                Return New ValidationResult(False, "Please enter a date in the future.")
            End If

        Else
            ' // Date Is Not a valid date, fail
            Return New ValidationResult(False, "Value is not a valid date.")
        End If

        ' Date is valid and in the future, pass
        Return ValidationResult.ValidResult

    End Function

End Class

El control TextBoxStartDateEntryForm usa FutureDateRule, tal y como se muestra en este ejemplo.

<TextBox Name="StartDateEntryForm" Grid.Row="3"
         Validation.ErrorTemplate="{StaticResource validationTemplate}" 
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged" 
                 Converter="{StaticResource dateConverter}" >
            <Binding.ValidationRules>
                <src:FutureDateRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Dado que el valor UpdateSourceTrigger es PropertyChanged, el motor de enlace actualiza el valor de origen en cada pulsación de tecla, lo que significa que también comprueba todas las reglas de la colección ValidationRules en cada pulsación de tecla. Ofreceremos una descripción más detallada en la sección Proceso de validación.

Proporcionar comentarios visuales

Si el usuario especifica un valor no válido, puede ser interesante proporcionar algunos comentarios sobre el error en la interfaz de usuario de la aplicación. Una manera de proporcionar estos comentarios es establecer la propiedad adjunta Validation.ErrorTemplate en un control ControlTemplate personalizado. Como se muestra en la subsección anterior, el control TextBoxStartDateEntryForm usa un control ErrorTemplate denominado validationTemplate. En este ejemplo se muestra la definición de validationTemplate.

<ControlTemplate x:Key="validationTemplate">
    <DockPanel>
        <TextBlock Foreground="Red" FontSize="20">!</TextBlock>
        <AdornedElementPlaceholder/>
    </DockPanel>
</ControlTemplate>

El elemento AdornedElementPlaceholder especifica dónde se debe colocar el control que se va a adornar.

Además, también puede usar un ToolTip para mostrar el mensaje de error. Los controles TextBoxStartDateEntryForm y StartPriceEntryForm usan el estilo textStyleTextBox, que crea un elemento ToolTip que muestra el mensaje de error. En el ejemplo siguiente se muestra la definición de textStyleTextBox. La propiedad adjunta Validation.HasError es true cuando uno o varios de los enlaces de las propiedades del elemento enlazado son erróneos.

<Style x:Key="textStyleTextBox" TargetType="TextBox">
    <Setter Property="Foreground" Value="#333333" />
    <Setter Property="MaxLength" Value="40" />
    <Setter Property="Width" Value="392" />
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip" 
                    Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={RelativeSource Self}}" />
        </Trigger>
    </Style.Triggers>
</Style>

Con los controles ErrorTemplate y ToolTip personalizados, el elemento TextBoxStartDateEntryForm tiene un aspecto similar a este cuando se produce un error de validación.

Error de validación de enlace de datos para la fecha

Si el elemento Binding tiene reglas de validación asociadas pero no especifica un elemento ErrorTemplate en el control enlazado, se usará un elemento ErrorTemplate predeterminado para notificar a los usuarios cuando se produzca un error de validación. El elemento ErrorTemplate predeterminado es una plantilla de control que define un borde rojo en la capa de adornos. Con los controles predeterminados ErrorTemplate y ToolTip, la interfaz de usuario del control TextBoxStartPriceEntryForm es similar a la siguiente cuando se produce un error de validación.

Error de validación de enlace de datos para el precio

Para ver un ejemplo sobre cómo proporcionar lógica para validar todos los controles de un cuadro de diálogo, vea la sección Cuadros de diálogo personalizados en Resumen de cuadros de diálogo.

Proceso de validación

La validación normalmente se produce cuando el valor de un destino se transfiere a la propiedad de origen del enlace. Esta transferencia se produce en los enlaces TwoWay y OneWayToSource. Recuerde que lo que hace una actualización de origen depende del valor de la propiedad UpdateSourceTrigger, tal y como se describe en la sección Qué desencadena la actualización del origen.

Los elementos siguientes describen el proceso de validación. Si se produce un error de validación o de cualquier otro tipo en cualquier momento del proceso, este se detiene:

  1. El motor de enlace comprueba si hay definido algún objeto ValidationRule personalizado cuyo ValidationStep esté establecido en RawProposedValue para ese Binding, en cuyo caso llama al método Validate en cada ValidationRule hasta que uno de ellos genere un error o hasta que todos ellos pasen.

  2. A continuación, el motor de enlace llama al convertidor, si existe alguno.

  3. Si el convertidor se ejecuta correctamente, el motor de enlace comprueba si hay definido algún objeto ValidationRule personalizado cuyo ValidationStep está establecido en ConvertedProposedValue para ese Binding, en cuyo caso llama al método Validate en cada ValidationRule que tiene ValidationStep establecido en ConvertedProposedValue, hasta que uno de ellos se ejecuta en un error o hasta que todos ellos pasan.

  4. El motor de enlace establece la propiedad de origen.

  5. El motor de enlace comprueba si hay definido algún objeto ValidationRule personalizado cuyo ValidationStep esté establecido en UpdatedValue para ese Binding, en cuyo caso llama al método Validate en cada ValidationRule que tenga ValidationStep establecido en UpdatedValue hasta que uno de ellos genere un error o hasta que todos ellos pasen. Si un elemento DataErrorValidationRule está asociado a un enlace y ValidationStep está establecido en el valor predeterminado, UpdatedValue, se comprueba DataErrorValidationRule en este momento. En este punto, se comprueba cualquier enlace que tenga ValidatesOnDataErrors establecido en true.

  6. El motor de enlace comprueba si hay definido algún objeto ValidationRule personalizado cuyo ValidationStep esté establecido en CommittedValue para ese Binding, en cuyo caso llama al método Validate en cada ValidationRule que tenga ValidationStep establecido en CommittedValue hasta que uno de ellos genere un error o hasta que todos ellos pasen.

Si ValidationRule no pasa en ningún momento de todo este proceso, el motor de enlace crea un objeto ValidationError y lo agrega a la colección Validation.Errors del elemento enlazado. Antes de que el motor de enlace ejecute los objetos ValidationRule en cualquier paso, quita cualquier ValidationError que se haya agregado a la propiedad adjunta Validation.Errors del elemento enlazado durante ese paso. Por ejemplo, si un elemento ValidationRule cuyo ValidationStep está establecido en UpdatedValue genera un error, la próxima vez que se produzca el proceso de validación, el motor de enlace quitará ese elemento ValidationError inmediatamente antes de llamar a cualquier ValidationRule que tenga ValidationStep establecido en UpdatedValue.

Cuando Validation.Errors no está vacío, propiedad adjunta Validation.HasError del elemento se establece en true. Además, si la propiedad NotifyOnValidationError de Binding está establecida en true, el motor de enlace genera el evento adjunto Validation.Error en el elemento.

Tenga en cuenta también que una transferencia de valor válida en cualquier dirección (destino a origen u origen a destino) borra la propiedad adjunta Validation.Errors.

Si el enlace tiene un elemento ExceptionValidationRule asociado o tiene la propiedad ValidatesOnExceptions establecida en true y se produce una excepción cuando el motor de enlace establece el origen, el motor de enlace comprueba si hay UpdateSourceExceptionFilter. Puede usar la devolución de llamada de UpdateSourceExceptionFilter para proporcionar un controlador personalizado para controlar excepciones. Si no se especifica UpdateSourceExceptionFilter en Binding, el motor de enlace crea ValidationError con la excepción y lo agrega a la colección Validation.Errors del elemento enlazado.

Mecanismo de depuración

Puede establecer la propiedad adjunta PresentationTraceSources.TraceLevel en un objeto relacionado con el enlace para recibir información sobre el estado de un enlace concreto.

Consulte también