Modo de enlace de Xamarin.FormsXamarin.Forms Binding Mode

Descargar ejemplo Descargar el ejemploDownload Sample Download the sample

En el artículo anterior, las páginas Enlace de código alternativo y Enlace XAML alternativo contenían un objeto Label con su propiedad Scale enlazada a la propiedad Value de un elemento Slider.In the previous article, the Alternative Code Binding and Alternative XAML Binding pages featured a Label with its Scale property bound to the Value property of a Slider. Como el valor inicial de Slider es 0, la propiedad Scale de Label se establece en 0 en lugar de 1, y el elemento Label desaparece.Because the Slider initial value is 0, this caused the Scale property of the Label to be set to 0 rather than 1, and the Label disappeared.

En el ejemplo DataBindingDemos, la página Enlace inverso es similar a los programas del artículo anterior, salvo que el enlace de datos se define en Slider en lugar de Label:In the DataBindingDemos sample, the Reverse Binding page is similar to the programs in the previous article, except that the data binding is defined on the Slider rather than on the Label:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.ReverseBindingPage"
             Title="Reverse Binding">
    <StackLayout Padding="10, 0">

        <Label x:Name="label"
               Text="TEXT"
               FontSize="80"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Slider x:Name="slider"
                VerticalOptions="CenterAndExpand"
                Value="{Binding Source={x:Reference label},
                                Path=Opacity}" />
    </StackLayout>
</ContentPage>

A primera vista, esto podría parecer al contrario: ahora Label es el origen de enlace de datos y Slider es el destino.At first, this might seem backwards: Now the Label is the data-binding source, and the Slider is the target. El enlace hace referencia a la propiedad Opacity de Label, que tiene un valor predeterminado de 1.The binding references the Opacity property of the Label, which has a default value of 1.

Como cabría esperar, Slider se inicializa en el valor 1 a partir del valor Opacity inicial de Label.As you might expect, the Slider is initialized to the value 1 from the initial Opacity value of Label. Esto se muestra en la captura de pantalla de iOS de la izquierda:This is shown in the iOS screenshot on the left:

Enlace inversoReverse Binding

Pero es posible que le sorprenda que Slider siga funcionando, como se muestra en las capturas de pantalla de Android y UWP.But you might be surprised that the Slider continues to work, as the Android and UWP screenshots demonstrate. Esto parece sugerir que el enlace de datos funciona mejor cuando Slider es el destino de enlace en lugar de Label, porque la inicialización funciona de la forma esperada.This seems to suggest that the data binding works better when the Slider is the binding target rather than the Label because the initialization works like we might expect.

La diferencia entre el ejemplo Enlace inverso y los ejemplos anteriores implica el modo de enlace.The difference between the Reverse Binding sample and the earlier samples involves the binding mode.

El modo de enlace predeterminadoThe Default Binding Mode

El modo de enlace se especifica con un miembro de la enumeración BindingMode:The binding mode is specified with a member of the BindingMode enumeration:

  • Default
  • TwoWay – los datos van en ambas direcciones entre el origen y el destinoTwoWay – data goes both ways between source and target
  • OneWay – los datos van del origen al destinoOneWay – data goes from source to target
  • OneWayToSource – los datos van del destino al origenOneWayToSource – data goes from target to source
  • OneTime – los datos van del origen al destino, pero solo cuando cambia BindingContext (novedad de Xamarin.Forms 3.0)OneTime – data goes from source to target, but only when the BindingContext changes (new with Xamarin.Forms 3.0)

Todas las propiedades enlazables tienen un modo de enlace predeterminado que se establece cuando se crea la propiedad enlazable, y que está disponible en la propiedad DefaultBindingMode del objeto BindableProperty.Every bindable property has a default binding mode that is set when the bindable property is created, and which is available from the DefaultBindingMode property of the BindableProperty object. Este modo de enlace predeterminado indica el modo en vigor cuando esa propiedad es un destino de enlace de datos.This default binding mode indicates the mode in effect when that property is a data-binding target.

El modo de enlace predeterminado para la mayoría de las propiedades como Rotation, Scale y Opacity es OneWay.The default binding mode for most properties such as Rotation, Scale, and Opacity is OneWay. Cuando estas propiedades son destinos de enlace de datos, la propiedad de destino se establece desde el origen.When these properties are data-binding targets, then the target property is set from the source.

