Parte 5.Part 5. Enlaces de datos a MVVMFrom Data Bindings to MVVM

Descargar ejemplo Descargar el ejemploDownload Sample Download the sample

El patrón arquitectónico Model-View-ViewModel (MVVM) se inventó pensando en XAML. El patrón exige una separación entre tres niveles de software: la interfaz de usuario de XAML, denominada vista; los datos subyacentes, denominados modelo; y un intermediario entre la vista y el modelo, denominado ViewModel. A menudo, la vista y el ViewModel se conectan a través de los enlaces de datos definidos en el archivo XAML. El BindingContext de la vista suele ser una instancia de ViewModel.The Model-View-ViewModel (MVVM) architectural pattern was invented with XAML in mind. The pattern enforces a separation between three software layers — the XAML user interface, called the View; the underlying data, called the Model; and an intermediary between the View and the Model, called the ViewModel. The View and the ViewModel are often connected through data bindings defined in the XAML file. The BindingContext for the View is usually an instance of the ViewModel.

Un ViewModel sencilloA Simple ViewModel

Como introducción a ViewModels, echemos un vistazo primero a un programa sin ninguno.As an introduction to ViewModels, let’s first look at a program without one. Anteriormente, vimos cómo definir una nueva declaración de espacio de nombres XML para permitir que un archivo XAML haga referencia a clases de otros ensamblados.Earlier you saw how to define a new XML namespace declaration to allow a XAML file to reference classes in other assemblies. Este es un programa que define una declaración de espacio de nombres XML para el System espacio de nombres:Here’s a program that defines an XML namespace declaration for the System namespace:

xmlns:sys="clr-namespace:System;assembly=netstandard"

El programa puede usar x:Static para obtener la fecha y hora actuales de la DateTime.Now propiedad estática y establecer ese DateTime valor en BindingContext en un StackLayout :The program can use x:Static to obtain the current date and time from the static DateTime.Now property and set that DateTime value to the BindingContext on a StackLayout:

<StackLayout BindingContext="{x:Static sys:DateTime.Now}" …>

BindingContextes una propiedad especial: cuando se establece BindingContext en un elemento, lo heredan todos los elementos secundarios de ese elemento.BindingContext is a special property: When you set the BindingContext on an element, it is inherited by all the children of that element. Esto significa que todos los elementos secundarios de StackLayout tienen este mismo BindingContext y pueden contener enlaces simples a las propiedades de ese objeto.This means that all the children of the StackLayout have this same BindingContext, and they can contain simple bindings to properties of that object.

En el programa de fecha y hora de una captura , dos de los elementos secundarios contienen enlaces a las propiedades de ese DateTime valor, pero otros dos elementos secundarios contienen enlaces a los que parece que falta una ruta de acceso de enlace.In the One-Shot DateTime program, two of the children contain bindings to properties of that DateTime value, but two other children contain bindings that seem to be missing a binding path. Esto significa que el DateTime propio valor se utiliza para StringFormat :This means that the DateTime value itself is used for the StringFormat:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:sys="clr-namespace:System;assembly=netstandard"
             x:Class="XamlSamples.OneShotDateTimePage"
             Title="One-Shot DateTime Page">

    <StackLayout BindingContext="{x:Static sys:DateTime.Now}"
                 HorizontalOptions="Center"
                 VerticalOptions="Center">

        <Label Text="{Binding Year, StringFormat='The year is {0}'}" />
        <Label Text="{Binding StringFormat='The month is {0:MMMM}'}" />
        <Label Text="{Binding Day, StringFormat='The day is {0}'}" />
        <Label Text="{Binding StringFormat='The time is {0:T}'}" />

    </StackLayout>
</ContentPage>

El problema es que la fecha y la hora se establecen una vez cuando la página se compila por primera vez y nunca cambian:The problem is that the date and time are set once when the page is first built, and never change:

Un archivo XAML puede mostrar un reloj que siempre muestra la hora actual, pero necesita código para ayudarlo. Al pensar en términos de MVVM, el modelo y el ViewModel son clases escritas completamente en el código.A XAML file can display a clock that always shows the current time, but it needs some code to help out. When thinking in terms of MVVM, the Model and ViewModel are classes written entirely in code. La vista suele ser un archivo XAML que hace referencia a las propiedades definidas en el ViewModel a través de los enlaces de datos.The View is often a XAML file that references properties defined in the ViewModel through data bindings.

