Compartir a través de


Enlace de datos en profundidad de Windows

En este artículo, describiremos las características de enlace de los datos del SDK de Windows App para las API que residen en el espacio de nombres Microsoft.UI.Xaml.Data.

Nota

En este tema se describen detalladamente las características del enlace de datos. Para obtener una introducción breve y práctica, consulta Introducción al enlace de datos.

API importantes

Introducción

El enlace de datos es una forma para que la interfaz de usuario de la aplicación muestre los datos y, opcionalmente, se mantenga sincronizada con dichos datos. El enlace de datos permite separar lo que concierne a los datos de lo que concierne a la interfaz de usuario, lo que da como resultado un modelo conceptual más sencillo y una mejor legibilidad, comprobación y mantenimiento de la aplicación.

Puedes usar el enlace de datos para simplemente mostrar los valores de un origen de datos aparece en primer lugar la interfaz de usuario, pero no para responder a cambios en dichos valores. Este es un modo de enlace denominado único y funciona bien para un valor que no cambia en tiempo de ejecución. También puedes elegir "observar" los valores y actualizar la interfaz de usuario cuando cambien. Este modo se denomina enlace unidireccional y funciona bien para datos de solo lectura. En última instancia, puedes elegir observar y actualizar para que los cambios que realiza el usuario a los valores de la interfaz de usuario se envíen automáticamente al origen de datos. Este modo se denomina enlace bidireccional y funciona bien para datos de lectura y escritura. A continuación se muestran algunos ejemplos.

  • Puedes usar el modo único para enlazar un elemento Image con la foto del usuario actual.
  • Puedes usar el modo unidireccional para enlazar un elemento ListView con una colección de artículos de noticias en tiempo real agrupados por sección de periódico.
  • Puedes usar el modo bidireccional para enlazar un elemento TextBox con el nombre de un cliente en un formulario.

Independientemente del modo, existen dos tipos de enlaces y, generalmente, ambos se declaran en el marcado de interfaz de usuario. Puedes usar la extensión de marcado {x:Bind} o extensión de marcado {Binding}. Incluso puedes usar una combinación de ambos en la misma aplicación, aun en el mismo elemento de la interfaz de usuario. {x:Bind} era nuevo en UWP para Windows 10 y tiene un mejor rendimiento. Todos los detalles que se describen en este tema se aplican a ambos tipos de enlace, a menos que especifiquemos explícitamente lo contrario.

Aplicaciones para UWP de ejemplo que muestran {x:Bind}

Aplicaciones para UWP de ejemplo que muestran {Binding}

Cada enlace implica estas piezas

  • Un origen de enlace. Este es el origen de los datos para el enlace y puede ser una instancia de cualquier clase que tenga miembros cuyos valores que quieres mostrar en la interfaz de usuario.
  • Un destino de enlace. Se trata de una DependencyProperty del FrameworkElement en la interfaz de usuario que muestra los datos.
  • Un objeto de enlace. Este es el fragmento que transfiere los valores de datos del origen al destino y, opcionalmente, del destino nuevamente al origen. Se crea el objeto de enlace en tiempo de carga de XAML desde tu {x:Bind} o la extensión de marcado {Binding}.

En las siguientes secciones, se echaremos un vistazo al origen de enlace, el destino de enlace y el objeto de enlace. Además, vincularemos las secciones junto con el ejemplo de enlace de contenido de un botón a una propiedad de cadena denominada NextButtonText, que pertenece a una clase denominada HostViewModel.

Origen de enlace

Esta es una implementación muy rudimentaria de una clase que podríamos usar como origen de enlace.

public class HostViewModel
{
    public HostViewModel()
    {
        NextButtonText = "Next";
    }

    public string NextButtonText { get; set; }
}

La implementación de HostViewModel y su propiedad NextButtonText solo es apropiada para el enlace único. Sin embargo, los enlace unidireccionales y bidireccionales son muy comunes y, en dichos tipos de enlaces, la interfaz de usuario se actualiza automáticamente en respuesta a cambios en los valores de datos del origen del enlace. Para que esos tipos de enlace funcionen correctamente, debe colocar al origen de enlace como observable en el objeto de enlace. Así, en nuestro ejemplo, si queremos un enlace unidireccional o bidireccional con la propiedad NextButtonText y, a continuación, cualquier cambio que se produzca en tiempo de ejecución en el valor de esa propiedad debe hacerse observable para el objeto de enlace.

Una manera de hacerlo es derivar la clase que representa el origen de enlace de DependencyObject y exponer un valor de datos a través de una DependencyProperty. De este modo, un FrameworkElement pasa a ser observable. Un FrameworkElement es un buen origen de enlace desde el primer momento.

Una manera más ligera de hacer que una clase sea observable, y necesaria para las clases que ya tienen una clase base, es implementar System.ComponentModel.INotifyPropertyChanged. Esto simplemente implica la implementación de un solo evento denominado PropertyChanged. A continuación, puede observar un ejemplo usando HostViewModel.

...
using System.ComponentModel;
using System.Runtime.CompilerServices;
...
public class HostViewModel : INotifyPropertyChanged
{
    private string nextButtonText;

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public HostViewModel()
    {
        NextButtonText = "Next";
    }

    public string NextButtonText
    {
        get { return nextButtonText; }
        set
        {
            nextButtonText = value;
            OnPropertyChanged();
        }
    }

    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        // Raise the PropertyChanged event, passing the name of the property whose value has changed.
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Ahora la propiedad NextButtonText es observable. Cuando creas una enlace unidireccional o bidireccional con esa propiedad (te mostraremos cómo más adelante), el objeto de enlace resultante se suscribe al evento PropertyChanged. Cuando se genera el evento, controlador del objeto de enlace recibe un argumento que contiene el nombre de la propiedad que ha cambiado. Así es cómo el objeto de enlace sabe a qué valor de propiedad dirigirse y volver a leer.

Para que no tengas que implementar el patrón mostrado anteriormente varias veces, si usas C#, puedes derivar de la clase base BindableBase que encontrarás en el ejemplo de QuizGame (en la carpeta "Common"). Este es un ejemplo de cómo queda.

public class HostViewModel : BindableBase
{
    private string nextButtonText;