Pero el modo de enlace predeterminado para la propiedad Value de Slider es TwoWay.However, the default binding mode for the Value property of Slider is TwoWay. Esto significa que cuando la propiedad Value es un destino de enlace de datos, el destino se establece desde el origen (como es habitual), pero el origen también se establece desde el destino.This means that when the Value property is a data-binding target, then the target is set from the source (as usual) but the source is also set from the target. Esto es lo que permite que Slider se establezca en el valor Opacity inicial.This is what allows the Slider to be set from the initial Opacity value.

Es posible que parezca que este enlace bidireccional crea un bucle infinito, pero no es lo que sucede.This two-way binding might seem to create an infinite loop, but that doesn't happen. Las propiedades enlazables no señalan un cambio de propiedad a menos que la propiedad cambie realmente.Bindable properties do not signal a property change unless the property actually changes. Esto evita un bucle infinito.This prevents an infinite loop.

Enlaces bidireccionalesTwo-Way Bindings

La mayoría de las propiedades enlazables tienen un modo de enlace predeterminado de OneWay, pero las propiedades siguientes tienen un modo de enlace predeterminado de TwoWay:Most bindable properties have a default binding mode of OneWay but the following properties have a default binding mode of TwoWay:

  • Propiedad Date de DatePickerDate property of DatePicker
  • Propiedad Text de Editor, Entry, SearchBar y EntryCellText property of Editor, Entry, SearchBar, and EntryCell
  • Propiedad IsRefreshing de ListViewIsRefreshing property of ListView
  • Propiedad SelectedItem de MultiPageSelectedItem property of MultiPage
  • Propiedades SelectedIndex y SelectedItem de PickerSelectedIndex and SelectedItem properties of Picker
  • Propiedad Value de Slider y StepperValue property of Slider and Stepper
  • Propiedad IsToggled de SwitchIsToggled property of Switch
  • Propiedad On de SwitchCellOn property of SwitchCell
  • Propiedad Time de TimePickerTime property of TimePicker

Estas propiedades concretas se definen como TwoWay por una muy buena razón:These particular properties are defined as TwoWay for a very good reason:

Cuando los enlaces de datos se usan con la arquitectura de aplicación Model-View-ViewModel (MVVM), la clase ViewModel es el origen del enlace de datos y la vista, que consta de vistas como Slider, son destinos de enlace de datos.When data bindings are used with the Model-View-ViewModel (MVVM) application architecture, the ViewModel class is the data-binding source, and the View, which consists of views such as Slider, are data-binding targets. Los enlaces de MVVM son más similares al ejemplo Enlace inverso que los enlaces de los ejemplos anteriores.MVVM bindings resemble the Reverse Binding sample more than the bindings in the previous samples. Es muy probable que quiera que cada vista de la página se inicialice con el valor de la propiedad correspondiente en el modelo de vista, pero los cambios en la vista también deberían afectar a la propiedad ViewModel.It is very likely that you want each view on the page to be initialized with the value of the corresponding property in the ViewModel, but changes in the view should also affect the ViewModel property.

Las propiedades con modos de enlace predeterminados de TwoWay son las que probablemente se usen más en escenarios de MVVM.The properties with default binding modes of TwoWay are those properties most likely to be used in MVVM scenarios.

Enlaces unidireccionales al origenOne-Way-to-Source Bindings

Las propiedades enlazables de solo lectura tienen un modo de enlace predeterminado de OneWayToSource.Read-only bindable properties have a default binding mode of OneWayToSource. Solo hay una propiedad enlazable de lectura y escritura que tiene un modo de enlace predeterminado de OneWayToSource:There is only one read/write bindable property that has a default binding mode of OneWayToSource:

  • Propiedad SelectedItem de ListViewSelectedItem property of ListView

La razón es que el resultado de un enlace en la propiedad SelectedItem debe ser el establecimiento del origen de enlace.The rationale is that a binding on the SelectedItem property should result in setting the binding source. En un ejemplo posterior de este artículo se invalida este comportamiento.An example later in this article overrides that behavior.

Enlaces de un solo usoOne-Time Bindings

Varias propiedades enlazables tienen un modo de enlace predeterminado de OneTime, que incluye la propiedad IsTextPredictionEnabled de Entry.Several properties have a default binding mode of OneTime, including the IsTextPredictionEnabled property of Entry.

Las propiedades de destino con un modo de enlace de OneTime solo se actualizan cuando cambia el contexto de enlace.Target properties with a binding mode of OneTime are updated only when the binding context changes. Para los enlaces en estas propiedades de destino, esto simplifica la infraestructura de enlace ya que no es necesario supervisar los cambios en las propiedades de origen.For bindings on these target properties, this simplifies the binding infrastructure because it is not necessary to monitor changes in the source properties.