Un modelo adecuado no es lo mismo que el ViewModel y un ViewModel adecuado se ignora en la vista.A proper Model is ignorant of the ViewModel, and a proper ViewModel is ignorant of the View. Sin embargo, a menudo un programador se pone a la cola de los tipos de datos expuestos por el ViewModel en los tipos de datos asociados a determinadas interfaces de usuario.However, often a programmer tailors the data types exposed by the ViewModel to the data types associated with particular user interfaces. Por ejemplo, si un modelo obtiene acceso a una base de datos que contiene cadenas ASCII de caracteres de 8 bits, el ViewModel necesitará convertir esas cadenas en cadenas Unicode para dar cabida al uso exclusivo de Unicode en la interfaz de usuario.For example, if a Model accesses a database that contains 8-bit character ASCII strings, the ViewModel would need to convert between those strings to Unicode strings to accommodate the exclusive use of Unicode in the user interface.

En ejemplos sencillos de MVVM (como los que se muestran aquí), a menudo no hay ningún modelo, y el patrón implica solo una vista y ViewModel vinculados a los enlaces de datos.In simple examples of MVVM (such as those shown here), often there is no Model at all, and the pattern involves just a View and ViewModel linked with data bindings.

Aquí se muestra un ViewModel para un reloj con solo una propiedad denominada DateTime , que actualiza esa DateTime propiedad cada segundo:Here’s a ViewModel for a clock with just a single property named DateTime, which updates that DateTime property every second:

using System;
using System.ComponentModel;
using Xamarin.Forms;

namespace XamlSamples
{
    class ClockViewModel : INotifyPropertyChanged
    {
        DateTime dateTime;

        public event PropertyChangedEventHandler PropertyChanged;

        public ClockViewModel()
        {
            this.DateTime = DateTime.Now;

            Device.StartTimer(TimeSpan.FromSeconds(1), () =>
                {
                    this.DateTime = DateTime.Now;
                    return true;
                });
        }

        public DateTime DateTime
        {
            set
            {
                if (dateTime != value)
                {
                    dateTime = value;

                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("DateTime"));
                    }
                }
            }
            get
            {
                return dateTime;
            }
        }
    }
}

ViewModels generalmente implementa la INotifyPropertyChanged interfaz, lo que significa que la clase activa un PropertyChanged evento cada vez que cambia una de sus propiedades.ViewModels generally implement the INotifyPropertyChanged interface, which means that the class fires a PropertyChanged event whenever one of its properties changes. El mecanismo de enlace de datos de Xamarin.Forms asocia un controlador a este PropertyChanged evento para que se le pueda notificar cuando una propiedad cambie y mantener el destino actualizado con el nuevo valor.The data binding mechanism in Xamarin.Forms attaches a handler to this PropertyChanged event so it can be notified when a property changes and keep the target updated with the new value.

Un reloj basado en este ViewModel puede ser tan sencillo como el siguiente:A clock based on this ViewModel can be as simple as this:

<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.ClockPage"
             Title="Clock Page">

    <Label Text="{Binding DateTime, StringFormat='{0:T}'}"
           FontSize="Large"
           HorizontalOptions="Center"
           VerticalOptions="Center">
        <Label.BindingContext>
            <local:ClockViewModel />
        </Label.BindingContext>
    </Label>
</ContentPage>

Observe cómo ClockViewModel se establece en el BindingContext de las Label etiquetas de elemento de propiedad using.Notice how the ClockViewModel is set to the BindingContext of the Label using property element tags. Como alternativa, puede crear una instancia ClockViewModel de en una Resources colección y establecerla en a BindingContext través de una StaticResource extensión de marcado.Alternatively, you can instantiate the ClockViewModel in a Resources collection and set it to the BindingContext via a StaticResource markup extension. O bien, el archivo de código subyacente puede crear una instancia del ViewModel.Or, the code-behind file can instantiate the ViewModel.

La Binding extensión de marcado de la Text propiedad de Label da formato a la DateTime propiedad.The Binding markup extension on the Text property of the Label formats the DateTime property. Esta es la pantalla:Here’s the display:

También es posible obtener acceso a las propiedades individuales de la DateTime propiedad del ViewModel separando las propiedades con puntos:It’s also possible to access individual properties of the DateTime property of the ViewModel by separating the properties with periods:

<Label Text="{Binding DateTime.Second, StringFormat='{0}'}" … >

MVVM interactivoInteractive MVVM

MVVM se usa a menudo con enlaces de datos bidireccionales para una vista interactiva basada en un modelo de datos subyacente.MVVM is often used with two-way data bindings for an interactive view based on an underlying data model.

Esta es una clase denominada HslViewModel que convierte un Color valor en Hue Saturation los valores, y Luminosity , y viceversa:Here’s a class named HslViewModel that converts a Color value into Hue, Saturation, and Luminosity values, and vice versa:

using System;
using System.ComponentModel;
using Xamarin.Forms;

namespace XamlSamples
{
    public class HslViewModel : INotifyPropertyChanged
    {
        double hue, saturation, luminosity;
        Color color;

        public event PropertyChangedEventHandler PropertyChanged;

        public double Hue
        {
            set
            {
                if (hue != value)
                {
                    hue = value;
                    OnPropertyChanged("Hue");
                    SetNewColor();
                }
            }
            get
            {
                return hue;
            }
        }

        public double Saturation
        {
            set
            {
                if (saturation != value)
                {
                    saturation = value;
                    OnPropertyChanged("Saturation");
                    SetNewColor();
                }
            }
            get
            {
                return saturation;
            }
        }

        public double Luminosity
        {
            set
            {
                if (luminosity != value)
                {
                    luminosity = value;
                    OnPropertyChanged("Luminosity");
                    SetNewColor();
                }
            }
            get
            {
                return luminosity;
            }
        }

        public Color Color
        {
            set
            {
                if (color != value)
                {
                    color = value;
                    OnPropertyChanged("Color");

                    Hue = value.Hue;
                    Saturation = value.Saturation;
                    Luminosity = value.Luminosity;
                }
            }
            get
            {
                return color;
            }
        }

        void SetNewColor()
        {
            Color = Color.FromHsla(Hue, Saturation, Luminosity);
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Los cambios en Hue las Saturation propiedades, y Luminosity hacen que la Color propiedad cambie y los cambios en Color hacen que las otras tres propiedades cambien.Changes to the Hue, Saturation, and Luminosity properties cause the Color property to change, and changes to Color causes the other three properties to change. Esto podría parecer un bucle infinito, salvo que la clase no invoca el PropertyChanged evento a menos que la propiedad haya cambiado.This might seem like an infinite loop, except that the class doesn't invoke the PropertyChanged event unless the property has changed. Esto coloca un extremo en el bucle de comentarios no controlable en caso contrario.This puts an end to the otherwise uncontrollable feedback loop.

El siguiente archivo XAML contiene una BoxView cuya Color propiedad está enlazada a la Color propiedad del ViewModel y tres Slider y tres Label vistas enlazadas a las Hue propiedades, Saturation y Luminosity :The following XAML file contains a BoxView whose Color property is bound to the Color property of the ViewModel, and three Slider and three Label views bound to the Hue, Saturation, and Luminosity properties:

<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.HslColorScrollPage"
             Title="HSL Color Scroll Page">
    <ContentPage.BindingContext>
        <local:HslViewModel Color="Aqua" />
    </ContentPage.BindingContext>

    <StackLayout Padding="10, 0">
        <BoxView Color="{Binding Color}"
                 VerticalOptions="FillAndExpand" />

        <Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}"
               HorizontalOptions="Center" />

        <Slider Value="{Binding Hue, Mode=TwoWay}" />

        <Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}"
               HorizontalOptions="Center" />

        <Slider Value="{Binding Saturation, Mode=TwoWay}" />

        <Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}"
               HorizontalOptions="Center" />

        <Slider Value="{Binding Luminosity, Mode=TwoWay}" />
    </StackLayout>
</ContentPage>

El enlace en cada Label es el valor predeterminado OneWay .The binding on each Label is the default OneWay. Solo necesita mostrar el valor.It only needs to display the value. Pero el enlace en cada Slider es TwoWay .But the binding on each Slider is TwoWay. Esto permite Slider que se inicialice desde el ViewModel.This allows the Slider to be initialized from the ViewModel. Observe que la Color propiedad se establece en Aqua cuando se crea una instancia del ViewModel.Notice that the Color property is set to Aqua when the ViewModel is instantiated. Pero un cambio en Slider también necesita establecer un nuevo valor para la propiedad en el ViewModel, que a continuación calcula un nuevo color.But a change in the Slider also needs to set a new value for the property in the ViewModel, which then calculates a new color.

