Mobiele apps programmeren met Xamarin – MVVM

print
Deze handleiding maakt deel uit van het programmeertraject:


Inhoud


Wat vooraf ging


Wat is MVVM?

Model-View-ViewModel (MVVM) is een architectuur patroon om de gebruikersinterface (View) te scheiden van de achterliggende databank en Business rules (Model). De verbinding, communicatie tussen de View en het Model verloopt via de ViewModel. De gegevens die getoond moeten worden (via de View) worden dus niet rechtstreeks uit de databank gehaald (Model) maar worden aangereikt door de ViewModel.

  • Modelbevat (de connectie naar) de databank en de Business rules.

Model classes are non-visual classes that encapsulate the app’s data. Therefore, the model can be thought of as representing the app’s domain model, which usually includes a data model along with business and validation logic.

  • Viewtoont de informatie (op het scherm). Dit zijn uw Xamarin Forms gecreëerd in XAML door middel van Bindings. De View bevat hoofdzakelijk XAML en zo weinig mogelijk code-behind, enkel het hoogstnodige om de verbinding te maken met de ViewModel. De View bepaalt hoe de informatie wordt bezorgd aan de gebruiker.

The view is responsible for defining the structure, layout, and appearance of what the user sees on screen. Ideally, each view is defined in XAML, with a limited code-behind that does not contain business logic. However, in some cases, the code-behind might contain UI logic that implements visual behavior that is difficult to express in XAML, such as animations.

  • ViewModelreageert op events komende van de View en levert de gevraagde data aan de View. De ViewModel bepaalt wat er moet getoond worden, welke informatie bezorgd moet worden aan de View en haalt deze informatie uit het Model.

The view model implements properties and commands to which the view can data bind to, and notifies the view of any state changes through change notification events. The properties and commands that the view model provides define the functionality to be offered by the UI, but the view determines how that functionality is to be displayed.

De View is platform specifix, uw Xamarin Forms. De ViewModel en Model zijn beide portable, dit wilt zeggen dat deze kunnen hergebruikt worden in verschillende projecten.

In kleinere projecten zonder databank kan het gebeuren dat er enkel een View en ViewModel is en dus geen Model, dit kan en mag.

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.


Data Binding

De connectie, verbinding tussen de View en de ViewModel verloopt via Data Binding.

Dit is niets nieuws voor ons, XAML Binding is reeds eerder besproken en zijn een essentieel onderdeel van XAML waar MVVM dankbaar gebruik van maakt. Bindings zijn dus niet uitgevonden om MVVM mogelijk te maken, MVVM is mogelijk omdat Bindings in XAML onderdeel zijn van XAML.

Ik herneem even onderstaande afbeelding, die reeds gebruikt werd in de handleiding over XAML Binding.

In een MVVM-context vertaalt deze afbeelding zich is als:

  • Source is een (data) object met een eigenschap/property (bv. de eigenschap Naam) binnen de ViewModel.
  • Target is een element binnen uw View (een UI Control in Xamarin Forms) dat eveneens een eigenschap/property bevat (bv. een Label met de eigenschap Text).
  • Binding verbindt nu de eigenschap van het object in de ViewModel met een eigenschap van een element binnen uw View (toont dus de waarde van de eigenschap Naam in de eigenschap Text van een Label).

Voorbeeldproject

Om de zaken niet nodeloos complex te maken beperken we ons in dit project tot de essentie van MVVM en negeren we zaken als databanken connecteren, wijzigen, opslaan,… Het accent komt dan ook te liggen op de Binding tussen de View en de ViewModel.

De Model, die normaal de databank en Business logic/rules bevat wordt wel aangemaakt maar verder genegeerd. In latere handleidingen, wanneer we het specifiek over de databank gaan hebben wordt er een volledig uitgewerkt voorbeeld gegeven op basis MVVM.

De essentie van MVVM is orde scheppen, laten we dit dan ook meteen doen in ons project. Hoewel niet verplicht wordt in een MVVM-project drie folders aangemaakt respectievelijk voor de Models, ViewModels en View.

  • Klik rechts op uw gedeeld project (portable) en kies AddNew Folder.

  • Voeg 3 folders toe: Models, ViewModels en Views.

Hoewel we de folder Views aangemaakt hebben, hebben we eigenlijk maar één View in dit project en dat is de reeds bestaande Mainpage die we dan ook gaan gebruiken. Onze Views map zal dus leeg blijven.

Model