Modelos de vista y notificaciones de cambio de propiedadViewModels and Property-Change Notifications

En la página Selector de colores simple se muestra el uso de un modelo de vista simple.The Simple Color Selector page demonstrates the use of a simple ViewModel. Los enlaces de datos permiten al usuario seleccionar un color mediante tres elementos Slider para el matiz, la saturación y la luminosidad.Data bindings allow the user to select a color using three Slider elements for the hue, saturation, and luminosity.

El modelo de vista es el origen del enlace de datos.The ViewModel is the data-binding source. El modelo de vista no define propiedades enlazables, pero implementa un mecanismo de notificación que permite que la infraestructura de enlace reciba una notificación cuando cambia el valor de una propiedad.The ViewModel does not define bindable properties, but it does implement a notification mechanism that allows the binding infrastructure to be notified when the value of a property changes. Este mecanismo de notificación es la interfaz de INotifyPropertyChanged, que define un único evento denominado PropertyChanged.This notification mechanism is the INotifyPropertyChanged interface, which defines a single event named PropertyChanged. Una clase que implemente esta interfaz normalmente desencadena el evento cuando cambia el valor de una de sus propiedades públicas.A class that implements this interface generally fires the event when one of its public properties changes value. No es necesario desencadenar el evento si la propiedad no cambia nunca.The event does not need to be fired if the property never changes. (La interfaz INotifyPropertyChanged también se implementa mediante BindableObject y se desencadena un evento PropertyChanged cada vez que una propiedad enlazable cambia de valor).(The INotifyPropertyChanged interface is also implemented by BindableObject and a PropertyChanged event is fired whenever a bindable property changes value.)

La HslColorViewModel clase define cinco propiedades: las propiedades Hue, Saturation, Luminosity y Color están interrelacionadas.The HslColorViewModel class defines five properties: The Hue, Saturation, Luminosity, and Color properties are interrelated. Cuando cambia el valor de cualquiera de los tres componentes de color, se vuelve a calcular la propiedad Color y se desencadenan eventos PropertyChanged para las cuatro propiedades:When any one of the three color components changes value, the Color property is recalculated, and PropertyChanged events are fired for all four properties:

public class HslColorViewModel : INotifyPropertyChanged
{
    Color color;
    string name;

    public event PropertyChangedEventHandler PropertyChanged;

    public double Hue
    {
        set
        {
            if (color.Hue != value)
            {
                Color = Color.FromHsla(value, color.Saturation, color.Luminosity);
            }
        }
        get
        {
            return color.Hue;
        }
    }

    public double Saturation
    {
        set
        {
            if (color.Saturation != value)
            {
                Color = Color.FromHsla(color.Hue, value, color.Luminosity);
            }
        }
        get
        {
            return color.Saturation;
        }
    }

    public double Luminosity
    {
        set
        {
            if (color.Luminosity != value)
            {
                Color = Color.FromHsla(color.Hue, color.Saturation, value);
            }
        }
        get
        {
            return color.Luminosity;
        }
    }

    public Color Color
    {
        set
        {
            if (color != value)
            {
                color = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Hue"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Saturation"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Luminosity"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Color"));

                Name = NamedColor.GetNearestColorName(color);
            }
        }
        get
        {
            return color;
        }
    }

    public string Name
    {
        private set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
            }
        }
        get
        {
            return name;
        }
    }
}

Cuando cambia la propiedad Color, el método estático GetNearestColorName de la clase NamedColor (incluido también en la solución DataBindingDemos)obtiene el color con nombre más cercano y establece la propiedad Name.When the Color property changes, the static GetNearestColorName method in the NamedColor class (also included in the DataBindingDemos solution) obtains the closest named color and sets the Name property. Esta propiedad Name tiene un descriptor de acceso set privado, por lo que no se puede establecer desde fuera de la clase.This Name property has a private set accessor, so it cannot be set from outside the class.

Cuando se establece una clase ViewModel como un origen de enlace, la infraestructura de enlace adjunta un controlador al evento PropertyChanged.When a ViewModel is set as a binding source, the binding infrastructure attaches a handler to the PropertyChanged event. De esta manera, el enlace puede recibir una notificación de cambios en las propiedades y, después, puede establecer las propiedades de destino a partir de los valores cambiados.In this way, the binding can be notified of changes to the properties, and can then set the target properties from the changed values.