Comandos con ViewModelsCommanding with ViewModels

En muchos casos, el patrón MVVM está restringido a la manipulación de los elementos de datos: objetos de la interfaz de usuario en la vista objetos de datos en paralelo en el ViewModel.In many cases, the MVVM pattern is restricted to the manipulation of data items: User-interface objects in the View parallel data objects in the ViewModel.

Sin embargo, a veces la vista debe contener botones que desencadenen varias acciones en el ViewModel.However, sometimes the View needs to contain buttons that trigger various actions in the ViewModel. Sin embargo, el ViewModel no debe contener Clicked Controladores para los botones porque esto enlazaría el modelo de administración a un paradigma determinado de la interfaz de usuario.But the ViewModel must not contain Clicked handlers for the buttons because that would tie the ViewModel to a particular user-interface paradigm.

Para permitir que ViewModels sea más independiente de los objetos de interfaz de usuario concretos pero que todavía permiten llamar a los métodos dentro de ViewModel, existe una interfaz de comandos .To allow ViewModels to be more independent of particular user interface objects but still allow methods to be called within the ViewModel, a command interface exists. Esta interfaz de comandos es compatible con los siguientes elementos de Xamarin.Forms :This command interface is supported by the following elements in Xamarin.Forms:

  • Button
  • MenuItem
  • ToolbarItem
  • SearchBar
  • TextCell(y, por tanto, también ImageCell )TextCell (and hence also ImageCell)
  • ListView
  • TapGestureRecognizer

A excepción del SearchBar ListView elemento y, estos elementos definen dos propiedades:With the exception of the SearchBar and ListView element, these elements define two properties:

  • Commandde tipoSystem.Windows.Input.ICommandCommand of type System.Windows.Input.ICommand
  • CommandParameterde tipoObjectCommandParameter of type Object

SearchBarDefine las SearchCommand SearchCommandParameter propiedades y, mientras que ListView define una RefreshCommand propiedad de tipo ICommand .The SearchBar defines SearchCommand and SearchCommandParameter properties, while the ListView defines a RefreshCommand property of type ICommand.

La ICommand interfaz define dos métodos y un evento:The ICommand interface defines two methods and one event:

  • void Execute(object arg)
  • bool CanExecute(object arg)
  • event EventHandler CanExecuteChanged

El ViewModel puede definir propiedades de tipo ICommand .The ViewModel can define properties of type ICommand. Después, puede enlazar estas propiedades a la Command propiedad de cada Button u otro elemento, o quizás una vista personalizada que implemente esta interfaz.You can then bind these properties to the Command property of each Button or other element, or perhaps a custom view that implements this interface. Opcionalmente, puede establecer la CommandParameter propiedad para identificar Button objetos individuales (u otros elementos) que estén enlazados a esta propiedad ViewModel.You can optionally set the CommandParameter property to identify individual Button objects (or other elements) that are bound to this ViewModel property. Internamente, Button llama al Execute método cada vez que el usuario puntea Button y pasa al Execute método CommandParameter .Internally, the Button calls the Execute method whenever the user taps the Button, passing to the Execute method its CommandParameter.

El CanExecute método y el CanExecuteChanged evento se usan para los casos en los que una Button derivación no es válida actualmente, en cuyo caso se Button debe deshabilitar.The CanExecute method and CanExecuteChanged event are used for cases where a Button tap might be currently invalid, in which case the Button should disable itself. ButtonLlama a CanExecute cuando la Command propiedad se establece por primera vez y cada vez que CanExecuteChanged se desencadena el evento.The Button calls CanExecute when the Command property is first set and whenever the CanExecuteChanged event is fired. Si CanExecute devuelve false , se Button deshabilita a sí mismo y no genera Execute llamadas.If CanExecute returns false, the Button disables itself and doesn’t generate Execute calls.

Para obtener ayuda con la adición de comandos a ViewModels, Xamarin.Forms define dos clases que implementan ICommand : Command y Command<T> donde T es el tipo de los argumentos de Execute y CanExecute .For help with adding commanding to your ViewModels, Xamarin.Forms defines two classes that implement ICommand: Command and Command<T> where T is the type of the arguments to Execute and CanExecute. Estas dos clases definen varios constructores más un ChangeCanExecute método al que puede llamar el ViewModel para forzar al Command objeto a desencadenar el CanExecuteChanged evento.These two classes define several constructors plus a ChangeCanExecute method that the ViewModel can call to force the Command object to fire the CanExecuteChanged event.

