Parte 5. Da data binding a MVVM

Download Sample Scaricare l'esempio

Il modello architetturale Model-View-ViewModel (MVVM) è stato inventato tenendo presente XAML. Il modello applica una separazione tra tre livelli software, ovvero l'interfaccia utente XAML, denominata View; i dati sottostanti, denominati Modello; e un intermediario tra View e Model, denominato ViewModel. View e ViewModel sono spesso connessi tramite data binding definiti nel file XAML. BindingContext per view è in genere un'istanza di ViewModel.

Modello di visualizzazione semplice

Come introduzione a ViewModels, esaminiamo prima un programma senza uno. In precedenza è stato illustrato come definire una nuova dichiarazione dello spazio dei nomi XML per consentire a un file XAML di fare riferimento alle classi in altri assembly. Ecco un programma che definisce una dichiarazione dello spazio dei nomi XML per lo spazio dei System nomi:

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

Il programma può usare x:Static per ottenere la data e l'ora correnti dalla proprietà statica DateTime.Now e impostare tale DateTime valore su su BindingContext in un StackLayoutoggetto :

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

BindingContext è una proprietà speciale: quando si imposta l'oggetto BindingContext su un elemento , viene ereditato da tutti gli elementi figlio di tale elemento. Ciò significa che tutti gli elementi figlio di StackLayout hanno lo stesso BindingContextoggetto e possono contenere associazioni semplici alle proprietà di tale oggetto.

Nel programma One-Shot DateTime, due degli elementi figlio contengono associazioni alle proprietà di tale DateTime valore, ma altri due elementi figlio contengono associazioni che sembrano mancare un percorso di associazione. Ciò significa che il DateTime valore stesso viene usato per :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>

Il problema è che la data e l'ora vengono impostate una volta alla prima compilazione della pagina e non cambiano mai:

View Displaying Date and Time

Un file XAML può visualizzare un orologio che mostra sempre l'ora corrente, ma richiede codice per aiutare. Quando si pensa in termini di MVVM, Model e ViewModel sono classi scritte interamente nel codice. La visualizzazione è spesso un file XAML che fa riferimento alle proprietà definite in ViewModel tramite data binding.

Un modello appropriato è ignorante di ViewModel e un ViewModel appropriato è ignorante della visualizzazione. Tuttavia, spesso un programmatore adatta i tipi di dati esposti da ViewModel ai tipi di dati associati a interfacce utente specifiche. Ad esempio, se un modello accede a un database che contiene stringhe ASCII a 8 bit, ViewModel dovrà eseguire la conversione tra tali stringhe in stringhe Unicode per supportare l'uso esclusivo di Unicode nell'interfaccia utente.

In semplici esempi di MVVM (ad esempio quelli mostrati qui), spesso non esiste alcun modello e il modello implica solo un view e ViewModel collegato con i data binding.

Ecco un viewModel per un orologio con una sola proprietà denominata DateTime, che aggiorna tale DateTime proprietà ogni secondo:

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 implementa in genere l'interfaccia INotifyPropertyChanged , il che significa che la classe genera un PropertyChanged evento ogni volta che una delle relative proprietà cambia. Il meccanismo di data binding in Xamarin.Forms associa un gestore a questo PropertyChanged evento in modo che possa ricevere una notifica quando una proprietà cambia e mantenere aggiornata la destinazione con il nuovo valore.

Un orologio basato su questo ViewModel può essere semplice come segue:

<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>

Si noti che l'oggetto ClockViewModel è impostato su BindingContext dell'oggetto dei tag dell'elemento Label della proprietà . In alternativa, è possibile creare un'istanza di ClockViewModel in una Resources raccolta e impostarla su tramite un'estensione BindingContextStaticResource di markup. In alternativa, il file code-behind può creare un'istanza di ViewModel.

Estensione Binding di markup nella Text proprietà della Label proprietà formatta la DateTime proprietà . Ecco la visualizzazione:

View Displaying Date and Time via ViewModel

È anche possibile accedere alle singole proprietà della DateTime proprietà di ViewModel separando le proprietà con i punti:

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

MVVM interattivo

MVVM viene spesso usato con data binding bidirezionali per una visualizzazione interattiva basata su un modello di dati sottostante.

Ecco una classe denominata HslViewModel che converte un Color valore in Huevalori , Saturatione Luminosity e e viceversa:

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));
        }
    }
}

Le modifiche apportate alle Hueproprietà , Saturatione Luminosity causano la modifica della Color proprietà e le modifiche Color causano la modifica delle altre tre proprietà. Questo potrebbe sembrare un ciclo infinito, ad eccezione del fatto che la classe non richiama l'evento PropertyChanged a meno che la proprietà non sia stata modificata. In questo modo viene risolto un ciclo di feedback non controllabile.

Il file XAML seguente contiene una BoxView la cui Color proprietà è associata alla Color proprietà di ViewModel e tre e tre SliderLabel visualizzazioni associate alle Hueproprietà , Saturatione Luminosity :

<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>

L'associazione per ogni Label è l'oggetto predefinito OneWay. Deve solo visualizzare il valore. Ma l'associazione su ogni Slider oggetto è TwoWay. In questo modo l'oggetto Slider può essere inizializzato da ViewModel. Si noti che la Color proprietà è impostata su Aqua quando viene creata un'istanza di ViewModel. Tuttavia, una modifica nell'oggetto Slider deve anche impostare un nuovo valore per la proprietà in ViewModel, che quindi calcola un nuovo colore.

MVVM using Two-Way Data Bindings

Esecuzione di comandi con ViewModels

In molti casi, il modello MVVM è limitato alla manipolazione degli elementi di dati: oggetti dell'interfaccia utente negli oggetti dati paralleli view in ViewModel.

