Aspectos básicos del enlace de datos

Browse sample.Examina la muestra

Los enlaces de datos de la interfaz de usuario de aplicaciones multiplataforma de .NET (.NET MAUI) permiten vincular las propiedades de dos objetos para que un cambio en uno provoque un cambio en el otro. Se trata de una herramienta muy valiosa y, aunque los enlaces de datos se pueden definir completamente con código, XAML proporciona métodos abreviados y comodidad.

Enlaces de datos

Los enlaces de datos conectan las propiedades de dos objetos, denominadas origen y destino. Con código, se necesitan dos pasos:

  1. La propiedad BindingContext del objeto de destino debe establecerse en el objeto de origen,
  2. El método SetBinding ( que a menudo se usa junto con la clase Binding) se debe llamar en el objeto de destino para enlazar una propiedad de ese objeto con una propiedad del objeto de origen.

La propiedad de destino debe ser una propiedad enlazable, lo que significa que el objeto de destino debe derivar de BindableObject. Una propiedad de Label, como Text, está asociada a la propiedad enlazable TextProperty.

En XAML, también debes realizar los mismos dos pasos necesarios en el código, excepto que la extensión de marcado Binding ocupa el lugar de la llamada SetBinding y la clase Binding. Sin embargo, cuando defines enlaces de datos en XAML, hay varias formas de establecer el BindingContext del objeto de destino. A veces se establece desde el archivo de código subyacente, a veces con una extensión de marcado StaticResource o x:Static, y a veces como contenido de etiquetas de elemento de propiedad BindingContext.

Enlaces de vista a vista

Puedes definir enlaces de datos para vincular propiedades de dos vistas en la misma página. En este caso, estableces el BindingContext del objeto de destino con la extensión de marcado x:Reference.

El siguiente ejemplo contiene una vista Slider y dos vistas Label, una de ellas rotada por el valor Slider y otra que muestra el valor Slider:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SliderBindingsPage"
             Title="Slider Bindings Page">
    <StackLayout>
        <Label Text="ROTATION"
               BindingContext="{x:Reference slider}"
               Rotation="{Binding Path=Value}"
               FontAttributes="Bold"
               FontSize="18"
               HorizontalOptions="Center"
               VerticalOptions="Center" />
        <Slider x:Name="slider"
                Maximum="360"
                VerticalOptions="Center" />
        <Label BindingContext="{x:Reference slider}"
               Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
               FontAttributes="Bold"
               FontSize="18"
               HorizontalOptions="Center"
               VerticalOptions="Center" />
    </StackLayout>
</ContentPage>

Slider contiene un atributo x:Name al que hacen referencia las dos vistas Label con la extensión de marcado x:Reference. La extensión de enlace x:Reference define una propiedad denominada Name para establecer el nombre del elemento referenciado, en este caso slider. Sin embargo, la clase ReferenceExtension que define la extensión de marcado x:Reference también define un atributo ContentProperty para Name, lo que significa que no se requiere de forma explícita.

La propia extensión de marcado Binding puede tener varias propiedades, al igual que la clase BindingBase y Binding. El ContentProperty para Binding es Path, pero la parte "Path=" de la extensión de marcado puede omitirse si la ruta de acceso es el primer elemento de la extensión de marcado Binding.

La segunda extensión de marcado Binding establece la propiedad StringFormat. En .NET MAUI, los enlaces no realizan conversiones de tipos implícitas y si necesitas mostrar un objeto que no es una cadena como una cadena debes proporcionar un convertidor de tipo o usar StringFormat.

Importante

Las cadenas de formato deben colocarse entre comillas simples.

Modo de enlace

Una sola vista puede tener enlaces de datos en varias de sus propiedades. Sin embargo, cada vista solo puede tener un BindingContext, por lo que varios enlaces de datos en esa vista deben hacer referencia a todas las propiedades del mismo objeto.

La solución a este y otros problemas implica la propiedad Mode, que se establece en un miembro de la enumeración BindingMode:

  • Default
  • OneWay: los valores se transfieren del origen al destino.
  • OneWayToSource : los valores se transfieren del destino al origen.
  • TwoWay : los valores se transfieren de ambas maneras entre el origen y el destino.
  • OneTime: los datos van del origen al destino, pero solo cuando cambia el BindingContext