    public HostViewModel()
    {
        NextButtonText = "Next";
    }

    public string NextButtonText
    {
        get { return nextButtonText; }
        set { SetProperty(ref nextButtonText, value); }
    }
}

Generar el evento PropertyChanged con un argumento de String.Empty o null indica que se deben volver a leer todas las propiedades no indexadoras del objeto. Puede generar el evento para indica que las propiedades de indizador del objeto han cambiado mediante un argumento de "Item[indexer]" para indexadores específicos (donde indexer es el valor de índice) o el valor de "Item[]" para todos los indexadores.

Un objeto de enlace puede tratarse como un solo objeto cuyas propiedades contienen datos, o bien, como una colección de objetos. En el código de C#, puedes enlazar una sola vez a un objeto que implementa List<T> para mostrar una colección que no cambia en tiempo de ejecución. Para una colección observable (observar cuando se agregan o quitan los elementos de la colección), se realiza un enlace unidireccional a ObservableCollection<T> en su lugar. Para enlazar tus propias clases de colecciones, usa las directrices de la siguiente tabla.

Escenario C# (CLR) C++/WinRT
Enlazar a un objeto. Puede ser cualquier objeto. Puede ser cualquier objeto.
Obtener notificaciones de cambio de propiedades de un objeto enlazado. El objeto debe implementar INotifyPropertyChanged. El objeto debe implementar INotifyPropertyChanged.
Enlazar a una colección. List<T> IVector de IInspectable o IBindableObservableVector. Consulta Controles de elementos de XAML; enlazar a una colección C++/WinRT y Colecciones con C++/WinRT.
Obtener notificaciones de cambios de colecciones de una colección enlazada. ObservableCollection<T> IObservableVector de IInspectable. Por ejemplo, winrt::single_threaded_observable_vector<T>.
Implementar una colección compatible con enlaces. Extienda List<T> o implemente IList, IList<Object>, IEnumerable o IEnumerable<Object>. No se admite el enlace a IList<T> y IEnumerable<T> genéricos. Implementa IVector de IInspectable. Consulta Controles de elementos de XAML; enlazar a una colección C++/WinRT y Colecciones con C++/WinRT.
Implementar una colección que admita notificaciones de cambios de colecciones. Extienda ObservableCollection<T> o implemente IList y INotifyCollectionChanged. Implementa IObservableVector de IInspectable o IBindableObservableVector.
Implementar una colección compatible con la carga incremental. Extienda ObservableCollection<T> o implemente IList y INotifyCollectionChanged. Además, implementa ISupportIncrementalLoading. Implementa IObservableVector de IInspectable o IBindableObservableVector. Además, implementa ISupportIncrementalLoading.

Con la carga incremental, puedes enlazar controles de lista a orígenes de datos que son arbitrariamente de gran tamaño y aun así lograr un alto rendimiento. Por ejemplo, puedes enlazar controles de lista a resultados de consulta de imágenes de Bing sin tener que cargarlos a todos de una vez. Solo cargas algunos resultados inmediatamente y después cargas otros, según sea necesario. Para admitir la carga incremental, debes implementar ISupportIncrementalLoading en un origen de datos compatible con las notificaciones de cambios de colección. Cuando el motor de enlace de datos solicite más datos, tu origen de datos debe realizar las solicitudes apropiadas, integrar los resultados y después enviar las debidas notificaciones para actualizar la interfaz de usuario.

Destino de enlace

En los dos ejemplos siguientes, la propiedad Button.Content es el destino de enlace y su valor se establece en una extensión de marcado que declara el objeto de enlace. Se muestra el primer {x:Bind} y luego {Binding}. Declarar enlaces en el marcado es el caso común (es cómodo, legible y administrable). Sin embargo, puedes evitar el marcado y de manera imperativa (mediante programación) crear una instancia de la clase Binding en su lugar, si necesitas.

<Button Content="{x:Bind ...}" ... />
<Button Content="{Binding ...}" ... />

Objeto de enlace que se declaran usando {x:Bind}

Hay un solo paso que necesitamos ejecutar antes de crear nuestro marcado {x:Bind}. Tenemos que exponer nuestra clase de origen de enlace desde la clase que representa nuestra página de marcado. Eso lo haremos agregando una propiedad (de tipo HostViewModel en este caso) a nuestra clase de ventana MainWindow.

namespace DataBindingInDepth
{
    public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
            ViewModel = new HostViewModel();
        }
    
        public HostViewModel ViewModel { get; set; }
    }
}

Una vez que hayas hecho esto, puedes analizar más minuciosamente el marcado que declara el objeto de enlace. El siguiente ejemplo usa el mismo destino de enlace Button.Content que usamos en la sección "Destino de enlace" anteriormente, y muestra que está enlazado a la propiedad HostViewModel.NextButtonText.

<!-- MainWindow.xaml -->
<Window x:Class="DataBindingInDepth.MainWindow" ... >
    <Button Content="{x:Bind Path=ViewModel.NextButtonText, Mode=OneWay}" ... />
</Window>

Observa el valor que especificamos para Path. Este valor se interpreta en el contexto de la ventana en sí y, en este caso, la ruta comienza haciendo referencia a la propiedad ViewModel que acabamos de agregar a la página MainWindow. Esa propiedad devuelve una instancia HostViewModel, así podemos usar el operador punto en ese objeto para acceder a la propiedad HostViewModel.NextButtonText. Además, especificamos Mode para invalidar el {x:Bind} predeterminado de una sola vez.