Tuttavia, a volte la visualizzazione deve contenere pulsanti che attivano varie azioni in ViewModel. Tuttavia, ViewModel non deve contenere Clicked gestori per i pulsanti perché collega viewModel a un paradigma specifico dell'interfaccia utente.

Per consentire a ViewModel di essere più indipendenti da specifici oggetti dell'interfaccia utente, ma consentire comunque la chiamata dei metodi all'interno di ViewModel, esiste un'interfaccia di comando . Questa interfaccia di comando è supportata dagli elementi seguenti in Xamarin.Forms:

  • Button
  • MenuItem
  • ToolbarItem
  • SearchBar
  • TextCell (e quindi anche ImageCell)
  • ListView
  • TapGestureRecognizer

Ad eccezione dell'elemento SearchBar e ListView , questi elementi definiscono due proprietà:

  • Command di tipo System.Windows.Input.ICommand
  • CommandParameter di tipo Object

Definisce SearchBar e proprietà, mentre definisce ListView una RefreshCommand proprietà di tipo ICommand.SearchCommandParameterSearchCommand

L'interfaccia ICommand definisce due metodi e un evento:

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

ViewModel può definire proprietà di tipo ICommand. È quindi possibile associare queste proprietà alla Command proprietà di un Button elemento o di un altro elemento o ad esempio una visualizzazione personalizzata che implementa questa interfaccia. Facoltativamente, è possibile impostare la CommandParameter proprietà per identificare singoli Button oggetti (o altri elementi) associati a questa proprietà ViewModel. Internamente, chiama Button il Execute metodo ogni volta che l'utente tocca , Buttonpassando al Execute metodo il relativo CommandParameter.

Il CanExecute metodo e CanExecuteChanged l'evento vengono usati per i casi in cui un Button tocco potrebbe non essere valido, nel qual caso deve Button disabilitare se stesso. Chiama ButtonCanExecute quando la Command proprietà viene impostata per la prima volta e ogni volta che viene generato l'evento CanExecuteChanged . Se CanExecute restituisce false, l'oggetto Button si disabilita e non genera Execute chiamate.

Per informazioni sull'aggiunta di comandi a ViewModels, Xamarin.Forms definisce due classi che implementano ICommand: Command e Command<T> dove T è il tipo degli argomenti in Execute e CanExecute. Queste due classi definiscono diversi costruttori e un ChangeCanExecute metodo che viewModel può chiamare per forzare l'evento CommandCanExecuteChanged .

Ecco un viewModel per un tastierino semplice destinato all'immissione di numeri di telefono. Si noti che il Execute metodo e CanExecute sono definiti come funzioni lambda direttamente nel costruttore:

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));
        }
    }
}

Questo ViewModel presuppone che la AddCharCommand proprietà sia associata alla Command proprietà di più pulsanti (o qualsiasi altro elemento con un'interfaccia CommandParameterdi comando), ognuno dei quali è identificato da . Questi pulsanti aggiungono caratteri a una InputString proprietà, che viene quindi formattata come numero di telefono per la DisplayText proprietà .

Esiste anche una seconda proprietà di tipo ICommand denominata DeleteCharCommand. Questa opzione è associata a un pulsante di spaziatura indietro, ma il pulsante deve essere disabilitato se non sono presenti caratteri da eliminare.

Il tastierino seguente non è così sofisticato visivamente come potrebbe essere. Al contrario, il markup è stato ridotto al minimo per illustrare più chiaramente l'uso dell'interfaccia del comando:

<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 proprietà del primo Button oggetto visualizzato in questo markup è associata a DeleteCharCommand. Il resto è associato a con un CommandParameter oggetto che corrisponde al AddCharCommand carattere visualizzato sul Button viso. Ecco il programma in azione:

Calculator using MVVM and Commands

Richiamo di metodi asincroni

I comandi possono anche richiamare metodi asincroni. A tale scopo, usare le async parole chiave e await quando si specifica il Execute metodo :

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

Ciò indica che il DownloadAsync metodo è un Task oggetto e deve essere atteso:

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

void Download ()
{
    ...
}

Implementazione di un menu di spostamento

Il programma XamlSamples che contiene tutto il codice sorgente di questa serie di articoli usa viewModel per la home page. Questo ViewModel è una definizione di una classe breve con tre proprietà denominate Type, Titlee Description che contengono il tipo di ognuna delle pagine di esempio, un titolo e una breve descrizione. Inoltre, ViewModel definisce una proprietà statica denominata All che è un insieme di tutte le pagine del programma:

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; }
}

Il file XAML per MainPage definisce una ListBox la cui ItemsSource proprietà è impostata su tale All proprietà e che contiene un TextCell oggetto per la visualizzazione delle Title proprietà e Description di ogni pagina:

<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>

Le pagine vengono visualizzate in un elenco scorrevole:

Scrollable list of pages

Il gestore nel file code-behind viene attivato quando l'utente seleziona un elemento. Il gestore imposta la SelectedItem proprietà della proprietà di ListBox nuovo su null e quindi crea un'istanza della pagina selezionata e lo passa:

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);
    }
}

Video

Xamarin Evolve 2016: MVVM reso semplice con Xamarin.Forms e prism

Riepilogo

XAML è uno strumento potente per definire le interfacce utente nelle Xamarin.Forms applicazioni, in particolare quando vengono usati data binding e MVVM. Il risultato è una rappresentazione pulita, elegante e potenzialmente utilizzabile di un'interfaccia utente con tutto il supporto in background nel codice.

Altri video di Xamarin sono disponibili su Channel 9 e YouTube.