Pero cuando una propiedad de destino (o la definición Binding en una propiedad de destino) tiene un elemento BindingMode de tipo OneTime, no es necesario que la infraestructura de enlace adjunte un controlador al evento PropertyChanged.However, when a target property (or the Binding definition on a target property) has a BindingMode of OneTime, it is not necessary for the binding infrastructure to attach a handler on the PropertyChanged event. La propiedad de destino solo se actualiza cuando cambia BindingContext y no cuando cambia la propiedad de origen.The target property is updated only when the BindingContext changes and not when the source property itself changes.

El archivo XAML Selector de colores simple crea una instancia de HslColorViewModel en el diccionario de recursos de la página e inicializa la propiedad Color.The Simple Color Selector XAML file instantiates the HslColorViewModel in the page's resource dictionary and initializes the Color property. La propiedad BindingContext de Grid se establece en una extensión de enlace StaticResource para hacer referencia a ese recurso:The BindingContext property of the Grid is set to a StaticResource binding extension to reference that resource:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.SimpleColorSelectorPage">

    <ContentPage.Resources>
        <ResourceDictionary>
            <local:HslColorViewModel x:Key="viewModel"
                                     Color="MediumTurquoise" />

            <Style TargetType="Slider">
                <Setter Property="VerticalOptions" Value="CenterAndExpand" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>

    <Grid BindingContext="{StaticResource viewModel}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <BoxView Color="{Binding Color}"
                 Grid.Row="0" />

        <StackLayout Grid.Row="1"
                     Margin="10, 0">

            <Label Text="{Binding Name}"
                   HorizontalTextAlignment="Center" />

            <Slider Value="{Binding Hue}" />

            <Slider Value="{Binding Saturation}" />

            <Slider Value="{Binding Luminosity}" />
        </StackLayout>
    </Grid>
</ContentPage>

Los elementos BoxView, Label y tres vistas Slider heredan el contexto de enlace del elemento Grid.The BoxView, Label, and three Slider views inherit the binding context from the Grid. Todas estas vistas son destinos de enlace que hacen referencia a las propiedades de origen en el modelo de vista.These views are all binding targets that reference source properties in the ViewModel. Para la propiedad Color de BoxView y la propiedad Text de Label, los enlaces de datos son OneWay: Las propiedades de la vista se establecen de las propiedades de ViewModel.For the Color property of the BoxView, and the Text property of the Label, the data bindings are OneWay: The properties in the view are set from the properties in the ViewModel.

Pero la propiedad Value de Slider es TwoWay.The Value property of the Slider, however, is TwoWay. Esto permite que cada elemento Slider se establezca a partir del modelo de vista, y también que el modelo de vista se establezca a partir de cada elemento Slider.This allows each Slider to be set from the ViewModel, and also for the ViewModel to be set from each Slider.

Cuando se ejecuta por primera vez el programa, BoxView, Label y los tres elementos Slider están establecidos a partir del modelo de vista en función de la propiedad Color inicial establecida cuando se creó la instancia del modelo de vista.When the program is first run, the BoxView, Label, and three Slider elements are all set from the ViewModel based on the initial Color property set when the ViewModel was instantiated. Esto se muestra en la captura de pantalla de iOS de la izquierda:This is shown in the iOS screenshot at the left:

Selector de colores simpleSimple Color Selector

Al manipular los controles deslizantes, se actualizan BoxView y Label en consecuencia, como se muestra en las capturas de pantalla de Android y UWP.As you manipulate the sliders, the BoxView and Label are updated accordingly, as illustrated by the Android and UWP screenshots.

Un enfoque común consiste en crear instancias de ViewModel en el diccionario de recursos.Instantiating the ViewModel in the resource dictionary is one common approach. También se pueden crear instancias de ViewModel dentro de etiquetas de elemento de propiedad para la propiedad BindingContext.It's also possible to instantiate the ViewModel within property element tags for the BindingContext property. En el archivo XAML Selector de colores simple, intente quitar HslColorViewModel del diccionario de recursos y establézcalo en la propiedad BindingContext de Grid de esta forma:In the Simple Color Selector XAML file, try removing the HslColorViewModel from the resource dictionary and set it to the BindingContext property of the Grid like this:

<Grid>
    <Grid.BindingContext>
        <local:HslColorViewModel Color="MediumTurquoise" />
    </Grid.BindingContext>

    ···

</Grid>