La propiedad Path admite una variedad de opciones de sintaxis para enlazar a propiedades anidadas, propiedades adjuntas e indexadores de cadenas y de enteros. Para más información, consulta Sintaxis de property-path. El enlace a indexadores de cadenas te ofrece el mismo efecto que el enlace a propiedades dinámicas sin tener que implementar ICustomPropertyProvider. Para otras opciones de configuración, consulta Extensión de marcado {x:Bind}.

Para ilustrar que la propiedad HostViewModel.NextButtonText es realmente observable, agrega un controlador de eventos Click al botón y actualiza el valor de HostViewModel.NextButtonText. Compila, ejecuta y haz clic en el botón para ver el valor de actualización de Content del botón.

// MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    ViewModel.NextButtonText = "Updated Next button text";
}

Nota

Los cambios en TextBox.Text se envían a un origen de enlace bidireccional cuando TextBox pierde el foco y no después de cada presión de tecla del usuario.

DataTemplate y x:DataType

Dentro de DataTemplate (independientemente de si se usa como plantilla de elemento, plantilla de contenido o plantilla de encabezado), el valor de Path no se interpreta en el contexto de la ventana, sino en el contexto del objeto de datos al que se aplica la plantilla. Cuando se usa {x:Bind} en una plantilla de datos para que sus enlaces puedan validarse (y un código eficaz generado para ellos) en tiempo de compilación, la clase DataTemplate debe declarar el tipo del objeto de datos mediante x:DataType. El ejemplo siguiente puede usarse como ItemTemplate de un control de elementos enlazados a una colección de objetos SampleDataGroup.

<DataTemplate x:Key="SimpleItemTemplate" x:DataType="data:SampleDataGroup">
    <StackPanel Orientation="Vertical" Height="50">
      <TextBlock Text="{x:Bind Title}"/>
      <TextBlock Text="{x:Bind Description}"/>
    </StackPanel>
  </DataTemplate>

Objetos poco tipificados en la ruta de acceso

Consideremos, por ejemplo, que tiene un tipo denominado SampleDataGroup, que implementa una propiedad de cadena denominada Title. Además, tiene una propiedad MainWindow.SampleDataGroupAsObject, que es de tipo object, pero que devuelve realmente una instancia de SampleDataGroup. El enlace <TextBlock Text="{x:Bind SampleDataGroupAsObject.Title}"/> generará un error de compilación porque la propiedad Title no se encuentra en el tipo object. La solución para esto es agregar una conversión a la sintaxis de Path como esta: <TextBlock Text="{x:Bind ((data:SampleDataGroup)SampleDataGroupAsObject).Title}"/>. Este es otro ejemplo donde se declara Element como object, pero que realmente es TextBlock: <TextBlock Text="{x:Bind Element.Text}"/>. Y una conversión soluciona el problema: <TextBlock Text="{x:Bind ((TextBlock)Element).Text}"/>.

Si los datos se cargan de forma asincrónica

El código para admitir {x:Bind} se genera en tiempo de compilación en las clases parciales para las ventanas. Estos archivos pueden encontrarse en la carpeta obj, con nombres como (para C#) <view name>.g.cs. El código generado incluye un controlador para el evento Loading de la ventana y ese controlador llama al método Initialize en una clase generada que representa los enlaces de la ventana. Initialize llama a su vez a Update para empezar a mover datos entre el origen de enlace y el destino. Loading se genera justo antes del primer paso de medida de la ventana o del control de usuario. Por lo que si los datos se cargan de forma asincrónica puede que no esté listo en el momento en que se llama a Initialize. Por lo tanto, después de cargar los datos, puedes forzar la inicialización de enlaces de un solo uso llamando a this.Bindings.Update();. Si solo necesitas enlaces de un solo uso para los datos cargados de forma asincrónica es mucho más barato inicializarlos así que tener enlaces unidireccionales y escuchar los cambios. Si los datos no sufren cambios específicos y es probable que se actualicen como parte de una acción específica, puedes hacer que tus enlaces sean de un solo uso y forzar una actualización manual en cualquier momento con una llamada a Update.

Nota

{x:Bind} no es adecuado para los escenarios enlazados en tiempo de ejecución, como la navegación por la estructura de diccionario de un objeto JSON, como tampoco lo es el estilo duck typing. "Duck typing" es un formato de escritura no seguro basado en las correspondencias léxicas de los nombres de propiedad (p. ej., "si anda, nada y grazna como un pato, entonces es un pato"). Con duck typing, un enlace a la propiedad Age podría corresponder a un objeto Person o a un objeto Wine (asumiendo que ambos tipos tuvieran una propiedad de Age). Para estos escenarios, usa la extensión de marcado {Binding} .

Objeto de enlace que se declara usando {Binding}

{Binding} supone que, de forma predeterminada, se está realizando un enlace a la propiedad DataContext de la ventana de marcado. Por lo tanto, estableceremos el DataContext de la ventana para que sea una instancia de la clase de origen de enlace (de tipo HostViewModel en este caso). El siguiente ejemplo muestra el marcado que declara el objeto de enlace. Usamos el mismo destino de enlace Button.Content que usamos en la sección "Destino de enlace" anterior, y enlazamos a la propiedad HostViewModel.NextButtonText.

<Window xmlns:viewmodel="using:DataBindingInDepth" ... >
    <Window.DataContext>
        <viewmodel:HostViewModel x:Name="viewModelInDataContext"/>
    </Window.DataContext>
    ...
    <Button Content="{Binding Path=NextButtonText}" ... />
</Window>
// MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    viewModelInDataContext.NextButtonText = "Updated Next button text";
}