Zoals reeds aangehaald wordt in deze handleiding de nadruk gelegd op de data binding in MVVM en werken we zonder achterliggende databank, dit komt aan bod in een latere handleiding. Bijgevolg is een Model eigenlijk overbodig.

We gaan toch Model aanmaken met de naam Contact maar hij blijft leeg achter.

  • Klik rechts op de folder Model en kies AddClass….
  • Geef de Class de gewenste naam (bv. Contact).

We hebben een klasse die we als Model zouden kunnen gebruiken, we laten ze echter leeg voor dit voorbeeld.

namespace TestMVVM.Models
{
    class Contact
    {
    }
}

Oké, voor de nieuwsgierigen onder ons, iedereen dus, hoe zou zo’n Model er dan kunnen uitzien. Wel, het Model bevat de overeenkomende velden van de databank en kan er bv. als volgt uitzien:

namespace TestMVVM.Models
{
    [Table("Contact")]
    public class Contact
    {
        [PrimaryKey, AutoIncrement]
        public int Id { get; set; }
        public string Naam { get; set; }
        public string Geslacht { get; set; }
        public DateTime Geboortedatum { get; set; }
        public string Adres { get; set; }
        public string Telefoon { get; set; }
    }
}

Er wordt een tabel Contact aangemaakt. Id is de PrimaryKey en is ook AutoIncrement (automatisch verhoogd met 1).

Moest u de code toevoegen zal u fouten krijgen omdat de nodige bibliotheken (SQLite) niet zijn toegevoegd. We houden onze klasse voor dit voorbeeld dus gewoon leeg.

namespace TestMVVM.Models
{
    class Contact
    {
    }
}

View

De View is de weergave naar de eindgebruiker, op het scherm, in XAML.

We maken het onderstaande aan:

 

  • Wijzig Mainpage.xaml zoals hieronder. De XAML, voorlopig zonder Binding, zou geen geheimen mogen bevatten.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:TestMVVM" x:Class="TestMVVM.MainPage">

    <StackLayout Padding="10" Spacing="10">
        <Label Text="Naam"/>
        <Entry Placeholder="Naam"/>

        <Label Text="Website"/>
        <Entry Placeholder="URL" />

        <StackLayout Orientation="Horizontal" Spacing="10">
            <Label Text="Beste vriend?"/>
            <Switch />
        </StackLayout>

        <Label Text="Hier komt een boodschap."/>

        <Button Text="Open Website" />
        <Button Text="Bewaar Contact"/>

        <ActivityIndicator />
    </StackLayout>

</ContentPage>

ViewModel

De ViewModel verbindt de View met de Model.

  • Klik rechts op de folder ViewModels en kies AddClass….
  • Geef de Class de gewenste naam (bv. ContactViewModel).
  • Pas de code aan zoals hieronder.
using System;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace TestMVVM.ViewModels
{
    public class ContactViewModel
    {
        public ContactViewModel()
        {
        }

        string name = "Geert Linthoudt";
        string website = "https://www.pcvogroeipunt.be/";
        bool bestFriend;
        bool isBusy = false;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public string Website
        {
            get { return website; }
            set { website = value; }
        }

        public bool BestFriend
        {
            get { return bestFriend; }
            set { bestFriend = value; }
        }

        public bool IsBusy
        {
            get { return isBusy; }
            set { isBusy = value; }
        }

        public string DisplayMessage
        {
            get
            {
                return $"Uw nieuw contact is {Name} en " +
                       $"{(bestFriend ? "is" : "is niet")} uw beste vriend.";
            }
        }

        void LaunchWebsite()
        {
            try
            {
                Device.OpenUri(new Uri(website));
            }
            catch
            {

            }
        }

        async Task SaveContact()
        {
            IsBusy = true;
            await Task.Delay(4000);

            IsBusy = false;

            await Application.Current.MainPage.DisplayAlert("Save", "Contact is bewaard.", "OK");
        }

    }
}

Start met de Class public te maken, net als de eigenschappen/variabelen die we wensen te gebruiken in de View.

We hebben, een voorlopig lege, constructor.

We hebben een aantal eigenschappen (Name, Website, BestFriend en IsBusy) die we waarden toekennen (let op het hoofdlettergebruik!). In een “echte” applicatie, met een databank, zijn dit de velden uit een tabel.

We hebben 3 methoden DisplayMessage, LaunchWebsite en SaveContact. Merk op dat LauchWebsite en SaveContact niet public zijn, oké, het had gekund maar door ze niet public te maken zijn we zeker dat ze niet van buitenaf gewijzigd kunnen worden.