Este es un ViewModel para un teclado simple que está pensado para especificar números de teléfono.Here is a ViewModel for a simple keypad that is intended for entering telephone numbers. Observe que el Execute CanExecute método y se definen como funciones lambda directamente en el constructor:Notice that the Execute and CanExecute method are defined as lambda functions right in the constructor:

using System;
using System.ComponentModel;
using System.Windows.Input;
using Xamarin.Forms;

namespace XamlSamples
{
    class KeypadViewModel : INotifyPropertyChanged
    {
        string inputString = "";
        string displayText = "";
        char[] specialChars = { '*', '#' };

        public event PropertyChangedEventHandler PropertyChanged;

        // Constructor
        public KeypadViewModel()
        {
            AddCharCommand = new Command<string>((key) =>
                {
                    // Add the key to the input string.
                    InputString += key;
                });

            DeleteCharCommand = new Command(() =>
                {
                    // Strip a character from the input string.
                    InputString = InputString.Substring(0, InputString.Length - 1);
                },
                () =>
                {
                    // Return true if there's something to delete.
                    return InputString.Length > 0;
                });
        }

        // Public properties
        public string InputString
        {
            protected set
            {
                if (inputString != value)
                {
                    inputString = value;
                    OnPropertyChanged("InputString");
                    DisplayText = FormatText(inputString);

                    // Perhaps the delete button must be enabled/disabled.
                    ((Command)DeleteCharCommand).ChangeCanExecute();
                }
            }

            get { return inputString; }
        }

        public string DisplayText
        {
            protected set
            {
                if (displayText != value)
                {
                    displayText = value;
                    OnPropertyChanged("DisplayText");
                }
            }
            get { return displayText; }
        }

        // ICommand implementations
        public ICommand AddCharCommand { protected set; get; }

        public ICommand DeleteCharCommand { protected set; get; }

        string FormatText(string str)
        {
            bool hasNonNumbers = str.IndexOfAny(specialChars) != -1;
            string formatted = str;

            if (hasNonNumbers || str.Length < 4 || str.Length > 10)
            {
            }
            else if (str.Length < 8)
            {
                formatted = String.Format("{0}-{1}",
                                          str.Substring(0, 3),
                                          str.Substring(3));
            }
            else
            {
                formatted = String.Format("({0}) {1}-{2}",
                                          str.Substring(0, 3),
                                          str.Substring(3, 3),
                                          str.Substring(6));
            }
            return formatted;
        }

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Este ViewModel supone que la AddCharCommand propiedad está enlazada a la Command propiedad de varios botones (o cualquier otro que tenga una interfaz de comandos), cada uno de los cuales se identifica mediante CommandParameter .This ViewModel assumes that the AddCharCommand property is bound to the Command property of several buttons (or anything else that has a command interface), each of which is identified by the CommandParameter. Estos botones agregan caracteres a una InputString propiedad, a la que se le aplica el formato de número de teléfono para la DisplayText propiedad.These buttons add characters to an InputString property, which is then formatted as a phone number for the DisplayText property.

También hay una segunda propiedad de tipo ICommand denominada DeleteCharCommand .There is also a second property of type ICommand named DeleteCharCommand. Está enlazado a un botón de espaciado de retroceso, pero el botón debe deshabilitarse si no hay ningún carácter para eliminar.This is bound to a back-spacing button, but the button should be disabled if there are no characters to delete.

El siguiente teclado no es tan sofisticado como podría ser.The following keypad is not as visually sophisticated as it could be. En su lugar, el marcado se ha reducido a un mínimo para demostrar más claramente el uso de la interfaz de comandos:Instead, the markup has been reduced to a minimum to demonstrate more clearly the use of the command interface:

<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.KeypadPage"
             Title="Keypad Page">

    <Grid HorizontalOptions="Center"
          VerticalOptions="Center">
        <Grid.BindingContext>
            <local:KeypadViewModel />
        </Grid.BindingContext>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="80" />
            <ColumnDefinition Width="80" />
            <ColumnDefinition Width="80" />
        </Grid.ColumnDefinitions>

