Enlaces básicos de Xamarin.Forms
Un enlace de datos de Xamarin.Forms enlaza un par de propiedades entre dos objetos, del que al menos uno suele ser un objeto de interfaz de usuario. Estos dos objetos se denominan el destino y el origen:
- El destino es el objeto (y la propiedad) en la que se establece el enlace de datos.
- El origen es el objeto (y la propiedad) al que hace referencia el enlace de datos.
Esta distinción a veces puede ser un poco confusa: en el caso más simple, los datos fluyen desde el origen al destino, lo que significa que el valor de la propiedad de destino se establece en el valor de la propiedad de origen. Pero en algunos casos, los datos también pueden fluir desde el destino al origen, o en ambas direcciones. Para evitar confusiones, tenga en cuenta que el destino siempre es el objeto en el que se establece el enlace de datos, incluso si ofrece datos en lugar de recibirlos.
Enlaces con un contexto de enlace
Aunque los enlaces de datos normalmente se especifican totalmente en XAML, es útil verlos en el código. La página Enlace básico de código contiene un archivo XAML con un elemento Label
y un elemento Slider
:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BasicCodeBindingPage"
Title="Basic Code Binding">
<StackLayout Padding="10, 0">
<Label x:Name="label"
Text="TEXT"
FontSize="48"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
El elemento Slider
se establece para un intervalo de 0 a 360. El propósito de este programa es girar el elemento Label
mediante la manipulación de Slider
.
Sin los enlaces de datos, se establecería el evento ValueChanged
del elemento Slider
en un controlador de eventos que accede a la propiedad Value
del elemento Slider
y establece ese valor en la propiedad Rotation
del elemento Label
. El enlace de datos automatiza ese trabajo; el controlador de eventos y el código que contiene ya no son necesarios.
Puede establecer un enlace en una instancia de cualquier clase que se derive de BindableObject
, lo que incluye las derivadas Element
, VisualElement
, View
y View
. El enlace siempre se establece en el objeto de destino. El enlace hace referencia al objeto de origen. Para establecer el enlace de datos, use los dos miembros siguientes de la clase de destino:
- La propiedad
BindingContext
especifica el objeto de origen. - El método
SetBinding
especifica la propiedad de destino y la de origen.
En este ejemplo, Label
es el destino de enlace y Slider
es el origen de enlace. Los cambios en el origen de Slider
afectan a la rotación del destino de Label
. Los datos fluyen del origen al destino.
El método SetBinding
definido por BindableObject
tiene un argumento de tipo BindingBase
del que se deriva la clase Binding
, pero existen otros métodos SetBinding
definidos por la clase BindableObjectExtensions
. En el archivo de código subyacente del ejemplo Enlace de código básico se usa un método de extensión SetBinding
más sencillo de esta clase.
public partial class BasicCodeBindingPage : ContentPage
{
public BasicCodeBindingPage()
{
InitializeComponent();
label.BindingContext = slider;
label.SetBinding(Label.RotationProperty, "Value");
}
}
El objeto Label
es el destino de enlace, por lo que es el objeto en el que se establece esta propiedad y en el que se llama al método. La propiedad BindingContext
indica el origen de enlace, que es el elemento Slider
.
El método SetBinding
se llama en el destino de enlace, pero especifica tanto la propiedad de destino como la de origen. La propiedad de destino se especifica como un objeto BindableProperty
: Label.RotationProperty
. La propiedad de origen se especifica como una cadena e indica la propiedad Value
de Slider
.
El método SetBinding
revela una de las reglas de enlaces de datos más importantes:
La propiedad de destino debe estar respaldada por una propiedad enlazable.
Esta regla implica que el objeto de destino debe ser una instancia de una clase que se derive de BindableObject
. Vea el artículo Propiedades enlazables para obtener información general sobre los objetos y las propiedades enlazables.
No hay ninguna regla de este tipo para la propiedad de origen, que se especifica como una cadena. De forma interna, se usa la reflexión para acceder a la propiedad real. Pero en este caso concreto, la propiedad Value
también está respaldada por una propiedad enlazable.
El código se puede simplificar ligeramente: la propiedad enlazable RotationProperty
se define mediante VisualElement
, y también la heredan Label
y ContentPage
, por lo que no es necesario el nombre de clase en la llamada a SetBinding
:
label.SetBinding(RotationProperty, "Value");
Pero la inclusión del nombre de clase es un buen recordatorio del objeto de destino.
Al manipular el elemento Slider
, el elemento Label
gira según corresponda:
La página Enlace Xaml básico es idéntica a la página Enlace de código básico, con la excepción de que define el enlace de datos completo en XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BasicXamlBindingPage"
Title="Basic XAML Binding">
<StackLayout Padding="10, 0">
<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BindingContext="{x:Reference Name=slider}"
Rotation="{Binding Path=Value}" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
Al igual que en el código, el enlace de datos se establece en el objeto de destino, que es el elemento Label
. Intervienen dos extensiones de marcado XAML. Son reconocibles al instante por los delimitadores de llave:
- La extensión de marcado
x:Reference
es necesaria para hacer referencia al objeto de origen, que es el elementoSlider
denominadoslider
. - La extensión de marcado
Binding
enlaza la propiedadRotation
deLabel
a la propiedadValue
deSlider
.
Vea el artículo Extensiones de marcado XAML para obtener más información sobre las extensiones de marcado XAML. La extensión de marcado x:Reference
es compatible con la clase ReferenceExtension
; Binding
es compatible con la clase BindingExtension
. Como indican los prefijos de espacio de nombres XML, x:Reference
forma parte de la especificación 2009 de XAML, mientras que Binding
forma parte de Xamarin.Forms. Observe que no aparecen comillas entre las llaves.
Es fácil olvidar la extensión de marcado x:Reference
cuando se establece el valor BindingContext
. Un error habitual es establecer la propiedad directamente en el nombre del origen de enlace, como en este caso:
BindingContext="slider"
Pero eso no es correcto. Ese marcado establece la propiedad BindingContext
en un objeto string
cuyos caracteres deletrean "slider".
Observe que la propiedad de origen se especifica con la propiedad Path
de BindingExtension
, que se corresponde con la propiedad Path
de la clase Binding
.
El marcado que se muestra en la página Enlace XAML básico se puede simplificar: en las extensiones de marcado XAML como x:Reference
y Binding
se pueden definir atributos de propiedad de contenido, lo que para las extensiones de marcado XAML significa que no es necesario que aparezca el nombre de propiedad. La propiedad Name
es la propiedad de contenido de x:Reference
y la propiedad Path
es la propiedad de contenido de Binding
, lo que significa que se pueden eliminar de las expresiones:
<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BindingContext="{x:Reference slider}"
Rotation="{Binding Value}" />
Enlaces sin un contexto de enlace
La propiedad BindingContext
es un componente importante de los enlaces de datos, pero no siempre es necesaria. En su lugar, el objeto de origen se puede especificar en la llamada a SetBinding
o en la extensión de marcado Binding
.
Esto se muestra en el ejemplo Enlace de código alternativo. El archivo XAML es similar al ejemplo Enlace de código básico, salvo que el elemento Slider
se define para controlar la propiedad Scale
del elemento Label
. Por ese motivo, el elemento Slider
se establece para un intervalo de 2 a 2:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.AlternativeCodeBindingPage"
Title="Alternative Code Binding">
<StackLayout Padding="10, 0">
<Label x:Name="label"
Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Slider x:Name="slider"
Minimum="-2"
Maximum="2"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
El archivo de código subyacente establece el enlace con el método SetBinding
definido por el objeto BindableObject
. El argumento es un constructor para la clase Binding
:
public partial class AlternativeCodeBindingPage : ContentPage
{
public AlternativeCodeBindingPage()
{
InitializeComponent();
label.SetBinding(Label.ScaleProperty, new Binding("Value", source: slider));
}
}
El constructor Binding
tiene seis parámetros, por lo que el parámetro source
se especifica con un argumento con nombre. El argumento es el objeto slider
.
La ejecución de este programa podría ser un poco sorprendente:
En la pantalla de iOS de la izquierda se muestra el aspecto de la pantalla cuando aparece la página por primera vez. ¿Dónde está el elemento Label
?
El problema es que el elemento Slider
tiene un valor inicial de 0. Esto hace que la propiedad Scale
de Label
también se establezca en 0, y se reemplace su valor predeterminado de 1. Como resultado, el elemento Label
es invisible inicialmente. Como se muestra en la captura de pantalla de Android, el elemento Slider
se puede manipular para que Label
aparezca de nuevo, pero su desaparición inicial es desconcertante.
En el artículo siguiente verá cómo evitar este problema si inicializa el elemento Slider
a partir del valor predeterminado de la propiedad Scale
.
Nota:
La clase VisualElement
también define las propiedades ScaleX
y ScaleY
, que pueden escalar el elemento VisualElement
de forma diferente en dirección horizontal y vertical.
En la página Enlace XAML alternativo se muestra el enlace equivalente completamente en XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.AlternativeXamlBindingPage"
Title="Alternative XAML Binding">
<StackLayout Padding="10, 0">
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Scale="{Binding Source={x:Reference slider},
Path=Value}" />
<Slider x:Name="slider"
Minimum="-2"
Maximum="2"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
Ahora la extensión de marcado Binding
tiene dos propiedades establecidas, Source
y Path
, separadas por una coma. Si lo prefiere, pueden aparecer en la misma línea:
Scale="{Binding Source={x:Reference slider}, Path=Value}" />
La propiedad Source
se establece en una extensión de marcado x:Reference
insertada que, en caso contrario, tiene la misma sintaxis que la configuración de BindingContext
. Observe que no aparecen comillas dentro de las llaves, y que las dos propiedades se deben separar por una coma.
La propiedad de contenido de la extensión de marcado Binding
es Path
, pero la parte Path=
de la extensión de marcado solo se puede eliminar si es la primera propiedad en la expresión. Para eliminar la parte Path=
, es necesario intercambiar las dos propiedades:
Scale="{Binding Value, Source={x:Reference slider}}" />
Aunque las extensiones de marcado XAML suelen estar delimitadas por llaves, también se pueden expresar como elementos de objeto:
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<Label.Scale>
<Binding Source="{x:Reference slider}"
Path="Value" />
</Label.Scale>
</Label>
Ahora las propiedades Source
y Path
son atributos XAML normales: los valores aparecen entre comillas y los atributos no están separados por una coma. La extensión de marcado x:Reference
también se puede convertir en un elemento de objeto:
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<Label.Scale>
<Binding Path="Value">
<Binding.Source>
<x:Reference Name="slider" />
</Binding.Source>
</Binding>
</Label.Scale>
</Label>
Esta sintaxis no es común, pero a veces es necesaria cuando hay objetos complejos implicados.
En los ejemplos mostrados hasta ahora se establecen las propiedades BindingContext
y Source
de Binding
en una extensión de marcado x:Reference
para hacer referencia a otra vista en la página. Estas dos propiedades son de tipo Object
, y se pueden establecer en cualquier objeto que incluya propiedades adecuadas para los orígenes de enlace.
En los artículos siguientes, descubrirá que puede establecer la propiedad BindingContext
o Source
en una extensión de marcado x:Static
para hacer referencia al valor de una propiedad estática o un campo, o bien en una extensión de marcado StaticResource
para hacer referencia a un objeto almacenado en un diccionario de recursos, o directamente en un objeto, que suele ser (pero no siempre) una instancia de una clase ViewModel.
La propiedad BindingContext
se puede establecer en un objeto Binding
para que las propiedades Source
y Path
de Binding
definan el contexto de enlace.
Herencia del contexto de enlace
En este artículo, ha visto que puede especificar el objeto de origen mediante las propiedades BindingContext
o Source
del objeto Binding
. Si se establecen las dos, la propiedad Source
de Binding
tiene prioridad sobre BindingContext
.
La propiedad BindingContext
tiene una característica muy importante:
La configuración de la propiedad BindingContext
se hereda a través del árbol visual.
Como verá, esto puede ser muy útil para simplificar las expresiones de enlace y, en algunos casos, especialmente en escenarios de Model-View-ViewModel (MVVM), es esencial.
El ejemplo Herencia de contexto de enlace es una simple demostración de la herencia de contexto de enlace:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BindingContextInheritancePage"
Title="BindingContext Inheritance">
<StackLayout Padding="10">
<StackLayout VerticalOptions="FillAndExpand"
BindingContext="{x:Reference slider}">
<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="EndAndExpand"
Rotation="{Binding Value}" />
<BoxView Color="#800000FF"
WidthRequest="180"
HeightRequest="40"
HorizontalOptions="Center"
VerticalOptions="StartAndExpand"
Rotation="{Binding Value}" />
</StackLayout>
<Slider x:Name="slider"
Maximum="360" />
</StackLayout>
</ContentPage>
La propiedad BindingContext
de StackLayout
se establece en el objeto slider
. Label
y BoxView
heredan este contexto de enlace, y en los dos sus propiedades Rotation
se establecen en la propiedad Value
del elemento Slider
:
En el artículo siguiente, verá cómo el modo de enlace puede cambiar el flujo de datos entre los objetos de origen y destino.