Aspectos básicos del enlace de datos
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:
- La propiedad
BindingContext
del objeto de destino debe establecerse en el objeto de origen, - El método
SetBinding
( que a menudo se usa junto con la claseBinding
) 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 elBindingContext
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:
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. No Label 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
:
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
:
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:
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.