        <!-- Internal Grid for top row of items -->
        <Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

            <Frame Grid.Column="0"
                   OutlineColor="Accent">
                <Label Text="{Binding DisplayText}" />
            </Frame>

            <Button Text="&#x21E6;"
                    Command="{Binding DeleteCharCommand}"
                    Grid.Column="1"
                    BorderWidth="0" />
        </Grid>

        <Button Text="1"
                Command="{Binding AddCharCommand}"
                CommandParameter="1"
                Grid.Row="1" Grid.Column="0" />

        <Button Text="2"
                Command="{Binding AddCharCommand}"
                CommandParameter="2"
                Grid.Row="1" Grid.Column="1" />

        <Button Text="3"
                Command="{Binding AddCharCommand}"
                CommandParameter="3"
                Grid.Row="1" Grid.Column="2" />

        <Button Text="4"
                Command="{Binding AddCharCommand}"
                CommandParameter="4"
                Grid.Row="2" Grid.Column="0" />

        <Button Text="5"
                Command="{Binding AddCharCommand}"
                CommandParameter="5"
                Grid.Row="2" Grid.Column="1" />

        <Button Text="6"
                Command="{Binding AddCharCommand}"
                CommandParameter="6"
                Grid.Row="2" Grid.Column="2" />

        <Button Text="7"
                Command="{Binding AddCharCommand}"
                CommandParameter="7"
                Grid.Row="3" Grid.Column="0" />

        <Button Text="8"
                Command="{Binding AddCharCommand}"
                CommandParameter="8"
                Grid.Row="3" Grid.Column="1" />

        <Button Text="9"
                Command="{Binding AddCharCommand}"
                CommandParameter="9"
                Grid.Row="3" Grid.Column="2" />

        <Button Text="*"
                Command="{Binding AddCharCommand}"
                CommandParameter="*"
                Grid.Row="4" Grid.Column="0" />

        <Button Text="0"
                Command="{Binding AddCharCommand}"
                CommandParameter="0"
                Grid.Row="4" Grid.Column="1" />

        <Button Text="#"
                Command="{Binding AddCharCommand}"
                CommandParameter="#"
                Grid.Row="4" Grid.Column="2" />
    </Grid>
</ContentPage>

La Command propiedad de la primera Button que aparece en este marcado se enlaza a DeleteCharCommand ; el resto se enlaza al AddCharCommand con un CommandParameter que es igual que el carácter que aparece en la Button superficie.The Command property of the first Button that appears in this markup is bound to the DeleteCharCommand; the rest are bound to the AddCharCommand with a CommandParameter that is the same as the character that appears on the Button face. Este es el programa en acción:Here’s the program in action:

Invocar métodos asincrónicosInvoking Asynchronous Methods

Los comandos también pueden invocar métodos asincrónicos.Commands can also invoke asynchronous methods. Esto se logra mediante el uso de las async await palabras clave y al especificar el Execute método:This is achieved by using the async and await keywords when specifying the Execute method:

DownloadCommand = new Command (async () => await DownloadAsync ());

Esto indica que el DownloadAsync método es Task y se debe esperar:This indicates that the DownloadAsync method is a Task and should be awaited:

async Task DownloadAsync ()
{
    await Task.Run (() => Download ());
}

void Download ()
{
    ...
}

Implementar un menú de navegaciónImplementing a Navigation Menu

El programa XamlSamples que contiene todo el código fuente de esta serie de artículos usa un ViewModel para su página principal.The XamlSamples program that contains all the source code in this series of articles uses a ViewModel for its home page. Este ViewModel es una definición de una clase corta con tres propiedades denominadas Type , Title y Description que contienen el tipo de cada una de las páginas de ejemplo, un título y una breve descripción.This ViewModel is a definition of a short class with three properties named Type, Title, and Description that contain the type of each of the sample pages, a title, and a short description. Además, el ViewModel define una propiedad estática denominada All que es una colección de todas las páginas del programa:In addition, the ViewModel defines a static property named All that is a collection of all the pages in the program:

public class PageDataViewModel
{
    public PageDataViewModel(Type type, string title, string description)
    {
        Type = type;
        Title = title;
        Description = description;
    }

    public Type Type { private set; get; }

    public string Title { private set; get; }

    public string Description { private set; get; }