El contexto de enlace se puede establecer de varias formas.The binding context can be set in a variety of ways. En ocasiones, el archivo de código subyacente crea una instancia de ViewModel y la establece en la propiedad BindingContext de la página.Sometimes, the code-behind file instantiates the ViewModel and sets it to the BindingContext property of the page. Todos estos enfoques son válidos.These are all valid approaches.

Reemplazo del modo de enlaceOverriding the Binding Mode

Si el modo de enlace predeterminado en la propiedad de destino no es adecuado para un enlace de datos concreto, es posible invalidarlo si se establece la propiedad Mode de Binding (o la propiedad Mode de la extensión de marcado Binding) en uno de los miembros de la enumeración BindingMode.If the default binding mode on the target property is not suitable for a particular data binding, it's possible to override it by setting the Mode property of Binding (or the Mode property of the Binding markup extension) to one of the members of the BindingMode enumeration.

Pero establecer la propiedad Mode en TwoWay no siempre funciona como cabría esperar.However, setting the Mode property to TwoWay doesn't always work as you might expect. Por ejemplo, intente modificar el archivo XAML Enlace XAML alternativo para incluir TwoWay en la definición del enlace:For example, try modifying the Alternative XAML Binding XAML file to include TwoWay in the binding definition:

<Label Text="TEXT"
       FontSize="40"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand"
       Scale="{Binding Source={x:Reference slider},
                       Path=Value,
                       Mode=TwoWay}" />

Se podría esperar que Slider se inicializara en el valor inicial de la propiedad Scale, que es 1, pero no es lo que sucede.It might be expected that the Slider would be initialized to the initial value of the Scale property, which is 1, but that doesn't happen. Cuando se inicializa un enlace TwoWay, primero se establece el destino a partir del origen, lo que significa que la propiedad Scale se establece en el valor predeterminado de Slider de 0.When a TwoWay binding is initialized, the target is set from the source first, which means that the Scale property is set to the Slider default value of 0. Cuando se configura el enlace TwoWay en el elemento Slider, el elemento Slider se establece inicialmente a partir del origen.When the TwoWay binding is set on the Slider, then the Slider is initially set from the source.

Puede establecer el modo de enlace en OneWayToSource en el ejemplo Enlace XAML alternativo:You can set the binding mode to OneWayToSource in the Alternative XAML Binding sample:

<Label Text="TEXT"
       FontSize="40"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand"
       Scale="{Binding Source={x:Reference slider},
                       Path=Value,
                       Mode=OneWayToSource}" />

Ahora se inicializa Slider en 1 (el valor predeterminado de Scale) pero la manipulación de Slider no afecta a la propiedad Scale, por lo que esto no es muy útil.Now the Slider is initialized to 1 (the default value of Scale) but manipulating the Slider doesn't affect the Scale property, so this is not very useful.

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.The VisualElement class also defines ScaleX and ScaleY properties, which can scale the VisualElement differently in the horizontal and vertical directions.

Una aplicación muy útil de la invalidación del modo de enlace predeterminado con TwoWay implica la propiedad SelectedItem de ListView.A very useful application of overriding the default binding mode with TwoWay involves the SelectedItem property of ListView. El modo de enlace predeterminado es OneWayToSource.The default binding mode is OneWayToSource. Cuando se establece un enlace de datos en la propiedad SelectedItem para hacer referencia a una propiedad de origen en un modelo de vista, esa propiedad de origen se establece a partir de la selección ListView.When a data binding is set on the SelectedItem property to reference a source property in a ViewModel, then that source property is set from the ListView selection. Pero en algunas circunstancias, es posible que le interese que también se inicialice ListView a partir del modelo de vista.However, in some circumstances, you might also want the ListView to be initialized from the ViewModel.

En la página Configuración de ejemplo se muestra esta técnica.The Sample Settings page demonstrates this technique. Esta página representa una implementación simple de la configuración de la aplicación, que a menudo se define en un modelo de vista, como en este archivo SampleSettingsViewModel:This page represents a simple implementation of application settings, which are very often defined in a ViewModel, such as this SampleSettingsViewModel file:

public class SampleSettingsViewModel : INotifyPropertyChanged
{
    string name;
    DateTime birthDate;
    bool codesInCSharp;
    double numberOfCopies;
    NamedColor backgroundNamedColor;

    public event PropertyChangedEventHandler PropertyChanged;