El siguiente ejemplo muestra un uso común de los modos de enlace OneWayToSource y TwoWay:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SliderTransformsPage"
             Padding="5"
             Title="Slider Transforms Page">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <!-- Scaled and rotated Label -->
        <Label x:Name="label"
               Text="TEXT"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <!-- Slider and identifying Label for Scale -->
        <Slider x:Name="scaleSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="1" Grid.Column="0"
                Maximum="10"
                Value="{Binding Scale, Mode=TwoWay}" />
        <Label BindingContext="{x:Reference scaleSlider}"
               Text="{Binding Value, StringFormat='Scale = {0:F1}'}"
               Grid.Row="1" Grid.Column="1"
               VerticalTextAlignment="Center" />

        <!-- Slider and identifying Label for Rotation -->
        <Slider x:Name="rotationSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="2" Grid.Column="0"
                Maximum="360"
                Value="{Binding Rotation, Mode=OneWayToSource}" />
        <Label BindingContext="{x:Reference rotationSlider}"
               Text="{Binding Value, StringFormat='Rotation = {0:F0}'}"
               Grid.Row="2" Grid.Column="1"
               VerticalTextAlignment="Center" />

        <!-- Slider and identifying Label for RotationX -->
        <Slider x:Name="rotationXSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="3" Grid.Column="0"
                Maximum="360"
                Value="{Binding RotationX, Mode=OneWayToSource}" />
        <Label BindingContext="{x:Reference rotationXSlider}"
               Text="{Binding Value, StringFormat='RotationX = {0:F0}'}"
               Grid.Row="3" Grid.Column="1"
               VerticalTextAlignment="Center" />

        <!-- Slider and identifying Label for RotationY -->
        <Slider x:Name="rotationYSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="4" Grid.Column="0"
                Maximum="360"
                Value="{Binding RotationY, Mode=OneWayToSource}" />
        <Label BindingContext="{x:Reference rotationYSlider}"
               Text="{Binding Value, StringFormat='RotationY = {0:F0}'}"
               Grid.Row="4" Grid.Column="1"
               VerticalTextAlignment="Center" />
    </Grid>
</ContentPage>

En este ejemplo, cuatro vistas Slider están diseñadas para controlar las propiedades Scale, Rotate, RotateX y RotateY de Label. Al principio, parece que estas cuatro propiedades de Label deben ser destinos del enlace de datos porque cada una se establece mediante Slider. Sin embargo, BindingContext de Label solo puede ser un objeto y hay cuatro controles deslizantes diferentes. Por ese motivo, el objeto BindingContext de cada uno de los cuatro controles deslizantes se establece en Label y los enlaces se establecen en las propiedades Value de los controles deslizantes. Mediante los modos OneWayToSource y TwoWay, estas propiedades Value pueden establecer las propiedades de origen, que son las propiedades Scale, Rotate, RotateX y RotateY de Label.

Los enlaces de tres de las vistas Slider son OneWayToSource, lo que significa que el valor de Slider produce un cambio en la propiedad de su BindingContext que es el elemento Label denominado label. Estas tres vistas Slider producen cambios en las propiedades Rotate, RotateX y RotateY de Label:

Reverse bindings.

No obstante, el enlace predeterminado de la propiedad Scale es TwoWay. Esto se debe a que la propiedad Scale tiene un valor predeterminado de 1 y el uso de un enlace TwoWay hace que el valor inicial de Slider se establezca en 1 en lugar de 0. Si ese enlace fuera OneWayToSource, la propiedad Scale se establecería inicialmente en 0 a partir del valor predeterminado de Slider. Label no sería visible

Nota:

La clase VisualElement también tiene propiedades ScaleX y ScaleY, que escalan el objeto VisualElement en el eje X y el eje Y respectivamente.

Enlaces y colecciones

ListView define una propiedad ItemsSource de tipo IEnumerable, y muestra los elementos de esa colección. Estos elementos pueden ser objetos de cualquier tipo. De forma predeterminada, ListView usa el método ToString de cada elemento para mostrar ese elemento. A veces es esto lo que quieres, pero, en muchos casos, ToString devuelve solo el nombre de clase completo del objeto.