    static PageDataViewModel()
    {
        All = new List<PageDataViewModel>
        {
            // Part 1. Getting Started with XAML
            new PageDataViewModel(typeof(HelloXamlPage), "Hello, XAML",
                                  "Display a Label with many properties set"),

            new PageDataViewModel(typeof(XamlPlusCodePage), "XAML + Code",
                                  "Interact with a Slider and Button"),

            // Part 2. Essential XAML Syntax
            new PageDataViewModel(typeof(GridDemoPage), "Grid Demo",
                                  "Explore XAML syntax with the Grid"),

            new PageDataViewModel(typeof(AbsoluteDemoPage), "Absolute Demo",
                                  "Explore XAML syntax with AbsoluteLayout"),

            // Part 3. XAML Markup Extensions
            new PageDataViewModel(typeof(SharedResourcesPage), "Shared Resources",
                                  "Using resource dictionaries to share resources"),

            new PageDataViewModel(typeof(StaticConstantsPage), "Static Constants",
                                  "Using the x:Static markup extensions"),

            new PageDataViewModel(typeof(RelativeLayoutPage), "Relative Layout",
                                  "Explore XAML markup extensions"),

            // Part 4. Data Binding Basics
            new PageDataViewModel(typeof(SliderBindingsPage), "Slider Bindings",
                                  "Bind properties of two views on the page"),

            new PageDataViewModel(typeof(SliderTransformsPage), "Slider Transforms",
                                  "Use Sliders with reverse bindings"),

            new PageDataViewModel(typeof(ListViewDemoPage), "ListView Demo",
                                  "Use a ListView with data bindings"),

            // Part 5. From Data Bindings to MVVM
            new PageDataViewModel(typeof(OneShotDateTimePage), "One-Shot DateTime",
                                  "Obtain the current DateTime and display it"),

            new PageDataViewModel(typeof(ClockPage), "Clock",
                                  "Dynamically display the current time"),

            new PageDataViewModel(typeof(HslColorScrollPage), "HSL Color Scroll",
                                  "Use a view model to select HSL colors"),

            new PageDataViewModel(typeof(KeypadPage), "Keypad",
                                  "Use a view model for numeric keypad logic")
        };
    }

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

El archivo XAML para MainPage define una ListBox cuya ItemsSource propiedad se establece en esa All propiedad y que contiene un TextCell para mostrar las Title Description propiedades y de cada página:The XAML file for MainPage defines a ListBox whose ItemsSource property is set to that All property and which contains a TextCell for displaying the Title and Description properties of each page:

<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.MainPage"
             Padding="5, 0"
             Title="XAML Samples">

    <ListView ItemsSource="{x:Static local:PageDataViewModel.All}"
              ItemSelected="OnListViewItemSelected">
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextCell Text="{Binding Title}"
                          Detail="{Binding Description}" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

Las páginas se muestran en una lista desplazable:The pages are shown in a scrollable list:

El controlador del archivo de código subyacente se desencadena cuando el usuario selecciona un elemento.The handler in the code-behind file is triggered when the user selects an item. El controlador establece la SelectedItem propiedad del de ListBox nuevo en null y, a continuación, crea una instancia de la página seleccionada y navega a ella:The handler sets the SelectedItem property of the ListBox back to null and then instantiates the selected page and navigates to it:

private async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)
{
    (sender as ListView).SelectedItem = null;

    if (args.SelectedItem != null)
    {
        PageDataViewModel pageData = args.SelectedItem as PageDataViewModel;
        Page page = (Page)Activator.CreateInstance(pageData.Type);
        await Navigation.PushAsync(page);
    }
}

VídeoVideo

Xamarin evolucione 2016: MVVM facilitado con Xamarin.Forms y PrismXamarin Evolve 2016: MVVM Made Simple with Xamarin.Forms and Prism

ResumenSummary

XAML es una herramienta eficaz para definir interfaces de usuario en Xamarin.Forms aplicaciones, especialmente cuando se usan enlace de datos y MVVM.XAML is a powerful tool for defining user interfaces in Xamarin.Forms applications, particularly when data-binding and MVVM are used. El resultado es una representación limpia, elegante y potencialmente compatible de una interfaz de usuario con toda la compatibilidad en segundo plano en el código.The result is a clean, elegant, and potentially toolable representation of a user interface with all the background support in code.

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.Find more Xamarin videos on Channel 9 and YouTube.