Observa el valor que especificamos para Path. Este valor se interpreta en el contexto de DataContext de la ventana, que en este ejemplo se establece en una instancia de HostViewModel. La ruta hace referencia a la propiedad HostViewModel.NextButtonText. Podemos omitir Mode, porque la {Binding} predeterminada unidireccional funciona aquí.

El valor predeterminado de DataContext para un elemento de la interfaz de usuario es el valor heredado de su elemento principal. Por supuesto, puedes anular ese valor predeterminado estableciendo DataContext explícitamente, que se hereda de forma predeterminada por los elementos secundarios. La configuración de DataContext explícitamente en un elemento es cuando quieres tener varios enlaces que usen el mismo origen.

Un objeto de enlace tiene una propiedad Source, que el valor predeterminado es el DataContext del elemento de interfaz de usuario en el que se declara el enlace. Puedes invalidar este valor predeterminado estableciendo Source, RelativeSource, o ElementName explícitamente en el enlace (consulte {Binding} para obtener más información).

Dentro de DataTemplate, DataContext se establece automáticamente en el objeto de datos con plantilla. El ejemplo siguiente puede usarse como la propiedad ItemTemplate de un control de elementos enlazado a una colección de cualquier tipo que tiene propiedades de cadena denominadas Title y Description.

<DataTemplate x:Key="SimpleItemTemplate">
    <StackPanel Orientation="Vertical" Height="50">
      <TextBlock Text="{Binding Title}"/>
      <TextBlock Text="{Binding Description"/>
    </StackPanel>
  </DataTemplate>

Nota

De forma predeterminada, los cambios en TextBox.Text se envían a un origen de enlace bidireccional cuando la clase TextBox pierde el foco. Para hacer que los cambios se envíen después de cada presión de tecla de usuario, establece UpdateSourceTrigger en PropertyChanged en el enlace en el marcado. También puede tomar el control completo de cuándo se envían los datos al origen estableciendo UpdateSourceTrigger en Explicit. Luego puedes controlar eventos en el cuadro de texto (normalmente TextBox.TextChanged), llamar a GetBindingExpression en el destino para obtener un BindingExpression y finalmente llamar a BindingExpression.UpdateSource para actualizar mediante programación el origen de datos.

La propiedad Path admite una variedad de opciones de sintaxis para enlazar a propiedades anidadas, propiedades adjuntas e indexadores de cadenas y de enteros. Para más información, consulta Sintaxis de property-path. El enlace a indexadores de cadenas te ofrece el mismo efecto que el enlace a propiedades dinámicas sin tener que implementar ICustomPropertyProvider. La propiedad ElementName es útil para el enlace de elemento a elemento. La propiedad RelativeSource tiene varios usos, uno de los cuales es como una alternativa más eficaz para el enlace de plantilla dentro de un ControlTemplate. Para otras opciones de configuración, consulta extensión de marcado {Binding} y la clase Binding.

¿Qué ocurre si el origen y el destino no son del mismo tipo?

Si desea controlar la visibilidad de un elemento de interfaz de usuario en función del valor de una propiedad booleana o si quieres representar un elemento de interfaz de usuario con un color que es una función de un intervalo o tendencia de un valor numérico o si deseas mostrar un valor de fecha y/u hora en una propiedad del elemento de interfaz de usuario que espera una cadena , necesitarás convertir los valores de un tipo a otro. Habrá casos en los que la solución correcta es exponer otra propiedad del tipo correcto desde la clase de origen de enlace y mantener la lógica de conversión encapsulada y comprobable allí. Sin embargo, no es escalable ni flexible cuando tienes muchos números o combinaciones grandes de propiedades de origen y destino. En ese caso, tienes un par de opciones:

  • Si se usa {x:Bind}, entonces puede enlazar directamente a una función para hacer esa conversión
  • O bien, puedes especificar un convertidor de valores, que es un objeto que se ha diseñado para realizar la conversión.

Convertidores de valores

Este es un convertidor de valores adecuado para un enlace único o unidireccional, que convierte un valor DateTime en un valor string que contiene el mes. La clase implementa IValueConverter.

public class DateToStringConverter : IValueConverter
{
    // Define the Convert method to convert a DateTime value to 
    // a month string.
    public object Convert(object value, Type targetType, 
        object parameter, string language)
    {
        // value is the data from the source object.
        DateTime thisDate = (DateTime)value;
        int monthNum = thisDate.Month;
        string month;
        switch (monthNum)
        {
            case 1:
                month = "January";
                break;
            case 2:
                month = "February";
                break;
            default:
                month = "Month not found";
                break;
        }
        // Return the value to pass to the target.
        return month;
    }