DisplayMessage gebruikt een nieuwe syntax (sinds C# 6.0) om tekst weer te geven op basis van Sting Interpolation return $"Uw nieuw contact is {Name} en " + $"{(bestFriend ? "is" : "is niet")} uw beste vriend.";

De methode SaveContact werkt asynchroon en “faket” het opslaan in een databank.

Als we deze code laten lopen gaan we de ingevulde waarden, zoals de naam, de website niet zien verschijnen, ze zijn nog niet verbonden met de View.

Data Binding

Data Binding of hoe verbinden we de ViewModel, die de informatie aanreikt, met de View die de informatie weergeeft?

Data Binding gebeurt eigenlijk op dezelfde manier als XAML Binding dat reeds eerder besproken is.

Bijvoorbeeld, om de waarde van de Naam te verbinden met de Text-eigenschap van een Entry gebruikt u onderstaande code.

<Entry Text="{Binding Name}" Placeholder="Full Name"/>

De Binding staat tussen {} en bevat het woordje Binding gevolgd door de eigenschap/variabele uit de ViewModel dat moet worden verbonden.

Op eenzelfde manier kunnen we de string, weergegeven door de functie DisplayMessage verbinden met de Text-eigenschap van een Label.

<Label Text="{Binding DisplayMessage}"/>

Let op dat de datatypen overeenkomen bij een Binding. Zo is de variabele/eigenschap BestFriend een Boolean en dient dit verbonden te worden met een eigenschap van een View-element dat eveneens een Boolean is zoals de IsToggled-eigenschap van een Switch-element.

<Switch IsToggled="{Binding BestFriend}"/>
  • Pas de XAML van MainPage.xaml aan.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:TestMVVM" x:Class="TestMVVM.MainPage">

    <StackLayout Padding="10" Spacing="10">
        <Label Text="Naam"/>
        <Entry Text="{Binding Name}" Placeholder="Naam"/>

        <Label Text="Website"/>
        <Entry Text="{Binding Website}" Placeholder="URL" />

        <StackLayout Orientation="Horizontal" Spacing="10">
            <Label Text="Beste vriend?"/>
            <Switch IsToggled="{Binding BestFriend}" />
        </StackLayout>

        <Label Text="{Binding DisplayMessage}"/>

        <Button Text="Open Website" />
        <Button Text="Bewaar Contact"/>

        <ActivityIndicator IsRunning="{Binding IsBusy}"/>
    </StackLayout>

</ContentPage>

Als u dit laat lopen, krijgt u weliswaar geen fouten, maar ziet u nog steeds geen naam of website verschijnen!

We moeten de View nog laten weten met welke ViewModel er moet verbonden worden, dit gebeurt in de Code-behind. In de Code-behind stellen we de BindingContext gelijk aan de ViewModel dat we wensen te verbinden.

BindingContext = new ContactViewModel();

Om dit te laten werken moeten we wel nog meegeven dat we gebruik willen maken van de aangemaakte Folder binnen ons project.

using TestMVVM.ViewModels;

Hier is TestMVVM de naam van mijn project en ViewModels de naam van de folder die de ViewModel, die verbonden moet worden, bevat.

Door de BindingContect op het niveau van de Code-behind, op het niveau van het volledige formulier te plaatsen maken, zijn alle elementen van het formulier ook meteen verbonden. De BindingContext verbindt dus alle elementen die eronder liggen (alle Children).

  • Pas de Code-behind van MainPage.xaml.cs als volgt aan:
using Xamarin.Forms;
using TestMVVM.ViewModels;

namespace TestMVVM
{
	public partial class MainPage : ContentPage
	{
		public MainPage()
		{
			InitializeComponent();
			BindingContext = new ContactViewModel();
		}
	}
}
  • Laat het programmaatje lopen en merk op dat de naam, de website,… nu wel getoond wordt, net als de boodschap.

Maar, als u nu de Switch verschuift naar “Beste vriend?” dan wordt de bijhorende tekst niet aangepast. Nochtans, een Binding is standaard TwoWay, wat betekent dat een verandering van een waarde in de View ook meteen moet worden doorgegeven aan de ViewModel. Dit gebeurt ook, alleen wordt het niet weergegeven in de View (op het scherm).

We zijn er dus nog niet. We moeten een methode vinden die de View met iedere verandering meteen aanpast. Dit gebeurt via Notificatie.

Notificatie

Onder Notificatie wordt verstaan, het automatisch herkennen, en doorgeven aan de View, dat er een eigenschap gewijzigd is.

Notificatie gebeurt in de ViewModel.

Om gebruik te kunnen maken van Notificatie moet de klasse overerven van INotifyPropertyChanged.

Er wordt ons attent gemaakt dat we Namespace using System.ComponentModel; moeten toevoegen.

Vervolgens worden we er attent op gemaakt dat we een interface moeten toevoegen.

Klik dit aan en onderstaande wordt automatisch toegevoegd.

public event PropertyChangedEventHandler PropertyChanged;

Iedere keer een eigenschap/property wijzigt zal een event PropertyChanged aangeroepen worden.

Deze PropertyChanged-event ziet er als volgt uit:

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

Merk het ? op dat ervoor zorgt dat de bijhorende code enkel uitgevoerd wordt indien de meegeleverd waarde niet null is.

Bij het wijzigen (SET) van een eigenschap/property moet de event PropertyChanged worden aangeroepen. Bv. bij het wijzigen van de eigenschap/property Name. Merk op dat we hier tegelijkertijd ook de methode DisplayMessage wijzigen zodat de bijhorende boodschap gewijzigd kan worden.

public string Name
{
    get { return name; }
    set {
        name = value;
        OnPropertyChanged(nameof(Name));
        OnPropertyChanged(nameof(DisplayMessage));
    }
}

Merk op dat we de naam van de gewijzigde eigenschap/property dienen mee te geven via de functie nameof(Name).

Oké, dit werkt, maar we kunnen dit nog een stapje optimaliseren zodat we niet telkens de naam van de property/eigenschap die gewijzigd is moeten meegeven. Daartoe moeten we eerst een parameter [CallerMemberName] meegeven aan de event PropertyChanged. Vergeet ook niet een standaardwaarde toe te kennen aan de parameter string name = "".

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

We dienen hiertoe ook de Namespace using System.Runtime.CompilerServices; op te nemen!

using System.Runtime.CompilerServices;

De nameof() van de eigen property/eigenschap mag nu worden weggelaten, niet die van een andere property/eigenschap of methode zoals nPropertyChanged(nameof(DisplayMessage));
.

public string Name
{
    get { return name; }
    set {
        name = value;
        OnPropertyChanged();
        OnPropertyChanged(nameof(DisplayMessage));
    }
}
  • Pas de code van ContactViewModel.cs als volgt aan:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace TestMVVM.ViewModels
{
    public class ContactViewModel : INotifyPropertyChanged
    {
        public ContactViewModel()
        {
        }

        string name = "Geert Linthoudt";
        string website = "https://www.pcvogroeipunt.be/";
        bool bestFriend;
        bool isBusy = false;

        public event PropertyChangedEventHandler PropertyChanged;

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

        public string Name
        {
            get { return name; }
            set {
                name = value;

                //Test de ActivityIndicator
                if (name == "Test")
                    IsBusy = true;
                else
                    IsBusy = false;

                OnPropertyChanged();
                OnPropertyChanged(nameof(DisplayMessage));
            }
        }

        public string Website
        {
            get
            {
                return website;
            }
            set
            {
                website = value;
                OnPropertyChanged();
            }
        }

        public bool BestFriend
        {
            get { return bestFriend; }
            set
            {
                bestFriend = value;
                OnPropertyChanged();
                OnPropertyChanged(nameof(DisplayMessage));
            }
        }

        public bool IsBusy
        {
            get { return isBusy; }
            set
            {
                isBusy = value;

                OnPropertyChanged();
            }
        }

        public string DisplayMessage
        {
            get
            {
                return $"Uw nieuw contact is {Name} en " +
                       $"{(bestFriend ? "is" : "is niet")} uw beste vriend.";
            }
        }

        void LaunchWebsite()
        {
            try
            {
                Device.OpenUri(new Uri(website));
            }
            catch
            {

            }
        }

        async Task SaveContact()
        {
            IsBusy = true;
            await Task.Delay(4000);

            IsBusy = false;

            await Application.Current.MainPage.DisplayAlert("Save", "Contact is bewaard.", "OK");
        }

    }
}
  • Laat het programmaatje lopen en merk op dat de boodschap wordt gewijzigd terwijl we typen, dankzij Notificatie.

Commands

Commands is de aangewezen manier om Events af te handelen (Event handling) in MVVM. U weet wel, een event zoals het klikken op een knop. We kunnen dit nog steeds op de klassieke manier, maar Commands bieden een elegantere oplossing, zonder bijkomende Code-behind.

Hieronder ziet u de flow van een Command.

Eerst gaan we de nodige Commands declareren in de ViewModel als volgt.

public Command LaunchWebsiteCommand { get; }
public Command SaveContactCommand { get; }

U maakt dus een public Command met de naam van de reeds bestaande functie gevolgd door het woordje Command (niet verplicht maar aanbevolen) en enkel een {get ; }

  • Ga naar de VieuwModel ContactViewMode en voeg de Commands toe.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace TestMVVM.ViewModels
{
    public class ContactViewModel : INotifyPropertyChanged
    {
        public ContactViewModel()
        {
        }

        string name = "Geert Linthoudt";
        string website = "https://www.pcvogroeipunt.be/";
        bool bestFriend;
        bool isBusy = false;

        public Command LaunchWebsiteCommand { get; }
        public Command SaveContactCommand { get; }

        public event PropertyChangedEventHandler PropertyChanged;

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

        public string Name
        {
            get { return name; }
            set {
                name = value;

                //Test de ActivityIndicator
                if (name == "Test")
                    IsBusy = true;
                else
                    IsBusy = false;

                OnPropertyChanged();
                OnPropertyChanged(nameof(DisplayMessage));
            }
        }

        public string Website
        {
            get
            {
                return website;
            }
            set
            {
                website = value;
                OnPropertyChanged();
            }
        }

        public bool BestFriend
        {
            get { return bestFriend; }
            set
            {
                bestFriend = value;
                OnPropertyChanged();
                OnPropertyChanged(nameof(DisplayMessage));
            }
        }

        public bool IsBusy
        {
            get { return isBusy; }
            set
            {
                isBusy = value;

                OnPropertyChanged();
            }
        }

        public string DisplayMessage
        {
            get
            {
                return $"Uw nieuw contact is {Name} en " +
                       $"{(bestFriend ? "is" : "is niet")} uw beste vriend.";
            }
        }

        void LaunchWebsite()
        {
            try
            {
                Device.OpenUri(new Uri(website));
            }
            catch
            {

            }
        }

        async Task SaveContact()
        {
            IsBusy = true;
            await Task.Delay(4000);

            IsBusy = false;

            await Application.Current.MainPage.DisplayAlert("Save", "Contact is bewaard.", "OK");
        }

    }
}

Vervolgens moet de Command-eigenschap van de betreffende XAML-elementen verbonden worden met het gewenste commando.

<Button Text="Open Website" Command="{Binding LaunchWebsiteCommand}"/>
<Button Text="Bewaar Contact" Command="{Binding SaveContactCommand}"/>
  • Pas de XAML van MainPage.xaml aan.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:TestMVVM" x:Class="TestMVVM.MainPage">

    <StackLayout Padding="10" Spacing="10">
        <Label Text="Naam"/>
        <Entry Text="{Binding Name}" Placeholder="Naam"/>

        <Label Text="Website"/>
        <Entry Text="{Binding Website}" Placeholder="URL" />

        <StackLayout Orientation="Horizontal" Spacing="10">
            <Label Text="Beste vriend?"/>
            <Switch IsToggled="{Binding BestFriend}" />
        </StackLayout>

        <Label Text="{Binding DisplayMessage}"/>

        <Button Text="Open Website" Command="{Binding LaunchWebsiteCommand}"/>
        <Button Text="Bewaar Contact" Command="{Binding SaveContactCommand}"/>

        <ActivityIndicator IsRunning="{Binding IsBusy}"/>
    </StackLayout>

</ContentPage>

Onze XAML is nu volledig klaar, er rest ons nog één ding, de Commands koppelen aan de te gebruiken functie.

  • Ga opnieuw naar de VieuwModel ContactViewMode.

We gaan dit in stapjes opbouwen.

Eerst gaat u, binnen de Constructor, de Commands koppelen aan de gewenste functie/methode.

public ContactViewModel()
{
    LaunchWebsiteCommand = new Command(LaunchWebsite);
    SaveContactCommand = new Command(SaveContact);
}

Merk echter op da er een fout gegeven wordt bij SaveContact. Als u naar de methode SaveContact kijkt ziet u meteen waarom, deze is asynchroon.

We passen onze code als volgt aan gebruikmakend van een Lambda Expressie en de =>-operator met de syntax () => SomeMethod()

public ContactViewModel()
{
    LaunchWebsiteCommand = new Command(LaunchWebsite);
    SaveContactCommand = new Command(async () => await SaveContact());
}
  • Start het programma en merk op dat de knoppen werken.

We gaan echter nog een kleine aanpassing maken: als de applicatie “Busy” is, kunnen de knoppen niet gebruikt worden.

U kunt dit bekomen door het toevoegen van een Lambda Expressie () => !IsBusy.

public ContactViewModel()
{
    LaunchWebsiteCommand = new Command(LaunchWebsite, () => !IsBusy);
    SaveContactCommand = new Command(async () => await SaveContact(), () => !IsBusy);
}

We moeten vervolgens vastleggen dat een verandering van IsBusy moet opgevangen worden door de Commandos. Dit gebeurt via ChangeCanExecute() binnen de IsBusy eigenschap/property.

public bool IsBusy
{
    get { return isBusy; }
    set
    {
        isBusy = value;

        OnPropertyChanged();
        LaunchWebsiteCommand.ChangeCanExecute();
        SaveContactCommand.ChangeCanExecute();
    }
}
  • Pas de code van ContactViewMode als volgt aan.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace TestMVVM.ViewModels
{
    public class ContactViewModel : INotifyPropertyChanged
    {

        public ContactViewModel()
        {
            LaunchWebsiteCommand = new Command(LaunchWebsite, () => !IsBusy);
            SaveContactCommand = new Command(async () => await SaveContact(), () => !IsBusy);
        }

        string name = "Geert Linthoudt";
        string website = "https://www.pcvogroeipunt.be/";
        bool bestFriend;
        bool isBusy = false;

        public Command LaunchWebsiteCommand { get; }
        public Command SaveContactCommand { get; }

        public event PropertyChangedEventHandler PropertyChanged;

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

        public string Name
        {
            get { return name; }
            set {
                name = value;

                OnPropertyChanged();
                OnPropertyChanged(nameof(DisplayMessage));
            }
        }

        public string Website
        {
            get
            {
                return website;
            }
            set
            {
                website = value;
                OnPropertyChanged();
            }
        }

        public bool BestFriend
        {
            get { return bestFriend; }
            set
            {
                bestFriend = value;
                OnPropertyChanged();
                OnPropertyChanged(nameof(DisplayMessage));
            }
        }

        public bool IsBusy
        {
            get { return isBusy; }
            set
            {
                isBusy = value;

                OnPropertyChanged();
                LaunchWebsiteCommand.ChangeCanExecute();
                SaveContactCommand.ChangeCanExecute();
            }
        }

        public string DisplayMessage
        {
            get
            {
                return $"Uw nieuw contact is {Name} en " +
                       $"{(bestFriend ? "is" : "is niet")} uw beste vriend.";
            }
        }

        void LaunchWebsite()
        {
            try
            {
                Device.OpenUri(new Uri(website));
            }
            catch
            {

            }
        }

        async Task SaveContact()
        {
            IsBusy = true;
            await Task.Delay(4000);

            IsBusy = false;

            await Application.Current.MainPage.DisplayAlert("Save", "Contact is bewaard.", "OK");
        }

    }
}
  • Start het programma en merk op dat terwijl u aan het bewaren bent, de knoppen niet beschikbaar zijn.


BaseViewModel

Als u meerdere ViewModels hebt kan het handig zijn dat u een BaseViewModel aanmaakt die de terugkerende code bevat (u weet wel, het DRY-principe).

Dit BaseViewModel handelt dan de OnPropertyChanged() af en kan nadien door andere ViewModels geërfd worden.

  • Klik rechts op de folder ViewModels en kies voor AddNew Item….
  • Selecteer een Class en geef het de naam BaseViewModel.

De code van BaseViewModel.cs kan er als volgt uitzien:

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace TestMVVM.ViewModels
{
    public class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

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

    }
}

Een ander ViewModel kan van deze BaseViewModel overerven en wordt zo wat “lichter”.

Bijvoorbeeld onderstaande, standaard AboutViewModel

using System;
using System.Windows.Input;

using Xamarin.Forms;

namespace TestMVVM.ViewModels
{
    public class AboutViewModel : BaseViewModel
    {
        public AboutViewModel()
        {
            Title = "About";

            OpenWebCommand = new Command(() => Device.OpenUri(new Uri("https://www.pcvogroeipunt.be/")));
        }

        public ICommand OpenWebCommand { get; }
    }
}

Hoor het eens van iemand anders

Onderstaande video is de bron van deze handleiding.

Geef een reactie

  • Abonneer je op deze website d.m.v. e-mail

    Voer je e-mailadres in om je in te schrijven op deze website en e-mailmeldingen te ontvangen van nieuwe berichten.