Sin embargo, los elementos de la colección ListView se pueden mostrar de la forma que quieras mediante el uso de una plantilla, lo que implica una clase que deriva de Cell. La plantilla se clona para cada elemento de ListView, y los enlaces de datos que se han establecido en la plantilla se transfieren a los clones individuales. Se pueden crear celdas personalizadas para los elementos mediante la clase ViewCell.

ListView puede mostrar una lista de todos los colores con nombre disponibles en .NET MAUI, con la ayuda de la clase NamedColor:

using System.Reflection;
using System.Text;

namespace XamlSamples
{
    public class NamedColor
    {
        public string Name { get; private set; }
        public string FriendlyName { get; private set; }
        public Color Color { get; private set; }

        // Expose the Color fields as properties
        public float Red => Color.Red;
        public float Green => Color.Green;
        public float Blue => Color.Blue;

        public static IEnumerable<NamedColor> All { get; private set; }

        static NamedColor()
        {
            List<NamedColor> all = new List<NamedColor>();
            StringBuilder stringBuilder = new StringBuilder();

            // Loop through the public static fields of the Color structure.
            foreach (FieldInfo fieldInfo in typeof(Colors).GetRuntimeFields())
            {
                if (fieldInfo.IsPublic &&
                    fieldInfo.IsStatic &&
                    fieldInfo.FieldType == typeof(Color))
                {
                    // Convert the name to a friendly name.
                    string name = fieldInfo.Name;
                    stringBuilder.Clear();
                    int index = 0;

                    foreach (char ch in name)
                    {
                        if (index != 0 && Char.IsUpper(ch))
                        {
                            stringBuilder.Append(' ');
                        }
                        stringBuilder.Append(ch);
                        index++;
                    }

                    // Instantiate a NamedColor object.
                    NamedColor namedColor = new NamedColor
                    {
                        Name = name,
                        FriendlyName = stringBuilder.ToString(),
                        Color = (Color)fieldInfo.GetValue(null)
                    };

                    // Add it to the collection.
                    all.Add(namedColor);
                }
            }
            all.TrimExcess();
            All = all;
        }
    }
}

Cada objeto NamedColor tiene propiedades Name y FriendlyName de tipo string, una propiedad Color de tipo Color, y propiedades Red, Green y Blue. Además, el constructor estático NamedColor crea una colección IEnumerable<NamedColor> que contiene objetos NamedColor correspondientes a los campos de tipo Color de la clase Colors, y la asigna a su propiedad pública estática All.

El establecimiento de la propiedad estática NamedColor.All en ItemsSource de ListView se puede lograr mediante la extensión de marcado x:Static:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.ListViewDemoPage"
             Title="ListView Demo Page">
    <ListView ItemsSource="{x:Static local:NamedColor.All}" />
</ContentPage>

El resultado establece que los elementos son de tipo XamlSamples.NamedColor:

Binding to a collection.

Para definir una plantilla para los elementos, ItemTemplate debe establecerse en DataTemplate que haga referencia a ViewCell. ViewCell debe definir el diseño de una o varias vistas para mostrar cada elemento:

<ListView ItemsSource="{x:Static local:NamedColor.All}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <Label Text="{Binding FriendlyName}" />
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Nota:

El origen de enlace de las celdas y sus elementos secundarios es la colección ListView.ItemsSource.

En este ejemplo, el elemento Label se establece en la propiedad View de ViewCell. Las etiquetas ViewCell.View no son necesarias porque la propiedad View es la propiedad de contenido de ViewCell. Este XAML muestra la propiedad FriendlyName de cada objeto NamedColor:

Binding to a collection with a DataTemplate.