    // ConvertBack is not implemented for a OneWay binding.
    public object ConvertBack(object value, Type targetType, 
        object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

Y esta es la forma de consumir ese convertidor de valores en el marcado del objeto de enlace.

<UserControl.Resources>
  <local:DateToStringConverter x:Key="Converter1"/>
</UserControl.Resources>
...
<TextBlock Grid.Column="0" 
  Text="{x:Bind ViewModel.Month, Converter={StaticResource Converter1}}"/>
<TextBlock Grid.Column="0" 
  Text="{Binding Month, Converter={StaticResource Converter1}}"/>

El motor de enlace llama a los métodos Convert y ConvertBack si se ha definido el parámetro Converter para el enlace. Cuando se pasan los datos del origen, el motor de enlace llama a Convert y pasa los datos devueltos al destino. Cuando se pasan los datos del destino (para un enlace bidireccional), el motor de enlace llama a ConvertBack y pasa los datos devueltos al origen.

El convertidor también tiene parámetros opcionales: ConverterLanguage, que permite especificar el lenguaje que se usará en la conversión, y ConverterParameter, que permite pasar un parámetro para la lógica de conversión. Para obtener un ejemplo en el que se use un parámetro de convertidor, consulta IValueConverter.

Nota

Si hay un error en la conversión, no inicies una excepción. En lugar de eso, devuelve DependencyProperty.UnsetValue, que detendrá la transferencia de datos.

Para mostrar un valor predeterminado para usar cuando no se pueda resolver el origen del enlace, establece la propiedad FallbackValue en el objeto de enlace en el marcado. Esto sirve para controlar los errores de formato y de conversión. También resulta útil para crear un enlace con propiedades de origen que quizás no existen en todos los objetos de una colección enlazada de tipos heterogéneos.

Si enlazas un control de texto con un valor que no es una cadena, el motor de enlace de datos lo convertirá en una. Si el valor es un tipo de referencia, dicho motor recuperará el valor de cadena llamando a ICustomPropertyProvider.GetStringRepresentation o a IStringable.ToString, si están disponibles, o bien llamando a Object.ToString, si no lo están. Pero ten en cuenta que el motor de enlace de datos pasará por alto las implementaciones de ToString que oculten la implementación de la clase base. Las implementaciones de la subclase deben invalidar, en cambio, el método ToString de la clase base. De forma similar, en los lenguajes nativos, todos los objetos administrados parecen implementar ICustomPropertyProvider e IStringable. Pero todas las llamadas a GetStringRepresentation y a IStringable.ToString se enrutan a Object.ToString o a una invalidación de ese método y nunca a una implementación de ToString que oculta la implementación de la clase base.

Nota

El Kit de herramientas de la comunidad de Windows proporciona un BoolToVisibilityConverter. El convertidor asigna true al valor de la enumeración Visible y false a Collapsed, para que pueda enlazar una propiedad Visibility a un valor booleano sin necesidad de crear un convertidor. Para usar el convertidor, el proyecto debe agregar el paquete NuGet CommunityToolkit.WinUI.Converters.

Enlace de función en {x: Bind}

{x:Bind} permite que el paso final de una ruta de acceso de enlace sea una función. Esto puede usarse para realizar conversiones y para realizar enlaces que dependen de más de una propiedad. Consulta Funciones de x:Bind

Enlace de elemento a elemento

Se puede enlazar la propiedad de un elemento XAML a la propiedad de otro elemento XAML. En este ejemplo se muestra cómo queda en marcado.

<TextBox x:Name="myTextBox" />
<TextBlock Text="{x:Bind myTextBox.Text, Mode=OneWay}" />

Diccionarios de recursos con {x:Bind}

La extensión de marcado {x:Bind} depende de la generación de códigos; por lo tanto, necesita un archivo de código subyacente que contenga un constructor que llame a InitializeComponent (para inicializar el código generado). Vuelves a usar el diccionario de recursos creando una instancia de su tipo (por lo que se llama a InitializeComponent) en lugar de hacer referencia a su nombre de archivo. A continuación, un ejemplo de qué hacer si dispones de un diccionario de recursos existente y quieres usar {x:Bind} en este.

<!-- TemplatesResourceDictionary.xaml -->
<ResourceDictionary
    x:Class="ExampleNamespace.TemplatesResourceDictionary"
    .....
    xmlns:examplenamespace="using:ExampleNamespace">
    
    <DataTemplate x:Key="EmployeeTemplate" x:DataType="examplenamespace:IEmployee">
        <Grid>
            <TextBlock Text="{x:Bind Name}"/>
        </Grid>
    </DataTemplate>
</ResourceDictionary>
// TemplatesResourceDictionary.xaml.cs
using Microsoft.UI.Xaml.Data;
 
namespace ExampleNamespace
{
    public partial class TemplatesResourceDictionary
    {
        public TemplatesResourceDictionary()
        {
            InitializeComponent();
        }
    }
}
<!-- MainWindow.xaml -->
<Window x:Class="ExampleNamespace.MainWindow"
    ....
    xmlns:examplenamespace="using:ExampleNamespace">

    <Window.Resources>
        <ResourceDictionary>
            .... 
            <ResourceDictionary.MergedDictionaries>
                <examplenamespace:TemplatesResourceDictionary/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
</Window>

Enlace de eventos e ICommand

{x:Bind} admite una característica llamada enlace de eventos. Con esta característica, puede especificar el controlador para un evento con un enlace, que es una opción adicional sobre el control de eventos con un método en el archivo de código subyacente. Supongamos que tiene un controlador de eventos ListViewDoubleTapped en la clase MainWindow.

public sealed partial class MainWindow : Window
{
    ...
    public void ListViewDoubleTapped()
    {
        // Handle double-tapped logic
    }
}

A continuación, puede enlazar un evento DoubleTapped de ListView a un método en MainWindow de esta manera.

<ListView DoubleTapped="{x:Bind ListViewDoubleTapped}" />

Los métodos sobrecargados no puede usarse para controlar un evento con esta técnica. Además, si el método que controla el evento tiene parámetros, todos deben ser asignables a partir de los tipos de todos los parámetros del evento, respectivamente. En este caso, ListViewDoubleTapped no está sobrecargado y no tiene parámetros (pero podría ser válido incluso si hubiera usado dos parámetros object).

La técnica de enlace de eventos es similar a implementar y consumir comandos (un comando es una propiedad que devuelve un objeto que implementa la interfaz ICommand). Ambos {x: enlace} y {Binding} funcionan con comandos. Debido a que no tiene que implementar el patrón de comando varias veces, puede usar la clase DelegateCommand auxiliar que encontrará en la muestra QuizGame de UWP (en la carpeta "Común").

Enlace a una colección de carpetas o archivos

Puede usar las API del espacio de nombres Windows.Storage para recuperar datos de archivos y carpetas en las aplicaciones de SDK de Windows App empaquetadas. No obstante, los distintos métodos GetFilesAsync, GetFoldersAsync y GetItemsAsync no devuelven valores adecuados para enlaces a controles de lista. En cambio, debes enlazar a los valores devueltos de los métodos GetVirtualizedFilesVector, GetVirtualizedFoldersVector y GetVirtualizedItemsVector de la clase FileInformationFactory. En el siguiente ejemplo de código de la muestra UWP de StorageDataSource y GetVirtualizedFilesVector encontrará el patrón de uso más común. No olvides que declarar el manifiesto picturesLibrary del paquete de funcionalidad de la aplicación y confirmar que hay imágenes en la carpeta de la biblioteca de imágenes.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    var library = Windows.Storage.KnownFolders.PicturesLibrary;
    var queryOptions = new Windows.Storage.Search.QueryOptions();
    queryOptions.FolderDepth = Windows.Storage.Search.FolderDepth.Deep;
    queryOptions.IndexerOption = Windows.Storage.Search.IndexerOption.UseIndexerWhenAvailable;

    var fileQuery = library.CreateFileQueryWithOptions(queryOptions);

    var fif = new Windows.Storage.BulkAccess.FileInformationFactory(
        fileQuery,
        Windows.Storage.FileProperties.ThumbnailMode.PicturesView,
        190,
        Windows.Storage.FileProperties.ThumbnailOptions.UseCurrentScale,
        false
        );

    var dataSource = fif.GetVirtualizedFilesVector();
    this.PicturesListView.ItemsSource = dataSource;
}

Normalmente usarás este enfoque para crear una vista de solo lectura de la información de archivo y carpeta. Puedes crear enlaces bidireccionales a las propiedades de archivo y carpeta; por ejemplo, para permitir que los usuarios califiquen una canción en una vista de música. No obstante, ningún cambio persiste hasta que no llames al método SavePropertiesAsync apropiado (por ejemplo, MusicProperties.SavePropertiesAsync). Debes confirmar cambios cuando el elemento pierde el foco, porque esto desencadena el restablecimiento de la selección.

Tenga en cuenta que el enlace bidireccional con esta técnica solo funciona con ubicaciones indexadas, como Music. Puedes determinar si una ubicación es indizada llamando al método FolderInformation.GetIndexedStateAsync.

Ten en cuenta también que un vector virtualizado puede devolver null para algunos elementos antes de que rellene su valor. Por ejemplo, debes comprobar null antes de usar el valor SelectedItem de un control de lista enlazado a un vector virtualizado o, en su lugar, usar SelectedIndex.

Enlace de datos agrupados por una clave

Si tomas una colección plana de elementos (libros, por ejemplo, representados por una clase BookSku) y agrupas los elementos mediante una propiedad común, como una clave (la propiedad BookSku.AuthorName, por ejemplo), el resultado son datos agrupados. Al agrupar los datos, ya no es una colección plana. Los datos agrupados son una colección de objetos de grupo, donde cada objeto de grupo dispone de:

  • una clave y
  • una colección de elementos cuya propiedad coincide con esa clave.

Si retomamos el ejemplo de los libros, la agrupación de los libros por nombre de autor da como resultado una colección de grupos de nombres de autor donde cada grupo tiene:

  • una clave, que es un nombre de autor, y
  • una colección de los objetos BookSku cuya propiedad AuthorName coincide con la clave del grupo.

En general, para mostrar una colección, enlazas el ItemsSource de un control de elementos (como ListView o GridView) directamente a una propiedad que devuelve una colección. Si es una colección de elementos plana no necesitas hacer nada especial. Pero si es una colección de objetos de grupo (como al enlazar a datos agrupados), a continuación, necesitas los servicios de un objeto intermediario llamado CollectionViewSource, que se encuentra entre el control de elementos y el origen del enlace. Enlaza el CollectionViewSource a la propiedad que devuelve datos agrupados y enlaza el control de elementos a CollectionViewSource. Un valor agregado adicional de un CollectionViewSource es que realiza el seguimiento del elemento actual, para poder mantener más de un control de elementos sincronizados al enlazarlos todos al mismo CollectionViewSource. Puedes acceder también al elemento actual mediante programación a través de la propiedad ICollectionView.CurrentItem del objeto devuelto por la propiedad CollectionViewSource.View.

Para activar la función de agrupación de un CollectionViewSource, establece IsSourceGrouped en true. Si también debes establecer la propiedad ItemsPath depende de cómo crees exactamente los objetos de grupo. Existen dos formas para crear un objeto de grupo: el patrón de "es un grupo" y el "tiene un grupo". En el modelo "es un grupo", el objeto de grupo se deriva de un tipo de colección (por ejemplo, List<T>), de modo que el objeto de grupo en realidad es el grupo de elementos. Con este modelo, es necesario establecer ItemsPath. En el modelo "tiene un grupo", el objeto de grupo tiene una propiedad o más de una, de un tipo de colección (como List<T>), por lo que el grupo "tiene un" grupo de elementos en forma de una propiedad (o varios grupos de elementos en forma de varias propiedades). Con este modelo, debes establecer ItemsPath en el nombre de la propiedad que contiene el grupo de elementos.

El siguiente ejemplo muestra el modelo de "tiene un grupo". La clase de ventana tiene una propiedad denominada DataContext, que devuelve una instancia de nuestro modelo de vista. El CollectionViewSource enlaza a la propiedad Authors del modelo de vista (Authors es la colección de objetos de grupo) y también especifica que es la propiedad Author.BookSkus que contiene los elementos agrupados. Por último, la GridView está enlazada al CollectionViewSource, y su estilo de grupo se define para que pueda representar los elementos en grupos.

<Window.Resources>
    <CollectionViewSource
    x:Name="AuthorHasACollectionOfBookSku"
    Source="{x:Bind ViewModel.Authors}"
    IsSourceGrouped="true"
    ItemsPath="BookSkus"/>
</Window.Resources>
...
<GridView
ItemsSource="{x:Bind AuthorHasACollectionOfBookSku}" ...>
    <GridView.GroupStyle>
        <GroupStyle
            HeaderTemplate="{StaticResource AuthorGroupHeaderTemplateWide}" ... />
    </GridView.GroupStyle>
</GridView>

Para implementar el patrón de "es un grupo" en uno de dos maneras. Es una forma crear tu propia clase de grupo. Derive la clase de List<T> (donde T es el tipo de los elementos). Por ejemplo, public class Author : List<BookSku>. La segunda manera es usar una expresión LINK para crear dinámicamente objetos de grupo (y una clase de grupo) desde valores de propiedad similares de los elementos BookSku. Este enfoque, mantener solo una lista de elementos y agrupar sobre la marcha, es típico de una aplicación que accede a los datos de un servicio de nube. Obtienes la flexibilidad de agrupar libros según el autor o el género (por ejemplo), sin necesidad de disponer de clases de grupo especiales, como Author y Genre.

El siguiente ejemplo muestra el uso del patrón "es un grupo" LINQ. Esta vez agrupamos libros por género, mostrados con el nombre de género en los encabezados de grupo. Esto se indica en la ruta de acceso de la propiedad "Key" en referencia al valor Key del grupo.

using System.Linq;
...
private IOrderedEnumerable<IGrouping<string, BookSku>> genres;

public IOrderedEnumerable<IGrouping<string, BookSku>> Genres
{
    get
    {
        if (genres == null)
        {
            genres = from book in bookSkus
                     group book by book.genre into grp
                     orderby grp.Key
                     select grp;
        }
        return genres;
    }
}

Recuerda que, al usar {x:Bind} con plantillas de datos que necesitamos para indicar el tipo enlazado a estableciendo un valor x:DataType. Si el tipo es genérico y no podemos declaramos que en el marcado que necesitamos usar {Binding} en su lugar en la plantilla de encabezado de estilo de grupo.