    public SampleSettingsViewModel(IDictionary<string, object> dictionary)
    {
        Name = GetDictionaryEntry<string>(dictionary, "Name");
        BirthDate = GetDictionaryEntry(dictionary, "BirthDate", new DateTime(1980, 1, 1));
        CodesInCSharp = GetDictionaryEntry<bool>(dictionary, "CodesInCSharp");
        NumberOfCopies = GetDictionaryEntry(dictionary, "NumberOfCopies", 1.0);
        BackgroundNamedColor = NamedColor.Find(GetDictionaryEntry(dictionary, "BackgroundNamedColor", "White"));
    }

    public string Name
    {
        set { SetProperty(ref name, value); }
        get { return name; }
    }

    public DateTime BirthDate
    {
        set { SetProperty(ref birthDate, value); }
        get { return birthDate; }
    }

    public bool CodesInCSharp
    {
        set { SetProperty(ref codesInCSharp, value); }
        get { return codesInCSharp; }
    }

    public double NumberOfCopies
    {
        set { SetProperty(ref numberOfCopies, value); }
        get { return numberOfCopies; }
    }

    public NamedColor BackgroundNamedColor
    {
        set
        {
            if (SetProperty(ref backgroundNamedColor, value))
            {
                OnPropertyChanged("BackgroundColor");
            }
        }
        get { return backgroundNamedColor; }
    }

    public Color BackgroundColor
    {
        get { return BackgroundNamedColor?.Color ?? Color.White; }
    }

    public void SaveState(IDictionary<string, object> dictionary)
    {
        dictionary["Name"] = Name;
        dictionary["BirthDate"] = BirthDate;
        dictionary["CodesInCSharp"] = CodesInCSharp;
        dictionary["NumberOfCopies"] = NumberOfCopies;
        dictionary["BackgroundNamedColor"] = BackgroundNamedColor.Name;
    }

    T GetDictionaryEntry<T>(IDictionary<string, object> dictionary, string key, T defaultValue = default(T))
    {
        return dictionary.ContainsKey(key) ? (T)dictionary[key] : defaultValue;
    }

    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Object.Equals(storage, value))
            return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Cada configuración de la aplicación es una propiedad que se guarda en el diccionario de propiedades de Xamarin.Forms en un método denominado SaveState y se carga desde ese diccionario al constructor.Each application setting is a property that is saved to the Xamarin.Forms properties dictionary in a method named SaveState and loaded from that dictionary in the constructor. Hacia la parte inferior de la clase hay dos métodos que ayudan a simplificar los modelos de vista y hacer que sean menos propensos a los errores.Towards the bottom of the class are two methods that help streamline ViewModels and make them less prone to errors. El método OnPropertyChanged de la parte inferior tiene un parámetro opcional que se establece en la propiedad que realiza la llamada.The OnPropertyChanged method at the bottom has an optional parameter that is set to the calling property. Esto evita errores ortográficos al especificar el nombre de la propiedad como una cadena.This avoids spelling errors when specifying the name of the property as a string.

El método SetProperty de la clase hace incluso más: compara el valor que se establece en la propiedad con el valor almacenado como un campo, y solo llama a OnPropertyChanged cuando los dos valores no son iguales.The SetProperty method in the class does even more: It compares the value that is being set to the property with the value stored as a field, and only calls OnPropertyChanged when the two values are not equal.

En la clase SampleSettingsViewModel se definen dos propiedades para el color de fondo: la propiedad BackgroundNamedColor es de tipo NamedColor, que es una clase que también se incluye en la solución DataBindingDemos.The SampleSettingsViewModel class defines two properties for the background color: The BackgroundNamedColor property is of type NamedColor, which is a class also included in the DataBindingDemos solution. La propiedad BackgroundColor es de tipo Color y se obtiene de la propiedad Color del objeto NamedColor.The BackgroundColor property is of type Color, and is obtained from the Color property of the NamedColor object.

La clase NamedColor usa la reflexión de .NET para enumerar todos los campos públicos estáticos en la estructura de Color de Xamarin.Forms y almacenarlos con sus nombres en una colección que sea accesible desde la propiedad All estática:The NamedColor class uses .NET reflection to enumerate all the static public fields in the Xamarin.Forms Color structure, and to store them with their names in a collection accessible from the static All property:

public class NamedColor : IEquatable<NamedColor>, IComparable<NamedColor>
{
    // Instance members
    private NamedColor()
    {
    }

    public string Name { private set; get; }

    public string FriendlyName { private set; get; }

    public Color Color { private set; get; }

    public string RgbDisplay { private set; get; }

    public bool Equals(NamedColor other)
    {
        return Name.Equals(other.Name);
    }