La plantilla de elemento se puede expandir para mostrar más información y el color real:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples"
             x:Class="XamlSamples.ListViewDemoPage"
             Title="ListView Demo Page">
    <ContentPage.Resources>
        <x:Double x:Key="boxSize">50</x:Double>
        <x:Int32 x:Key="rowHeight">60</x:Int32>
        <local:FloatToIntConverter x:Key="intConverter" />
    </ContentPage.Resources>

    <ListView ItemsSource="{x:Static local:NamedColor.All}"
              RowHeight="{StaticResource rowHeight}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <StackLayout Padding="5, 5, 0, 5"
                                 Orientation="Horizontal"
                                 Spacing="15">
                        <BoxView WidthRequest="{StaticResource boxSize}"
                                 HeightRequest="{StaticResource boxSize}"
                                 Color="{Binding Color}" />
                        <StackLayout Padding="5, 0, 0, 0"
                                     VerticalOptions="Center">
                            <Label Text="{Binding FriendlyName}"
                                   FontAttributes="Bold"
                                   FontSize="14" />
                            <StackLayout Orientation="Horizontal"
                                         Spacing="0">
                                <Label Text="{Binding Red,
                                                      Converter={StaticResource intConverter},
                                                      ConverterParameter=255,
                                                      StringFormat='R={0:X2}'}" />                                
                                <Label Text="{Binding Green,
                                                      Converter={StaticResource intConverter},
                                                      ConverterParameter=255,
                                                      StringFormat=', G={0:X2}'}" />                                
                                <Label Text="{Binding Blue,
                                                      Converter={StaticResource intConverter},
                                                      ConverterParameter=255,
                                                      StringFormat=', B={0:X2}'}" />
                            </StackLayout>
                        </StackLayout>
                    </StackLayout>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

Enlace de convertidores de valores

En el ejemplo de XAML anterior se muestran las propiedades individuales Red, Green y Blue de cada NamedColor. Estas propiedades son de tipo float y van de 0 a 1. Si quieres mostrar los valores hexadecimales, no puedes usar StringFormat solo con una especificación de formato "X2". Esto solo funciona para enteros y además, los valores float deben multiplicarse por 255.

Este problema se puede resolver con un convertidor de valores, también denominado convertidor de enlaces. Se trata de una clase que implementa la interfaz IValueConverter, lo que significa que tiene dos métodos denominados Convert y ConvertBack. El método Convert se llama cuando se transfiere un valor del origen al destino. El método ConvertBack se llama para las transferencias de destino a origen en los enlaces OneWayToSource o TwoWay:

using System.Globalization;

namespace XamlSamples
{
    public class FloatToIntConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            float multiplier;

            if (!float.TryParse(parameter as string, out multiplier))
                multiplier = 1;

            return (int)Math.Round(multiplier * (float)value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            float divider;

            if (!float.TryParse(parameter as string, out divider))
                divider = 1;

            return ((float)(int)value) / divider;
        }
    }
}

Nota:

El método ConvertBack no desempeña ningún papel en este ejemplo porque los enlaces son solo unidreccionales desde el origen al destino.

Un enlace hace referencia a un convertidor de enlaces con la propiedad Converter. Un convertidor de enlaces también puede aceptar un parámetro especificado con la propiedad ConverterParameter. Para obtener cierta versatilidad, se especifica así el multiplicador. El convertidor de enlaces comprueba si el parámetro del convertidor tiene un valor float válido.

Se creará una instancia del convertidor en el diccionario de recursos de la página para que pueda compartirse entre varios enlaces:

<local:FloatToIntConverter x:Key="intConverter" />

Tres enlaces de datos hacen referencia a esta instancia única:

<Label Text="{Binding Red,
                      Converter={StaticResource intConverter},
                      ConverterParameter=255,
                      StringFormat='R={0:X2}'}" />

La plantilla de elemento muestra el color, su nombre descriptivo y sus valores RGB:

Binding to a collection with a DataTemplate and a converter.

ListView puede controlar los cambios que se producen de forma dinámica en los datos subyacentes, pero solo si se siguen ciertos pasos. Si la colección de elementos asignados a la propiedad ItemsSource de ListView cambia durante el tiempo de ejecución, usa una clase ObservableCollection<T> para estos elementos. ObservableCollection<T> implementa la interfaz INotifyCollectionChanged, y ListView instalará un controlador para el evento CollectionChanged.

Si las propiedades de los elementos en cuestión cambian durante el tiempo de ejecución, los elementos de la colección deben implementar la interfaz INotifyPropertyChanged y señalar los cambios en los valores de las propiedades con el evento PropertyChanged.

Pasos siguientes

Los enlaces de datos proporcionan un mecanismo eficaz para vincular propiedades entre dos objetos dentro de una página o entre objetos visuales y datos subyacentes. Pero cuando la aplicación comienza a trabajar con orígenes de datos, comienza a aparecer un patrón de arquitectura de aplicaciones conocido como paradigma útil.