    <Grid.Resources>
        <CollectionViewSource x:Name="GenreIsACollectionOfBookSku"
        Source="{x:Bind Genres}"
        IsSourceGrouped="true"/>
    </Grid.Resources>
    <GridView ItemsSource="{x:Bind GenreIsACollectionOfBookSku}">
        <GridView.ItemTemplate x:DataType="local:BookTemplate">
            <DataTemplate>
                <TextBlock Text="{x:Bind Title}"/>
            </DataTemplate>
        </GridView.ItemTemplate>
        <GridView.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Key}"/>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </GridView.GroupStyle>
    </GridView>

Un control SemanticZoom es una manera ideal para que los usuarios vean y naveguen por los datos agrupados. La aplicación de muestra Bookstore2 de UWP ilustra cómo usar SemanticZoom. En esa aplicación, puedes ver una lista de libros agrupados por autor (la vista ampliada) o alejar para ver una lista de accesos directos de autores (la vista alejada). La lista de accesos directos ofrece una navegación mucho más rápida que un desplazamiento por la lista de libros. Las vistas acercada y alejada en realidad son controles ListView o GridView enlazados a la misma clase CollectionViewSource.

An illustration of a SemanticZoom

Cuando se enlaza a datos jerárquicos (como subcategorías dentro de las categorías), puedes mostrar los niveles jerárquicos en la interfaz de usuario con una serie de controles de elementos. Una selección en un control de elementos determina el contenido de los controles de elementos posteriores. Puedes mantener las listas sincronizadas, al enlazar cada una de ellas a su propio CollectionViewSource y al enlazar las instancias de CollectionViewSource juntas en una cadena. Esto se denomina una vista maestro/detalles (o la lista y detalles). Para más información, consulta el tema Cómo enlazar a datos jerárquicos y crear una vista maestro y detalles.

Diagnóstico y depuración de problemas de enlace de datos

