Parte 4. Conceptos básicos del enlace de datos
Los enlaces de datos 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 en el código, XAML proporciona accesos directos y comodidad. Por lo tanto, una de las extensiones de marcado más importantes de Xamarin.Forms es Binding.
Enlaces de datos
Los enlaces de datos conectan propiedades de dos objetos, denominados origen y destino. En el código, se requieren dos pasos: la BindingContext
propiedad del objeto de destino debe establecerse en el objeto de origen y se SetBinding
debe llamar al método (que se usa con frecuencia junto con la Binding
clase ) en el objeto de destino para enlazar una propiedad de ese objeto a 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
. La documentación en línea Xamarin.Forms indica qué propiedades son propiedades enlazables. Una propiedad de Label
, como Text
, está asociada a la propiedad TextProperty
enlazable .
En el marcado, también debe realizar los mismos dos pasos necesarios en el código, salvo que la Binding
extensión de marcado tiene lugar la SetBinding
llamada y la Binding
clase .
Sin embargo, al definir enlaces de datos en XAML, hay varias maneras de establecer el BindingContext
del objeto de destino. A veces se establece desde el archivo de código subyacente, a veces mediante una StaticResource
extensión de marcado o x:Static
y, a veces, como contenido de etiquetas de elemento de BindingContext
propiedad.
Los enlaces se usan con más frecuencia para conectar los objetos visuales de un programa con un modelo de datos subyacente, normalmente en una realización de la arquitectura de la aplicación MVVM (Model-View-ViewModel), como se describe en la parte 5. Desde Enlaces de datos a MVVM, pero otros escenarios son posibles.
Enlaces de vista a vista
Puede definir enlaces de datos para vincular propiedades de dos vistas en la misma página. En este caso, se establece el BindingContext
del objeto de destino mediante la x:Reference
extensión de marcado.
Este es un archivo XAML que contiene una Slider
y dos Label
vistas, una de las cuales gira el Slider
valor y otra que muestra el Slider
valor:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SliderBindingsPage"
Title="Slider Bindings Page">
<StackLayout>
<Label Text="ROTATION"
BindingContext="{x:Reference Name=slider}"
Rotation="{Binding Path=Value}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />
<Label BindingContext="{x:Reference slider}"
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
Slider
contiene un x:Name
atributo al que hacen referencia las dos Label
vistas mediante la x:Reference
extensión de marcado.
La x:Reference
extensión de enlace define una propiedad denominada Name
para establecer en el nombre del elemento al que se hace referencia, en este caso slider
. Sin embargo, la ReferenceExtension
clase que define la x:Reference
extensión de marcado también define un ContentProperty
atributo para Name
, lo que significa que no es necesario explícitamente. Solo para la variedad, la primera x:Reference
incluye "Name=" pero la segunda no:
BindingContext="{x:Reference Name=slider}"
…
BindingContext="{x:Reference slider}"
La Binding
propia extensión de marcado puede tener varias propiedades, al igual que la BindingBase
clase y Binding
. Para ContentProperty
Binding
es Path
, pero la parte "Path=" de la extensión de marcado se puede omitir si la ruta de acceso es el primer elemento de la extensión de Binding
marcado. El primer ejemplo tiene "Path=", pero el segundo ejemplo lo omite:
Rotation="{Binding Path=Value}"
…
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
Todas las propiedades pueden estar en una línea o separadas en varias líneas:
Text="{Binding Value,
StringFormat='The angle is {0:F0} degrees'}"
Haga lo que sea conveniente.
Observe la StringFormat
propiedad en la segunda Binding
extensión de marcado. En Xamarin.Forms, los enlaces no realizan ninguna conversión de tipos implícitas y, si necesita mostrar un objeto que no es de cadena como una cadena, debe proporcionar un convertidor de tipos o usar StringFormat
. En segundo plano, el método estático String.Format
se usa para implementar StringFormat
. Esto podría ser un problema, ya que las especificaciones de formato de .NET implican llaves, que también se usan para delimitar extensiones de marcado. Esto crea un riesgo de confundir el analizador XAML. Para evitarlo, coloque toda la cadena de formato entre comillas simples:
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
Este es el programa en ejecución:
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 Mode
propiedad , que se establece en un miembro de la BindingMode
enumeración:
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 cambian.BindingContext
En el siguiente programa se muestra un uso común de los OneWayToSource
modos de enlace y TwoWay
. Cuatro Slider
vistas están diseñadas para controlar las Scale
propiedades , Rotate
, RotateX
y RotateY
de .Label
En primer lugar, parece que estas cuatro propiedades de deben ser destinos de Label
enlace de datos porque cada una se establece mediante .Slider
Sin embargo, el BindingContext
de Label
solo puede ser un objeto y hay cuatro controles deslizantes diferentes.
Por ese motivo, todos los enlaces se establecen de maneras aparentemente anteriores: el BindingContext
de cada uno de los cuatro controles deslizantes se establece Label
en y los enlaces se establecen en las Value
propiedades de los controles deslizantes. Mediante el uso de los OneWayToSource
modos y TwoWay
, estas Value
propiedades pueden establecer las propiedades de origen, que son las Scale
propiedades , Rotate
, RotateX
y RotateY
de Label
:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
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>
Los enlaces de tres de las Slider
vistas son OneWayToSource
, lo que significa que el Slider
valor provoca un cambio en la propiedad de su BindingContext
, que es el Label
denominado label
. Estas tres Slider
vistas provocan cambios en las Rotate
propiedades , RotateX
y RotateY
de Label
.
Sin embargo, el enlace de la Scale
propiedad es TwoWay
. Esto se debe a que la Scale
propiedad tiene un valor predeterminado de 1 y el uso de un TwoWay
enlace hace que el Slider
valor inicial se establezca en 1 en lugar de 0. Si ese enlace fuera OneWayToSource
, inicialmente la Scale
propiedad se establecería en 0 del Slider
valor predeterminado. no Label
sería visible y esto podría causar cierta confusión al usuario.
Nota
La VisualElement
clase también tiene ScaleX
propiedades y ScaleY
, que escalan en VisualElement
el eje x y eje y respectivamente.
Enlaces y colecciones
Nada ilustra la eficacia de los enlaces de datos y XAML mejor que un objeto con ListView
plantilla.
ListView
define una ItemsSource
propiedad 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 ToString
método de cada elemento para mostrar ese elemento. A veces, esto es solo lo que desea, pero, en muchos casos, ToString
devuelve solo el nombre de clase completo del objeto.
Sin embargo, los elementos de la ListView
colección se pueden mostrar de cualquier manera que desee 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.
Con mucha frecuencia, querrá crear una celda personalizada para estos elementos mediante la ViewCell
clase . Este proceso es algo desordenado en el código, pero en XAML se vuelve muy sencillo.
Incluido en el proyecto XamlSamples es una clase denominada NamedColor
. Cada NamedColor
objeto tiene Name
propiedades y FriendlyName
de tipo string
, y una Color
propiedad de tipo Color
. Además, NamedColor
tiene 141 campos estáticos de solo lectura de tipo Color
correspondientes a los colores definidos en la Xamarin.FormsColor
clase . Un constructor estático crea una IEnumerable<NamedColor>
colección que contiene NamedColor
objetos correspondientes a estos campos estáticos y los asigna a su propiedad estática All
pública.
Establecer la propiedad estática NamedColor.All
en de ItemsSource
un ListView
es fácil de usar la x:Static
extensión de marcado:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
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>
La presentación resultante establece que los elementos son verdaderamente de tipo XamlSamples.NamedColor
:
No es mucha información, pero es ListView
desplazable y seleccionable.
Para definir una plantilla para los elementos, querrá dividir la ItemTemplate
propiedad como un elemento de propiedad y establecerla en , DataTemplate
que a continuación hace referencia a .ViewCell
Para la View
propiedad de ViewCell
puede definir un diseño de una o varias vistas para mostrar cada elemento. Este es un ejemplo sencillo:
<ListView ItemsSource="{x:Static local:NamedColor.All}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Label Text="{Binding FriendlyName}" />
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Nota
El origen de enlace para las celdas y los elementos secundarios de las celdas es la ListView.ItemsSource
colección .
El Label
elemento se establece en la View
propiedad de ViewCell
. (Las ViewCell.View
etiquetas no son necesarias porque la View
propiedad es la propiedad content de ViewCell
). Este marcado muestra la FriendlyName
propiedad de cada NamedColor
objeto:
Mucho mejor. Ahora todo lo que se necesita es aducir la plantilla de elemento con más información y el color real. Para admitir esta plantilla, algunos valores y objetos se han definido en el diccionario de recursos de la página:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
x:Class="XamlSamples.ListViewDemoPage"
Title="ListView Demo Page">
<ContentPage.Resources>
<ResourceDictionary>
<OnPlatform x:Key="boxSize"
x:TypeArguments="x:Double">
<On Platform="iOS, Android, UWP" Value="50" />
</OnPlatform>
<OnPlatform x:Key="rowHeight"
x:TypeArguments="x:Int32">
<On Platform="iOS, Android, UWP" Value="60" />
</OnPlatform>
<local:DoubleToIntConverter x:Key="intConverter" />
</ResourceDictionary>
</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="Medium" />
<StackLayout Orientation="Horizontal"
Spacing="0">
<Label Text="{Binding Color.R,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />
<Label Text="{Binding Color.G,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat=', G={0:X2}'}" />
<Label Text="{Binding Color.B,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat=', B={0:X2}'}" />
</StackLayout>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
Observe el uso de OnPlatform
para definir el tamaño de y BoxView
el alto de las ListView
filas. Aunque los valores de todas las plataformas son los mismos, el marcado podría adaptarse fácilmente para otros valores para ajustar la pantalla.
Enlace de convertidores de valores
El archivo XAML de demostración de ListView anterior muestra las propiedades individuales R
, G
y B
de la Xamarin.FormsColor
estructura. Estas propiedades son de tipo double
y van de 0 a 1. Si desea mostrar los valores hexadecimales, no puede usar StringFormat
simplemente con una especificación de formato "X2". Esto solo funciona para enteros y, además, los double
valores deben multiplicarse por 255.
Este pequeño problema se resolvió con un convertidor de valores, también denominado convertidor de enlaces. Se trata de una clase que implementa la IValueConverter
interfaz , lo que significa que tiene dos métodos denominados Convert
y ConvertBack
. Se Convert
llama al método cuando se transfiere un valor de origen a destino; se llama al ConvertBack
método para las transferencias de destino a origen en OneWayToSource
o TwoWay
enlaces:
using System;
using System.Globalization;
using Xamarin.Forms;
namespace XamlSamples
{
class DoubleToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
double multiplier;
if (!Double.TryParse(parameter as string, out multiplier))
multiplier = 1;
return (int)Math.Round(multiplier * (double)value);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
double divider;
if (!Double.TryParse(parameter as string, out divider))
divider = 1;
return ((double)(int)value) / divider;
}
}
}
El ConvertBack
método no desempeña un papel en este programa porque los enlaces son solo una manera de llegar de origen a destino.
Un enlace hace referencia a un convertidor de enlaces con la Converter
propiedad . Un convertidor de enlaces también puede aceptar un parámetro especificado con la ConverterParameter
propiedad . Para cierta versatilidad, así es como se especifica el multiplicador. El convertidor de enlaces comprueba el parámetro del convertidor para obtener un valor válido double
.
Se crea una instancia del convertidor en el diccionario de recursos para que se pueda compartir entre varios enlaces:
<local:DoubleToIntConverter x:Key="intConverter" />
Tres enlaces de datos hacen referencia a esta única instancia. Observe que la Binding
extensión de marcado contiene una extensión de marcado incrustada StaticResource
:
<Label Text="{Binding Color.R,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />
Este es el resultado:
ListView
es bastante sofisticado en el control de los cambios que pueden producirse dinámicamente en los datos subyacentes, pero solo si realiza ciertos pasos. Si la colección de elementos asignados a la propiedad de los cambios durante el ItemsSource
ListView
tiempo de ejecución( es decir, si se pueden agregar o quitar elementos de la colección), use una ObservableCollection
clase para estos elementos. ObservableCollection
implementa la INotifyCollectionChanged
interfaz e ListView
instalará un controlador para el CollectionChanged
evento.
Si las propiedades de los propios elementos cambian durante el tiempo de ejecución, los elementos de la colección deben implementar la INotifyPropertyChanged
interfaz y indicar los cambios en los valores de propiedad mediante el PropertyChanged
evento . Esto se muestra en la siguiente parte de esta serie, Parte 5. Del enlace de datos a MVVM.
Resumen
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, un patrón de arquitectura de aplicación popular comienza a surgir como paradigma útil. Esto se trata en la parte 5. Desde Enlaces de datos a MVVM.