    public int CompareTo(NamedColor other)
    {
        return Name.CompareTo(other.Name);
    }

    // Static members
    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(Color).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.
                Color color = (Color)fieldInfo.GetValue(null);

                NamedColor namedColor = new NamedColor
                {
                    Name = name,
                    FriendlyName = stringBuilder.ToString(),
                    Color = color,
                    RgbDisplay = String.Format("{0:X2}-{1:X2}-{2:X2}",
                                                (int)(255 * color.R),
                                                (int)(255 * color.G),
                                                (int)(255 * color.B))
                };

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

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

    public static NamedColor Find(string name)
    {
        return ((List<NamedColor>)All).Find(nc => nc.Name == name);
    }

    public static string GetNearestColorName(Color color)
    {
        double shortestDistance = 1000;
        NamedColor closestColor = null;

        foreach (NamedColor namedColor in NamedColor.All)
        {
            double distance = Math.Sqrt(Math.Pow(color.R - namedColor.Color.R, 2) +
                                        Math.Pow(color.G - namedColor.Color.G, 2) +
                                        Math.Pow(color.B - namedColor.Color.B, 2));

            if (distance < shortestDistance)
            {
                shortestDistance = distance;
                closestColor = namedColor;
            }
        }
        return closestColor.Name;
    }
}

En la clase App del proyecto DataBindingDemos se define una propiedad denominada Settings de tipo SampleSettingsViewModel.The App class in the DataBindingDemos project defines a property named Settings of type SampleSettingsViewModel. Esta propiedad se inicializa cuando se crea una instancia de la clase App y el método SaveState se llama cuando se llama al método OnSleep:This property is initialized when the App class is instantiated, and the SaveState method is called when the OnSleep method is called:

public partial class App : Application
{
    public App()
    {
        InitializeComponent();

        Settings = new SampleSettingsViewModel(Current.Properties);

        MainPage = new NavigationPage(new MainPage());
    }

    public SampleSettingsViewModel Settings { private set; get; }

    protected override void OnStart()
    {
        // Handle when your app starts
    }

    protected override void OnSleep()
    {
        // Handle when your app sleeps
        Settings.SaveState(Current.Properties);
    }

    protected override void OnResume()
    {
        // Handle when your app resumes
    }
}

Para obtener más información sobre los métodos de ciclo de vida de aplicación, vea el artículo Ciclo de vida de aplicación.For more information on the application lifecycle methods, see the article App Lifecycle.

Prácticamente todo lo demás se controla en el archivo SampleSettingsPage.xaml.Almost everything else is handled in the SampleSettingsPage.xaml file. El elemento BindingContext de la página se establece mediante una extensión de marcado Binding: el origen de enlace es la propiedad estática Application.Current, que es la instancia de la clase App del proyecto, y Path se establece en la propiedad Settings, que es el objeto SampleSettingsViewModel:The BindingContext of the page is set using a Binding markup extension: The binding source is the static Application.Current property, which is the instance of the App class in the project, and the Path is set to the Settings property, which is the SampleSettingsViewModel object:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.SampleSettingsPage"
             Title="Sample Settings"
             BindingContext="{Binding Source={x:Static Application.Current},
                                      Path=Settings}">

    <StackLayout BackgroundColor="{Binding BackgroundColor}"
                 Padding="10"
                 Spacing="10">

        <StackLayout Orientation="Horizontal">
            <Label Text="Name: "
                   VerticalOptions="Center" />

            <Entry Text="{Binding Name}"
                   Placeholder="your name"
                   HorizontalOptions="FillAndExpand"
                   VerticalOptions="Center" />
        </StackLayout>

        <StackLayout Orientation="Horizontal">
            <Label Text="Birth Date: "
                   VerticalOptions="Center" />

            <DatePicker Date="{Binding BirthDate}"
                        HorizontalOptions="FillAndExpand"
                        VerticalOptions="Center" />
        </StackLayout>

        <StackLayout Orientation="Horizontal">
            <Label Text="Do you code in C#? "
                   VerticalOptions="Center" />

            <Switch IsToggled="{Binding CodesInCSharp}"
                    VerticalOptions="Center" />
        </StackLayout>

        <StackLayout Orientation="Horizontal">
            <Label Text="Number of Copies: "
                   VerticalOptions="Center" />

            <Stepper Value="{Binding NumberOfCopies}"
                     VerticalOptions="Center" />

            <Label Text="{Binding NumberOfCopies}"
                   VerticalOptions="Center" />
        </StackLayout>

        <Label Text="Background Color:" />

        <ListView x:Name="colorListView"
                  ItemsSource="{x:Static local:NamedColor.All}"
                  SelectedItem="{Binding BackgroundNamedColor, Mode=TwoWay}"
                  VerticalOptions="FillAndExpand"
                  RowHeight="40">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Orientation="Horizontal">
                            <BoxView Color="{Binding Color}"
                                     HeightRequest="32"
                                     WidthRequest="32"
                                     VerticalOptions="Center" />

                            <Label Text="{Binding FriendlyName}"
                                   FontSize="24"
                                   VerticalOptions="Center" />
                        </StackLayout>                        
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage>