El marcado de enlace contiene los nombres de propiedades (y para C#, a veces campos y métodos). Por lo tanto, cuando cambias el nombre de una propiedad, también tendrás que cambiar cualquier enlace que haga referencia a él. Olvidar hacer que conduce a un ejemplo típico de un error de enlace de datos y la aplicación no compile o no ejecutarse correctamente.

Los objetos de enlace creados por {x: enlace} y {Binding} son prácticamente funcionalmente equivalentes. Pero {x:Bind} tiene información de tipo de origen de enlace, y genera código fuente en tiempo de compilación. Con {x:Bind}, se obtiene el mismo tipo de detección de problemas que se obtiene con el resto del código. Que incluye la validación de tiempo de compilación de las expresiones de enlace y depuración estableciendo puntos de interrupción en el código fuente que se generan como la clase parcial de la página. Estas clases pueden encontrarse en los archivos en tu obj carpetas con nombres como (para C#) <view name>.g.cs). Si tiene un problema con un enlace y activar interrumpir en las excepciones no controladas en el depurador de Microsoft Visual Studio. El depurador interrumpirá la ejecución en ese punto y, a continuación, se pueden depurar lo que ha funcionado. El código generado por {x:Bind} sigue el mismo patrón para cada parte del gráfico de nodos de origen de enlace, y puede usar la información de la ventana Pila de llamadas para ayudar a determinar la secuencia de llamadas que condujeron al problema.

{Binding} no tiene información de tipo de origen de enlace. Cuando se ejecuta la aplicación con el depurador adjunto, los errores de enlace aparecen en las ventanas Resultado y Errores de enlace XAML en Visual Studio. Para obtener más información sobre los errores de enlace de depuración en Visual Studio, consulte Diagnósticos de enlace de datos XAML.

Crear enlaces en el código

Nota

Esta sección solo se aplica a {Binding}, porque no puedes crear enlaces {x:Bind} en el código. Sin embargo, algunas de las ventajas de {x:Bind} pueden conseguirse con DependencyObject.RegisterPropertyChangedCallback, que te permite registrar notificaciones de cambio en cualquier propiedad de dependencia.

También puedes conectar elementos de la interfaz de usuario a datos usando código de procedimientos en lugar de XAML. Para ello, crea un nuevo objeto Binding, establece las propiedades correspondientes y luego llama a FrameworkElement.SetBinding o BindingOperations.SetBinding. Crear enlaces mediante programación te resultará útil si quieres elegir los valores de propiedad de los enlaces en tiempo de ejecución o compartir un único enlace entre varios controles. Pero ten en cuenta que no puedes cambiar los valores de propiedad de los enlaces después de llamar a SetBinding.

En el siguiente ejemplo se muestra cómo implementar un enlace en el código.

<TextBox x:Name="MyTextBox" Text="Text"/>
// Create an instance of the MyColors class 
// that implements INotifyPropertyChanged.
var textcolor = new MyColors();

// Brush1 is set to be a SolidColorBrush with the value Red.
textcolor.Brush1 = new SolidColorBrush(Colors.Red);

// Set the DataContext of the TextBox MyTextBox.
MyTextBox.DataContext = textcolor;

// Create the binding and associate it with the text box.
var binding = new Binding { Path = new PropertyPath("Brush1") };
MyTextBox.SetBinding(TextBox.ForegroundProperty, binding);

{x: enlace} y {Binding} comparación de características

Característica {x:Bind} frente a {Binding} Notas
La ruta de acceso es la propiedad predeterminada {x:Bind a.b.c}
-
{Binding a.b.c}
Propiedades de acceso {x:Bind Path=a.b.c}
-
{Binding Path=a.b.c}
En x:Bind, Path está en la ventana de forma predeterminada, no DataContext.
indizador {x:Bind Groups[2].Title}
-
{Binding Groups[2].Title}
Enlaza con el elemento especificado en la colección. Se admiten solamente en números enteros índices.
Propiedades adjuntas {x:Bind Button22.(Grid.Row)}
-
{Binding Button22.(Grid.Row)}
Las propiedades adjuntas se especifican mediante paréntesis. Si la propiedad no está declarada en un espacio de nombres XAML, agrega un prefijo con un espacio de nombres XML, que debe estar asignado a un espacio de nombres de código al principio del documento.
Conversión {x:Bind groups[0].(data:SampleDataGroup.Title)}
-
No es necesario para {Binding}.
Las conversiones de tipos se especifican mediante paréntesis. Si la propiedad no está declarada en un espacio de nombres XAML, agrega un prefijo con un espacio de nombres XML, que debe estar asignado a un espacio de nombres de código al principio del documento.
Converter {x:Bind IsShown, Converter={StaticResource BoolToVisibility}}
-
{Binding IsShown, Converter={StaticResource BoolToVisibility}}
Los convertidores deben declararse en la raíz de Window/Control/ResourceDictionary o en App.xaml.
ConvertidorParameter, ConvertidorLanguage {x:Bind IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr}
-
{Binding IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr}
Los convertidores deben declararse en la raíz de Window/Control/ResourceDictionary o en App.xaml.
TargetNullValue {x:Bind Name, TargetNullValue=0}
-
{Binding Name, TargetNullValue=0}
Se usa cuando la hoja de la expresión de enlace es null. Usar comillas simples para un valor de cadena.
FallbackValue {x:Bind Name, FallbackValue='empty'}
-
{Binding Name, FallbackValue='empty'}
Se usa cuando cualquier parte de la ruta de acceso para el enlace (excepto la hoja) es null.
ElementName {x:Bind slider1.Value}
-
{Binding Value, ElementName=slider1}
Con {x:Bind}, enlaza a un campo; Path está en la ventana de forma predeterminada, por lo que cualquier elemento con nombre se puede acceder mediante el campo.
RelativeSource: Self (Automático) <Rectangle x:Name="rect1" Width="200" Height="{x:Bind rect1.Width}" ... />
-
<Rectangle Width="200" Height="{Binding Width, RelativeSource={RelativeSource Self}}" ... />
Con {x:Bind}, denomine el elemento y use el nombre en Path.
RelativeSource: TemplatedParent No es necesario para {x:Bind}
-
{Binding <path>, RelativeSource={RelativeSource TemplatedParent}}
Con {x:Bind}, TargetType en ControlTemplate indica enlace con la plantilla principal. Para {Binding}, se puede usar el enlace de plantilla normal para plantillas de control en la mayoría de los casos. Pero use TemplatedParent donde necesite usar un convertidor o un enlace bidireccional.
Source No es necesario para {x:Bind}
-
<ListView ItemsSource="{Binding Orders, Source={StaticResource MyData}}"/>
Para {x:Bind}, puede usar directamente el elemento con nombre y una propiedad o ruta de acceso estática.
Mode {x:Bind Name, Mode=OneWay}
-
{Binding Name, Mode=TwoWay}
Mode puede ser OneTime, OneWay o TwoWay. {x:Bind} por defecto es OneTime; {Binding} por defecto es OneWay.
UpdateSourceTrigger {x:Bind Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
-
{Binding UpdateSourceTrigger=PropertyChanged}
UpdateSourceTrigger puede ser Default, LostFocus o PropertyChanged. {x:Bind} no admite UpdateSourceTrigger=Explicit. {x:Bind} usa el comportamiento PropertyChanged para todos los casos excepto TextBox.Text, donde usa el comportamiento LostFocus.

Vea también