Todos los elementos secundarios de la página heredan el contexto de enlace.All the children of the page inherit the binding context. La mayoría de los demás enlaces de esta página son para las propiedades de SampleSettingsViewModel.Most of the other bindings on this page are to properties in SampleSettingsViewModel. La propiedad BackgroundColor se usa para establecer la propiedad BackgroundColor de StackLayout, y las propiedades Entry, DatePicker, Switch y Stepper se enlazan a otras propiedades del modelo de vista.The BackgroundColor property is used to set the BackgroundColor property of the StackLayout, and the Entry, DatePicker, Switch, and Stepper properties are all bound to other properties in the ViewModel.

La propiedad ItemsSource de ListView se establece en la propiedad estática NamedColor.All.The ItemsSource property of the ListView is set to the static NamedColor.All property. Esto rellena ListView con todas las instancias de NamedColor.This fills the ListView with all the NamedColor instances. Para cada elemento de ListView, el contexto de enlace para el elemento se establece en un objeto NamedColor.For each item in the ListView, the binding context for the item is set to a NamedColor object. Los elementos BoxView y Label de ViewCell están enlazados a propiedades de NamedColor.The BoxView and Label in the ViewCell are bound to properties in NamedColor.

La propiedad SelectedItem de ListView es de tipo NamedColor y se enlaza a la propiedad BackgroundNamedColor de SampleSettingsViewModel:The SelectedItem property of the ListView is of type NamedColor, and is bound to the BackgroundNamedColor property of SampleSettingsViewModel:

SelectedItem="{Binding BackgroundNamedColor, Mode=TwoWay}"

El modo de enlace predeterminado para SelectedItem es OneWayToSource, que establece la propiedad ViewModel a partir del elemento seleccionado.The default binding mode for SelectedItem is OneWayToSource, which sets the ViewModel property from the selected item. El modo TwoWay permite que se inicialice SelectedItem a partir del modelo de vista.The TwoWay mode allows the SelectedItem to be initialized from the ViewModel.

Pero cuando SelectedItem se establece de esta forma, ListView no se desplaza automáticamente para mostrar el elemento seleccionado.However, when the SelectedItem is set in this way, the ListView does not automatically scroll to show the selected item. Se necesita un poco de código en el archivo de código subyacente:A little code in the code-behind file is necessary:

public partial class SampleSettingsPage : ContentPage
{
    public SampleSettingsPage()
    {
        InitializeComponent();

        if (colorListView.SelectedItem != null)
        {
            colorListView.ScrollTo(colorListView.SelectedItem,
                                   ScrollToPosition.MakeVisible,
                                   false);
        }
    }
}

En la captura de pantalla de iOS de la izquierda se muestra el programa al ejecutarlo por primera vez.The iOS screenshot at the left shows the program when it's first run. El constructor de SampleSettingsViewModel inicializa el color de fondo en blanco, y eso es lo que está seleccionado en ListView:The constructor in SampleSettingsViewModel initializes the background color to white, and that's what's selected in the ListView:

Configuración de ejemploSample Settings

La otra captura de pantalla muestra la configuración modificada.The other screenshot shows altered settings. Al experimentar con esta página, recuerde colocar el programa en modo de suspensión o finalícelo en el dispositivo o emulador que se está ejecutando.When experimenting with this page, remember to put the program to sleep or to terminate it on the device or emulator that it's running. La finalización del programa desde el depurador de Visual Studio no provocará que se llame a la invalidación de OnSleep en la clase App.Terminating the program from the Visual Studio debugger will not cause the OnSleep override in the App class to be called.

En el artículo siguiente verá cómo especificar los formatos de cadena de los enlaces de datos que se establecen en la propiedad Text de Label.In the next article you'll see how to specify String Formatting of data bindings that are set on the Text property of Label.